summaryrefslogtreecommitdiffstats
path: root/src/include/libplacebo
diff options
context:
space:
mode:
Diffstat (limited to 'src/include/libplacebo')
-rw-r--r--src/include/libplacebo/cache.h200
-rw-r--r--src/include/libplacebo/colorspace.h719
-rw-r--r--src/include/libplacebo/common.h244
-rw-r--r--src/include/libplacebo/config.h.in102
-rw-r--r--src/include/libplacebo/d3d11.h248
-rw-r--r--src/include/libplacebo/dispatch.h239
-rw-r--r--src/include/libplacebo/dither.h82
-rw-r--r--src/include/libplacebo/dummy.h131
-rw-r--r--src/include/libplacebo/filters.h415
-rw-r--r--src/include/libplacebo/gamut_mapping.h182
-rw-r--r--src/include/libplacebo/gpu.h1464
-rw-r--r--src/include/libplacebo/log.h113
-rw-r--r--src/include/libplacebo/meson.build6
-rw-r--r--src/include/libplacebo/opengl.h230
-rw-r--r--src/include/libplacebo/options.h201
-rw-r--r--src/include/libplacebo/renderer.h847
-rw-r--r--src/include/libplacebo/shaders.h273
-rw-r--r--src/include/libplacebo/shaders/colorspace.h381
-rw-r--r--src/include/libplacebo/shaders/custom.h341
-rw-r--r--src/include/libplacebo/shaders/deinterlacing.h137
-rw-r--r--src/include/libplacebo/shaders/dithering.h140
-rw-r--r--src/include/libplacebo/shaders/film_grain.h137
-rw-r--r--src/include/libplacebo/shaders/icc.h135
-rw-r--r--src/include/libplacebo/shaders/lut.h78
-rw-r--r--src/include/libplacebo/shaders/sampling.h257
-rw-r--r--src/include/libplacebo/swapchain.h171
-rw-r--r--src/include/libplacebo/tone_mapping.h268
-rw-r--r--src/include/libplacebo/utils/dav1d.h129
-rw-r--r--src/include/libplacebo/utils/dav1d_internal.h613
-rw-r--r--src/include/libplacebo/utils/dolbyvision.h34
-rw-r--r--src/include/libplacebo/utils/frame_queue.h230
-rw-r--r--src/include/libplacebo/utils/libav.h284
-rw-r--r--src/include/libplacebo/utils/libav_internal.h1482
-rw-r--r--src/include/libplacebo/utils/upload.h153
-rw-r--r--src/include/libplacebo/vulkan.h638
35 files changed, 11304 insertions, 0 deletions
diff --git a/src/include/libplacebo/cache.h b/src/include/libplacebo/cache.h
new file mode 100644
index 0000000..5897ac8
--- /dev/null
+++ b/src/include/libplacebo/cache.h
@@ -0,0 +1,200 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_CACHE_H_
+#define LIBPLACEBO_CACHE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <libplacebo/config.h>
+#include <libplacebo/common.h>
+#include <libplacebo/log.h>
+
+PL_API_BEGIN
+
+typedef struct pl_cache_obj {
+ // Cache object key. This will uniquely identify this cached object.
+ uint64_t key;
+
+ // Cache data pointer and length. 0-length cached objects are invalid
+ // and will be silently dropped. You can explicitly remove a cached
+ // object by overwriting it with a length 0 object.
+ void *data;
+ size_t size;
+
+ // Free callback, to free memory associated with `data`. (Optional)
+ // Will be called when the object is either explicitly deleted, culled
+ // due to hitting size limits, or on pl_cache_destroy().
+ void (*free)(void *data);
+} pl_cache_obj;
+
+struct pl_cache_params {
+ // Optional `pl_log` that is used for logging internal events related
+ // to the cache, such as insertions, saving and loading.
+ pl_log log;
+
+ // Size limits. If 0, no limit is imposed.
+ //
+ // Note: libplacebo will never detect or invalidate stale cache entries, so
+ // setting an upper size limit is strongly recommended
+ size_t max_object_size;
+ size_t max_total_size;
+
+ // Optional external callback to call after a cached object is modified
+ // (including deletion and (re-)insertion). Note that this is not called on
+ // objects which are merely pruned from the cache due to `max_total_size`,
+ // so users must rely on some external mechanism to prune stale entries or
+ // enforce size limits.
+ //
+ // Note: `pl_cache_load` does not trigger this callback.
+ // Note: Ownership of `obj` does *not* pass to the caller.
+ // Note: This function must be thread safe.
+ void (*set)(void *priv, pl_cache_obj obj);
+
+ // Optional external callback to call on a cache miss. Ownership of the
+ // returned object passes to the `pl_cache`. Objects returned by this
+ // callback *should* have a valid `free` callback, unless lifetime can be
+ // externally managed and guaranteed to outlive the `pl_cache`.
+ //
+ // Note: This function must be thread safe.
+ pl_cache_obj (*get)(void *priv, uint64_t key);
+
+ // External context for insert/lookup.
+ void *priv;
+};
+
+#define pl_cache_params(...) (&(struct pl_cache_params) { __VA_ARGS__ })
+PL_API extern const struct pl_cache_params pl_cache_default_params;
+
+// Thread-safety: Safe
+//
+// Note: In any context in which `pl_cache` is used, users may also pass NULL
+// to disable caching. In other words, NULL is a valid `pl_cache`.
+typedef const struct pl_cache_t {
+ struct pl_cache_params params;
+} *pl_cache;
+
+// Create a new cache. This function will never fail.
+PL_API pl_cache pl_cache_create(const struct pl_cache_params *params);
+
+// Destroy a `pl_cache` object, including all underlying objects.
+PL_API void pl_cache_destroy(pl_cache *cache);
+
+// Explicitly clear all objects in the cache without destroying it. This is
+// similar to `pl_cache_destroy`, but the cache remains valid afterwards.
+//
+// Note: Objects destroyed in this way *not* propagated to the `set` callback.
+PL_API void pl_cache_reset(pl_cache cache);
+
+// Return the current internal number of objects and total size (bytes)
+PL_API int pl_cache_objects(pl_cache cache);
+PL_API size_t pl_cache_size(pl_cache cache);
+
+// --- Cache saving and loading APIs
+
+// Serialize the internal state of a `pl_cache` into an abstract cache
+// object that can be e.g. saved to disk and loaded again later. Returns the
+// number of objects saved.
+//
+// Note: Using `save/load` is largely redundant with using `insert/lookup`
+// callbacks, and the user should decide whether to use the explicit API or the
+// callback-based API.
+PL_API int pl_cache_save_ex(pl_cache cache,
+ void (*write)(void *priv, size_t size, const void *ptr),
+ void *priv);
+
+// Load the result of a previous `pl_cache_save` call. Any duplicate entries in
+// the `pl_cache` will be overwritten. Returns the number of objects loaded, or
+// a negative number on serious error (e.g. corrupt header)
+//
+// Note: This does not trigger the `update` callback.
+PL_API int pl_cache_load_ex(pl_cache cache,
+ bool (*read)(void *priv, size_t size, void *ptr),
+ void *priv);
+
+// --- Convenience wrappers around pl_cache_save/load_ex
+
+// Writes data directly to a pointer. Returns the number of bytes that *would*
+// have been written, so this can be used on a size 0 buffer to get the required
+// total size.
+PL_API size_t pl_cache_save(pl_cache cache, uint8_t *data, size_t size);
+
+// Reads data directly from a pointer. This still reads from `data`, so it does
+// not avoid a copy.
+PL_API int pl_cache_load(pl_cache cache, const uint8_t *data, size_t size);
+
+// Writes/loads data to/from a FILE stream at the current position.
+#define pl_cache_save_file(c, file) pl_cache_save_ex(c, pl_write_file_cb, file)
+#define pl_cache_load_file(c, file) pl_cache_load_ex(c, pl_read_file_cb, file)
+
+static inline void pl_write_file_cb(void *priv, size_t size, const void *ptr)
+{
+ (void) fwrite(ptr, 1, size, (FILE *) priv);
+}
+
+static inline bool pl_read_file_cb(void *priv, size_t size, void *ptr)
+{
+ return fread(ptr, 1, size, (FILE *) priv) == size;
+}
+
+// --- Object modification API. Mostly intended for internal use.
+
+// Insert a new cached object into a `pl_cache`. Returns whether successful.
+// Overwrites any existing cached object with that signature, so this can be
+// used to e.g. delete objects as well (set their size to 0). On success,
+// ownership of `obj` passes to the `pl_cache`.
+//
+// Note: If `object.free` is NULL, this will perform an internal memdup. To
+// bypass this (e.g. when directly adding externally managed memory), you can
+// set the `free` callback to an explicit noop function.
+//
+// Note: `obj->data/free` will be reset to NULL on successful insertion.
+PL_API bool pl_cache_try_set(pl_cache cache, pl_cache_obj *obj);
+
+// Variant of `pl_cache_try_set` that simply frees `obj` on failure.
+PL_API void pl_cache_set(pl_cache cache, pl_cache_obj *obj);
+
+// Looks up `obj->key` in the object cache. If successful, `obj->data` is
+// set to memory owned by the caller, which must be either explicitly
+// re-inserted, or explicitly freed (using obj->free).
+//
+// Note: On failure, `obj->data/size/free` are reset to NULL.
+PL_API bool pl_cache_get(pl_cache cache, pl_cache_obj *obj);
+
+// Run a callback on every object currently stored in `cache`.
+//
+// Note: Running any `pl_cache_*` function on `cache` from this callback is
+// undefined behavior.
+PL_API void pl_cache_iterate(pl_cache cache,
+ void (*cb)(void *priv, pl_cache_obj obj),
+ void *priv);
+
+// Utility wrapper to free a `pl_cache_obj` if necessary (and sanitize it)
+static inline void pl_cache_obj_free(pl_cache_obj *obj)
+{
+ if (obj->free)
+ obj->free(obj->data);
+ obj->data = NULL;
+ obj->free = NULL;
+ obj->size = 0;
+}
+
+PL_API_END
+
+#endif // LIBPLACEBO_CACHE_H_
diff --git a/src/include/libplacebo/colorspace.h b/src/include/libplacebo/colorspace.h
new file mode 100644
index 0000000..6663019
--- /dev/null
+++ b/src/include/libplacebo/colorspace.h
@@ -0,0 +1,719 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_COLORSPACE_H_
+#define LIBPLACEBO_COLORSPACE_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <libplacebo/common.h>
+
+PL_API_BEGIN
+
+// The underlying color representation (e.g. RGB, XYZ or YCbCr)
+enum pl_color_system {
+ PL_COLOR_SYSTEM_UNKNOWN = 0,
+ // YCbCr-like color systems:
+ PL_COLOR_SYSTEM_BT_601, // ITU-R Rec. BT.601 (SD)
+ PL_COLOR_SYSTEM_BT_709, // ITU-R Rec. BT.709 (HD)
+ PL_COLOR_SYSTEM_SMPTE_240M, // SMPTE-240M
+ PL_COLOR_SYSTEM_BT_2020_NC, // ITU-R Rec. BT.2020 (non-constant luminance)
+ PL_COLOR_SYSTEM_BT_2020_C, // ITU-R Rec. BT.2020 (constant luminance)
+ PL_COLOR_SYSTEM_BT_2100_PQ, // ITU-R Rec. BT.2100 ICtCp PQ variant
+ PL_COLOR_SYSTEM_BT_2100_HLG, // ITU-R Rec. BT.2100 ICtCp HLG variant
+ PL_COLOR_SYSTEM_DOLBYVISION, // Dolby Vision (see pl_dovi_metadata)
+ PL_COLOR_SYSTEM_YCGCO, // YCgCo (derived from RGB)
+ // Other color systems:
+ PL_COLOR_SYSTEM_RGB, // Red, Green and Blue
+ PL_COLOR_SYSTEM_XYZ, // Digital Cinema Distribution Master (XYZ)
+ PL_COLOR_SYSTEM_COUNT
+};
+
+PL_API bool pl_color_system_is_ycbcr_like(enum pl_color_system sys);
+
+// Returns true for color systems that are linear transformations of the RGB
+// equivalent, i.e. are simple matrix multiplications. For color systems with
+// this property, `pl_color_repr_decode` is sufficient for conversion to RGB.
+PL_API bool pl_color_system_is_linear(enum pl_color_system sys);
+
+// Guesses the best YCbCr-like colorspace based on a image given resolution.
+// This only picks conservative values. (In particular, BT.2020 is never
+// auto-guessed, even for 4K resolution content)
+PL_API enum pl_color_system pl_color_system_guess_ycbcr(int width, int height);
+
+// Friendly names for the canonical channel names and order.
+enum pl_channel {
+ PL_CHANNEL_NONE = -1,
+ PL_CHANNEL_A = 3, // alpha
+ // RGB system
+ PL_CHANNEL_R = 0,
+ PL_CHANNEL_G = 1,
+ PL_CHANNEL_B = 2,
+ // YCbCr-like systems
+ PL_CHANNEL_Y = 0,
+ PL_CHANNEL_CB = 1,
+ PL_CHANNEL_CR = 2,
+ // Aliases for Cb/Cr
+ PL_CHANNEL_U = 1,
+ PL_CHANNEL_V = 2
+ // There are deliberately no names for the XYZ system to avoid
+ // confusion due to PL_CHANNEL_Y.
+};
+
+// The numerical range of the representation (where applicable).
+enum pl_color_levels {
+ PL_COLOR_LEVELS_UNKNOWN = 0,
+ PL_COLOR_LEVELS_LIMITED, // Limited/TV range, e.g. 16-235
+ PL_COLOR_LEVELS_FULL, // Full/PC range, e.g. 0-255
+ PL_COLOR_LEVELS_COUNT,
+
+ // Compatibility aliases
+ PL_COLOR_LEVELS_TV = PL_COLOR_LEVELS_LIMITED,
+ PL_COLOR_LEVELS_PC = PL_COLOR_LEVELS_FULL,
+};
+
+// The alpha representation mode.
+enum pl_alpha_mode {
+ PL_ALPHA_UNKNOWN = 0, // or no alpha channel present
+ PL_ALPHA_INDEPENDENT, // alpha channel is separate from the video
+ PL_ALPHA_PREMULTIPLIED, // alpha channel is multiplied into the colors
+ PL_ALPHA_MODE_COUNT,
+};
+
+// The underlying bit-wise representation of a color sample. For example,
+// a 10-bit TV-range YCbCr value uploaded to a 16 bit texture would have
+// sample_depth=16 color_depth=10 bit_shift=0.
+//
+// For another example, a 12-bit XYZ full range sample shifted to 16-bits with
+// the lower 4 bits all set to 0 would have sample_depth=16 color_depth=12
+// bit_shift=4. (libavcodec likes outputting this type of `xyz12`)
+//
+// To explain the meaning of `sample_depth` further; the consideration factor
+// here is the fact that GPU sampling will normalized the sampled color to the
+// range 0.0 - 1.0 in a manner dependent on the number of bits in the texture
+// format. So if you upload a 10-bit YCbCr value unpadded as 16-bit color
+// samples, all of the sampled values will be extremely close to 0.0. In such a
+// case, `pl_color_repr_normalize` would return a high scaling factor, which
+// would pull the color up to their 16-bit range.
+struct pl_bit_encoding {
+ int sample_depth; // the number of bits the color is stored/sampled as
+ int color_depth; // the effective number of bits of the color information
+ int bit_shift; // a representational bit shift applied to the color
+};
+
+// Returns whether two bit encodings are exactly identical.
+PL_API bool pl_bit_encoding_equal(const struct pl_bit_encoding *b1,
+ const struct pl_bit_encoding *b2);
+
+// Parsed metadata from the Dolby Vision RPU
+struct pl_dovi_metadata {
+ // Colorspace transformation metadata
+ float nonlinear_offset[3]; // input offset ("ycc_to_rgb_offset")
+ pl_matrix3x3 nonlinear; // before PQ, also called "ycc_to_rgb"
+ pl_matrix3x3 linear; // after PQ, also called "rgb_to_lms"
+
+ // Reshape data, grouped by component
+ struct pl_reshape_data {
+ uint8_t num_pivots;
+ float pivots[9]; // normalized to [0.0, 1.0] based on BL bit depth
+ uint8_t method[8]; // 0 = polynomial, 1 = MMR
+ // Note: these must be normalized (divide by coefficient_log2_denom)
+ float poly_coeffs[8][3]; // x^0, x^1, x^2, unused must be 0
+ uint8_t mmr_order[8]; // 1, 2 or 3
+ float mmr_constant[8];
+ float mmr_coeffs[8][3 /* order */][7];
+ } comp[3];
+};
+
+// Struct describing the underlying color system and representation. This
+// information is needed to convert an encoded color to a normalized RGB triple
+// in the range 0-1.
+struct pl_color_repr {
+ enum pl_color_system sys;
+ enum pl_color_levels levels;
+ enum pl_alpha_mode alpha;
+ struct pl_bit_encoding bits; // or {0} if unknown
+
+ // Metadata for PL_COLOR_SYSTEM_DOLBYVISION. Note that, for the sake of
+ // efficiency, this is treated purely as an opaque reference - functions
+ // like pl_color_repr_equal will merely do a pointer equality test.
+ //
+ // The only functions that actually dereference it in any way are
+ // pl_color_repr_decode, pl_shader_decode_color and pl_render_image(_mix).
+ const struct pl_dovi_metadata *dovi;
+};
+
+// Some common color representations. It's worth pointing out that all of these
+// presets leave `alpha` and `bits` as unknown - that is, only the system and
+// levels are predefined
+PL_API extern const struct pl_color_repr pl_color_repr_unknown;
+PL_API extern const struct pl_color_repr pl_color_repr_rgb;
+PL_API extern const struct pl_color_repr pl_color_repr_sdtv;
+PL_API extern const struct pl_color_repr pl_color_repr_hdtv; // also Blu-ray
+PL_API extern const struct pl_color_repr pl_color_repr_uhdtv; // SDR, NCL system
+PL_API extern const struct pl_color_repr pl_color_repr_jpeg;
+
+// Returns whether two colorspace representations are exactly identical.
+PL_API bool pl_color_repr_equal(const struct pl_color_repr *c1,
+ const struct pl_color_repr *c2);
+
+// Replaces unknown values in the first struct by those of the second struct.
+PL_API void pl_color_repr_merge(struct pl_color_repr *orig,
+ const struct pl_color_repr *update);
+
+// This function normalizes the color representation such that
+// color_depth=sample_depth and bit_shift=0; and returns the scaling factor
+// that must be multiplied into the color value to accomplish this, assuming
+// it has already been sampled by the GPU. If unknown, the color and sample
+// depth will both be inferred as 8 bits for the purposes of this conversion.
+PL_API float pl_color_repr_normalize(struct pl_color_repr *repr);
+
+// Guesses the best color levels based on the specified color levels and
+// falling back to using the color system instead. YCbCr-like systems are
+// assumed to be TV range, otherwise this defaults to PC range.
+PL_API enum pl_color_levels pl_color_levels_guess(const struct pl_color_repr *repr);
+
+// The colorspace's primaries (gamut)
+enum pl_color_primaries {
+ PL_COLOR_PRIM_UNKNOWN = 0,
+ // Standard gamut:
+ PL_COLOR_PRIM_BT_601_525, // ITU-R Rec. BT.601 (525-line = NTSC, SMPTE-C)
+ PL_COLOR_PRIM_BT_601_625, // ITU-R Rec. BT.601 (625-line = PAL, SECAM)
+ PL_COLOR_PRIM_BT_709, // ITU-R Rec. BT.709 (HD), also sRGB
+ PL_COLOR_PRIM_BT_470M, // ITU-R Rec. BT.470 M
+ PL_COLOR_PRIM_EBU_3213, // EBU Tech. 3213-E / JEDEC P22 phosphors
+ // Wide gamut:
+ PL_COLOR_PRIM_BT_2020, // ITU-R Rec. BT.2020 (UltraHD)
+ PL_COLOR_PRIM_APPLE, // Apple RGB
+ PL_COLOR_PRIM_ADOBE, // Adobe RGB (1998)
+ PL_COLOR_PRIM_PRO_PHOTO, // ProPhoto RGB (ROMM)
+ PL_COLOR_PRIM_CIE_1931, // CIE 1931 RGB primaries
+ PL_COLOR_PRIM_DCI_P3, // DCI-P3 (Digital Cinema)
+ PL_COLOR_PRIM_DISPLAY_P3, // DCI-P3 (Digital Cinema) with D65 white point
+ PL_COLOR_PRIM_V_GAMUT, // Panasonic V-Gamut (VARICAM)
+ PL_COLOR_PRIM_S_GAMUT, // Sony S-Gamut
+ PL_COLOR_PRIM_FILM_C, // Traditional film primaries with Illuminant C
+ PL_COLOR_PRIM_ACES_AP0, // ACES Primaries #0 (ultra wide)
+ PL_COLOR_PRIM_ACES_AP1, // ACES Primaries #1
+ PL_COLOR_PRIM_COUNT
+};
+
+PL_API bool pl_color_primaries_is_wide_gamut(enum pl_color_primaries prim);
+
+// Guesses the best primaries based on a resolution. This always guesses
+// conservatively, i.e. it will never return a wide gamut color space even if
+// the resolution is 4K.
+PL_API enum pl_color_primaries pl_color_primaries_guess(int width, int height);
+
+// The colorspace's transfer function (gamma / EOTF)
+enum pl_color_transfer {
+ PL_COLOR_TRC_UNKNOWN = 0,
+ // Standard dynamic range:
+ PL_COLOR_TRC_BT_1886, // ITU-R Rec. BT.1886 (CRT emulation + OOTF)
+ PL_COLOR_TRC_SRGB, // IEC 61966-2-4 sRGB (CRT emulation)
+ PL_COLOR_TRC_LINEAR, // Linear light content
+ PL_COLOR_TRC_GAMMA18, // Pure power gamma 1.8
+ PL_COLOR_TRC_GAMMA20, // Pure power gamma 2.0
+ PL_COLOR_TRC_GAMMA22, // Pure power gamma 2.2
+ PL_COLOR_TRC_GAMMA24, // Pure power gamma 2.4
+ PL_COLOR_TRC_GAMMA26, // Pure power gamma 2.6
+ PL_COLOR_TRC_GAMMA28, // Pure power gamma 2.8
+ PL_COLOR_TRC_PRO_PHOTO, // ProPhoto RGB (ROMM)
+ PL_COLOR_TRC_ST428, // Digital Cinema Distribution Master (XYZ)
+ // High dynamic range:
+ PL_COLOR_TRC_PQ, // ITU-R BT.2100 PQ (perceptual quantizer), aka SMPTE ST2048
+ PL_COLOR_TRC_HLG, // ITU-R BT.2100 HLG (hybrid log-gamma), aka ARIB STD-B67
+ PL_COLOR_TRC_V_LOG, // Panasonic V-Log (VARICAM)
+ PL_COLOR_TRC_S_LOG1, // Sony S-Log1
+ PL_COLOR_TRC_S_LOG2, // Sony S-Log2
+ PL_COLOR_TRC_COUNT
+};
+
+// Returns the nominal peak of a given transfer function, relative to the
+// reference white. This refers to the highest encodable signal level.
+// Always equal to 1.0 for SDR curves.
+//
+// Note: For HLG in particular, which is scene-referred, this returns the
+// highest nominal peak in scene-referred space (3.77), which may be different
+// from the actual peak in display space after application of the HLG OOTF.
+PL_API float pl_color_transfer_nominal_peak(enum pl_color_transfer trc);
+
+static inline bool pl_color_transfer_is_hdr(enum pl_color_transfer trc)
+{
+ return pl_color_transfer_nominal_peak(trc) > 1.0;
+}
+
+// This defines the display-space standard reference white level (in cd/m^2)
+// that is assumed for SDR content, for use when mapping between HDR and SDR in
+// display space. See ITU-R Report BT.2408 for more information.
+#define PL_COLOR_SDR_WHITE 203.0f
+
+// This defines the assumed contrast level of an unknown SDR display. This
+// will be used to determine the black point in the absence of any tagged
+// minimum luminance, relative to the tagged maximum luminance (or
+// PL_COLOR_SDR_WHITE in the absence of all tagging)
+#define PL_COLOR_SDR_CONTRAST 1000.0f
+
+// This defines the default black point assumed for "infinite contrast" HDR
+// displays. This is not exactly 0.0 because a value of 0.0 is interpreted
+// as "unknown / missing metadata" inside struct pl_hdr_metadata, and also
+// to avoid numerical issues in a variety of tone mapping functions.
+// Essentially, a black level below this number is functionally meaningless
+// inside libplacebo, and will be clamped to this value regardless.
+//
+// The value used here (1e-6) is about one 13-bit PQ step above absolute zero,
+// which is a small fraction of the human JND at this brightness level, and also
+// about 3 bits above the floating point machine epsilon.
+#define PL_COLOR_HDR_BLACK 1e-6f
+
+// This defines the assumed peak brightness of a HLG display with no HDR10
+// metadata. This is set to the brightness of a "nominal" HLG reference display.
+#define PL_COLOR_HLG_PEAK 1000.0f
+
+// Represents a single CIE xy coordinate (e.g. CIE Yxy with Y = 1.0)
+struct pl_cie_xy {
+ float x, y;
+};
+
+// Creates a pl_cie_xyz from raw XYZ values
+static inline struct pl_cie_xy pl_cie_from_XYZ(float X, float Y, float Z)
+{
+ float k = 1.0f / (X + Y + Z);
+ struct pl_cie_xy xy = { k * X, k * Y };
+ return xy;
+}
+
+// Recovers (X / Y) from a CIE xy value.
+static inline float pl_cie_X(struct pl_cie_xy xy)
+{
+ return xy.x / xy.y;
+}
+
+// Recovers (Z / Y) from a CIE xy value.
+static inline float pl_cie_Z(struct pl_cie_xy xy)
+{
+ return (1 - xy.x - xy.y) / xy.y;
+}
+
+static inline bool pl_cie_xy_equal(const struct pl_cie_xy *a,
+ const struct pl_cie_xy *b)
+{
+ return a->x == b->x && a->y == b->y;
+}
+
+// Computes the CIE xy chromaticity coordinates of a CIE D-series illuminant
+// with the given correlated color temperature.
+//
+// `temperature` must be between 2500 K and 25000 K, inclusive.
+PL_API struct pl_cie_xy pl_white_from_temp(float temperature);
+
+// Represents the raw physical primaries corresponding to a color space.
+struct pl_raw_primaries {
+ struct pl_cie_xy red, green, blue, white;
+};
+
+// Returns whether two raw primaries are exactly identical.
+PL_API bool pl_raw_primaries_equal(const struct pl_raw_primaries *a,
+ const struct pl_raw_primaries *b);
+
+// Returns whether two raw primaries are approximately equal
+PL_API bool pl_raw_primaries_similar(const struct pl_raw_primaries *a,
+ const struct pl_raw_primaries *b);
+
+// Replaces unknown values in the first struct by those of the second struct.
+PL_API void pl_raw_primaries_merge(struct pl_raw_primaries *orig,
+ const struct pl_raw_primaries *update);
+
+// Returns the raw primaries for a given color space.
+PL_API const struct pl_raw_primaries *pl_raw_primaries_get(enum pl_color_primaries prim);
+
+enum pl_hdr_scaling {
+ PL_HDR_NORM = 0, // 0.0 is absolute black, 1.0 is PL_COLOR_SDR_WHITE
+ PL_HDR_SQRT, // sqrt() of PL_HDR_NORM values
+ PL_HDR_NITS, // absolute brightness in raw cd/m²
+ PL_HDR_PQ, // absolute brightness in PQ (0.0 to 1.0)
+ PL_HDR_SCALING_COUNT,
+};
+
+// Generic helper for performing HDR scale conversions.
+PL_API float pl_hdr_rescale(enum pl_hdr_scaling from, enum pl_hdr_scaling to, float x);
+
+enum pl_hdr_metadata_type {
+ PL_HDR_METADATA_ANY = 0,
+ PL_HDR_METADATA_NONE,
+ PL_HDR_METADATA_HDR10, // HDR10 static mastering display metadata
+ PL_HDR_METADATA_HDR10PLUS, // HDR10+ dynamic metadata
+ PL_HDR_METADATA_CIE_Y, // CIE Y derived dynamic luminance metadata
+ PL_HDR_METADATA_TYPE_COUNT,
+};
+
+// Bezier curve for HDR metadata
+struct pl_hdr_bezier {
+ float target_luma; // target luminance (cd/m²) for this OOTF
+ float knee_x, knee_y; // cross-over knee point (0-1)
+ float anchors[15]; // intermediate bezier curve control points (0-1)
+ uint8_t num_anchors;
+};
+
+// Represents raw HDR metadata as defined by SMPTE 2086 / CTA 861.3, which is
+// often attached to HDR sources and can be forwarded to HDR-capable displays,
+// or used to guide the libplacebo built-in tone mapping. Values left as 0
+// are treated as unknown by libplacebo.
+//
+// Note: This means that a value of `min_luma == 0.0` gets treated as "minimum
+// luminance not known", which in practice may end up inferring a default
+// contrast of 1000:1 for SDR transfer functions. To avoid this, the user should
+// set these fields to a low positive value, e.g. PL_COLOR_HDR_BLACK, to signal
+// a "zero" black point (i.e. infinite contrast display).
+struct pl_hdr_metadata {
+ // --- PL_HDR_METADATA_HDR10
+ // Mastering display metadata.
+ struct pl_raw_primaries prim; // mastering display primaries
+ float min_luma, max_luma; // min/max luminance (in cd/m²)
+
+ // Content light level. (Note: this is ignored by libplacebo itself)
+ float max_cll; // max content light level (in cd/m²)
+ float max_fall; // max frame average light level (in cd/m²)
+
+ // --- PL_HDR_METADATA_HDR10PLUS
+ float scene_max[3]; // maxSCL in cd/m² per component (RGB)
+ float scene_avg; // average of maxRGB in cd/m²
+ struct pl_hdr_bezier ootf; // reference OOTF (optional)
+
+ // --- PL_HDR_METADATA_CIE_Y
+ float max_pq_y; // maximum PQ luminance (in PQ, 0-1)
+ float avg_pq_y; // averaged PQ luminance (in PQ, 0-1)
+};
+
+PL_API extern const struct pl_hdr_metadata pl_hdr_metadata_empty; // equal to {0}
+PL_API extern const struct pl_hdr_metadata pl_hdr_metadata_hdr10; // generic HDR10 display
+
+// Returns whether two sets of HDR metadata are exactly identical.
+PL_API bool pl_hdr_metadata_equal(const struct pl_hdr_metadata *a,
+ const struct pl_hdr_metadata *b);
+
+// Replaces unknown values in the first struct by those of the second struct.
+PL_API void pl_hdr_metadata_merge(struct pl_hdr_metadata *orig,
+ const struct pl_hdr_metadata *update);
+
+// Returns `true` if `data` contains a complete set of a given metadata type.
+// Note: for PL_HDR_METADATA_HDR10, only `min_luma` and `max_luma` are
+// considered - CLL/FALL and primaries are irrelevant for HDR tone-mapping.
+PL_API bool pl_hdr_metadata_contains(const struct pl_hdr_metadata *data,
+ enum pl_hdr_metadata_type type);
+
+// Rendering intent for colorspace transformations. These constants match the
+// ICC specification (Table 23)
+enum pl_rendering_intent {
+ PL_INTENT_AUTO = -1, // not a valid ICC intent, but used to auto-infer
+ PL_INTENT_PERCEPTUAL = 0,
+ PL_INTENT_RELATIVE_COLORIMETRIC = 1,
+ PL_INTENT_SATURATION = 2,
+ PL_INTENT_ABSOLUTE_COLORIMETRIC = 3
+};
+
+// Struct describing a physical color space. This information is needed to
+// turn a normalized RGB triple into its physical meaning, as well as to convert
+// between color spaces.
+struct pl_color_space {
+ enum pl_color_primaries primaries;
+ enum pl_color_transfer transfer;
+
+ // HDR metadata for this color space, if present. (Optional)
+ struct pl_hdr_metadata hdr;
+};
+
+#define pl_color_space(...) (&(struct pl_color_space) { __VA_ARGS__ })
+
+// Returns whether or not a color space is considered as effectively HDR.
+// This is true when the effective signal peak is greater than the SDR
+// reference white (1.0), taking into account `csp->hdr`.
+PL_API bool pl_color_space_is_hdr(const struct pl_color_space *csp);
+
+// Returns whether or not a color space is "black scaled", in which case 0.0 is
+// the true black point. This is true for SDR signals other than BT.1886, as
+// well as for HLG.
+PL_API bool pl_color_space_is_black_scaled(const struct pl_color_space *csp);
+
+struct pl_nominal_luma_params {
+ // The color space to infer luminance from
+ const struct pl_color_space *color;
+
+ // Which type of metadata to draw values from
+ enum pl_hdr_metadata_type metadata;
+
+ // This field controls the scaling of `out_*`
+ enum pl_hdr_scaling scaling;
+
+ // Fields to write the detected nominal luminance to. (Optional)
+ //
+ // For SDR displays, this will default to a contrast level of 1000:1 unless
+ // indicated otherwise in the `min/max_luma` static HDR10 metadata fields.
+ float *out_min;
+ float *out_max;
+
+ // Field to write the detected average luminance to, or 0.0 in the absence
+ // of dynamic metadata. (Optional)
+ float *out_avg;
+};
+
+#define pl_nominal_luma_params(...) \
+ (&(struct pl_nominal_luma_params) { __VA_ARGS__ })
+
+// Returns the effective luminance described by a pl_color_space.
+PL_API void pl_color_space_nominal_luma_ex(const struct pl_nominal_luma_params *params);
+
+// Backwards compatibility wrapper for `pl_color_space_nominal_luma_ex`
+PL_DEPRECATED PL_API void pl_color_space_nominal_luma(const struct pl_color_space *csp,
+ float *out_min, float *out_max);
+
+// Replaces unknown values in the first struct by those of the second struct.
+PL_API void pl_color_space_merge(struct pl_color_space *orig,
+ const struct pl_color_space *update);
+
+// Returns whether two colorspaces are exactly identical.
+PL_API bool pl_color_space_equal(const struct pl_color_space *c1,
+ const struct pl_color_space *c2);
+
+// Go through a color-space and explicitly default all unknown fields to
+// reasonable values. After this function is called, none of the values will be
+// PL_COLOR_*_UNKNOWN or 0.0, except for the dynamic HDR metadata fields.
+PL_API void pl_color_space_infer(struct pl_color_space *space);
+
+// Like `pl_color_space_infer`, but takes default values from the reference
+// color space (excluding certain special cases like HDR or wide gamut).
+PL_API void pl_color_space_infer_ref(struct pl_color_space *space,
+ const struct pl_color_space *ref);
+
+// Infer both the source and destination gamut simultaneously, and also adjust
+// values for optimal display. This is mostly the same as
+// `pl_color_space_infer(src)` followed by `pl_color_space_infer_ref`, but also
+// takes into account the SDR contrast levels and PQ black points. This is
+// basically the logic used by `pl_shader_color_map` and `pl_renderer` to
+// decide the output color space in a conservative way and compute the final
+// end-to-end color transformation that needs to be done.
+PL_API void pl_color_space_infer_map(struct pl_color_space *src,
+ struct pl_color_space *dst);
+
+// Some common color spaces. Note: These don't necessarily have all fields
+// filled, in particular `hdr` is left unset.
+PL_API extern const struct pl_color_space pl_color_space_unknown;
+PL_API extern const struct pl_color_space pl_color_space_srgb;
+PL_API extern const struct pl_color_space pl_color_space_bt709;
+PL_API extern const struct pl_color_space pl_color_space_hdr10;
+PL_API extern const struct pl_color_space pl_color_space_bt2020_hlg;
+PL_API extern const struct pl_color_space pl_color_space_monitor; // typical display
+
+// This represents metadata about extra operations to perform during colorspace
+// conversion, which correspond to artistic adjustments of the color.
+struct pl_color_adjustment {
+ // Brightness boost. 0.0 = neutral, 1.0 = solid white, -1.0 = solid black
+ float brightness;
+ // Contrast boost. 1.0 = neutral, 0.0 = solid black
+ float contrast;
+ // Saturation gain. 1.0 = neutral, 0.0 = grayscale
+ float saturation;
+ // Hue shift, corresponding to a rotation around the [U, V] subvector, in
+ // radians. 0.0 = neutral
+ float hue;
+ // Gamma adjustment. 1.0 = neutral, 0.0 = solid black
+ float gamma;
+ // Color temperature shift. 0.0 = 6500 K, -1.0 = 3000 K, 1.0 = 10000 K
+ float temperature;
+};
+
+#define PL_COLOR_ADJUSTMENT_NEUTRAL \
+ .contrast = 1.0, \
+ .saturation = 1.0, \
+ .gamma = 1.0,
+
+#define pl_color_adjustment(...) (&(struct pl_color_adjustment) { PL_COLOR_ADJUSTMENT_NEUTRAL __VA_ARGS__ })
+PL_API extern const struct pl_color_adjustment pl_color_adjustment_neutral;
+
+// Represents the chroma placement with respect to the luma samples. This is
+// only relevant for YCbCr-like colorspaces with chroma subsampling.
+enum pl_chroma_location {
+ PL_CHROMA_UNKNOWN = 0,
+ PL_CHROMA_LEFT, // MPEG2/4, H.264
+ PL_CHROMA_CENTER, // MPEG1, JPEG
+ PL_CHROMA_TOP_LEFT,
+ PL_CHROMA_TOP_CENTER,
+ PL_CHROMA_BOTTOM_LEFT,
+ PL_CHROMA_BOTTOM_CENTER,
+ PL_CHROMA_COUNT,
+};
+
+// Fills *x and *y with the offset in luma pixels corresponding to a given
+// chroma location.
+//
+// Note: PL_CHROMA_UNKNOWN defaults to PL_CHROMA_LEFT
+PL_API void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y);
+
+// Returns an RGB->XYZ conversion matrix for a given set of primaries.
+// Multiplying this into the RGB color transforms it to CIE XYZ, centered
+// around the color space's white point.
+PL_API pl_matrix3x3 pl_get_rgb2xyz_matrix(const struct pl_raw_primaries *prim);
+
+// Similar to pl_get_rgb2xyz_matrix, but gives the inverse transformation.
+PL_API pl_matrix3x3 pl_get_xyz2rgb_matrix(const struct pl_raw_primaries *prim);
+
+// Returns a primary adaptation matrix, which converts from one set of
+// primaries to another. This is an RGB->RGB transformation. For rendering
+// intents other than PL_INTENT_ABSOLUTE_COLORIMETRIC, the white point is
+// adapted using the Bradford matrix.
+PL_API pl_matrix3x3 pl_get_color_mapping_matrix(const struct pl_raw_primaries *src,
+ const struct pl_raw_primaries *dst,
+ enum pl_rendering_intent intent);
+
+// Return a chromatic adaptation matrix, which converts from one white point to
+// another, using the Bradford matrix. This is an RGB->RGB transformation.
+PL_API pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst);
+
+// Returns true if 'b' is entirely contained in 'a'. Useful for figuring out if
+// colorimetric clipping will occur or not.
+PL_API bool pl_primaries_superset(const struct pl_raw_primaries *a,
+ const struct pl_raw_primaries *b);
+
+// Returns true if `prim` forms a nominally valid set of primaries. This does
+// not check whether or not these primaries are actually physically realisable,
+// merely that they satisfy the requirements for colorspace math (to avoid NaN).
+PL_API bool pl_primaries_valid(const struct pl_raw_primaries *prim);
+
+// Returns true if two primaries are 'compatible', which is the case if
+// they preserve the relationship between primaries (red=red, green=green,
+// blue=blue). In other words, this is false for synthetic primaries that have
+// channels misordered from the convention (e.g. for some test ICC profiles).
+PL_API bool pl_primaries_compatible(const struct pl_raw_primaries *a,
+ const struct pl_raw_primaries *b);
+
+// Clip points in the first gamut (src) to be fully contained inside the second
+// gamut (dst). Only works on compatible primaries (pl_primaries_compatible).
+PL_API struct pl_raw_primaries
+pl_primaries_clip(const struct pl_raw_primaries *src,
+ const struct pl_raw_primaries *dst);
+
+// Primary-dependent RGB->LMS matrix for the IPTPQc4 color system. This is
+// derived from the HPE XYZ->LMS matrix with 4% crosstalk added.
+PL_API pl_matrix3x3 pl_ipt_rgb2lms(const struct pl_raw_primaries *prim);
+PL_API pl_matrix3x3 pl_ipt_lms2rgb(const struct pl_raw_primaries *prim);
+
+// Primary-independent L'M'S' -> IPT matrix for the IPTPQc4 color system, and
+// its inverse. This is identical to the Ebner & Fairchild (1998) IPT matrix.
+PL_API extern const pl_matrix3x3 pl_ipt_lms2ipt;
+PL_API extern const pl_matrix3x3 pl_ipt_ipt2lms;
+
+// Cone types involved in human vision
+enum pl_cone {
+ PL_CONE_L = 1 << 0,
+ PL_CONE_M = 1 << 1,
+ PL_CONE_S = 1 << 2,
+
+ // Convenience aliases
+ PL_CONE_NONE = 0,
+ PL_CONE_LM = PL_CONE_L | PL_CONE_M,
+ PL_CONE_MS = PL_CONE_M | PL_CONE_S,
+ PL_CONE_LS = PL_CONE_L | PL_CONE_S,
+ PL_CONE_LMS = PL_CONE_L | PL_CONE_M | PL_CONE_S,
+};
+
+// Structure describing parameters for simulating color blindness
+struct pl_cone_params {
+ enum pl_cone cones; // Which cones are *affected* by the vision model
+ float strength; // Coefficient for how strong the defect is
+ // (1.0 = Unaffected, 0.0 = Full blindness)
+};
+
+#define pl_cone_params(...) (&(struct pl_cone_params) { __VA_ARGS__ })
+
+// Built-in color blindness models
+PL_API extern const struct pl_cone_params pl_vision_normal; // No distortion (92%)
+PL_API extern const struct pl_cone_params pl_vision_protanomaly; // Red deficiency (0.66%)
+PL_API extern const struct pl_cone_params pl_vision_protanopia; // Red absence (0.59%)
+PL_API extern const struct pl_cone_params pl_vision_deuteranomaly; // Green deficiency (2.7%)
+PL_API extern const struct pl_cone_params pl_vision_deuteranopia; // Green absence (0.56%)
+PL_API extern const struct pl_cone_params pl_vision_tritanomaly; // Blue deficiency (0.01%)
+PL_API extern const struct pl_cone_params pl_vision_tritanopia; // Blue absence (0.016%)
+PL_API extern const struct pl_cone_params pl_vision_monochromacy; // Blue cones only (<0.001%)
+PL_API extern const struct pl_cone_params pl_vision_achromatopsia; // Rods only (<0.0001%)
+
+// Returns a cone adaptation matrix. Applying this to an RGB color in the given
+// color space will apply the given cone adaptation coefficients for simulating
+// a type of color blindness.
+//
+// For the color blindness models which don't entail complete loss of a cone,
+// you can partially counteract the effect by using a similar model with the
+// `strength` set to its inverse. For example, to partially counteract
+// deuteranomaly, you could generate a cone matrix for PL_CONE_M with the
+// strength 2.0 (or some other number above 1.0).
+PL_API pl_matrix3x3 pl_get_cone_matrix(const struct pl_cone_params *params,
+ const struct pl_raw_primaries *prim);
+
+// Returns a color decoding matrix for a given combination of source color
+// representation and adjustment parameters. This mutates `repr` to reflect the
+// change. If `params` is NULL, it defaults to &pl_color_adjustment_neutral.
+//
+// This function always performs a conversion to RGB. To convert to other
+// colorspaces (e.g. between YUV systems), obtain a second YUV->RGB matrix
+// and invert it using `pl_transform3x3_invert`.
+//
+// Note: For BT.2020 constant-luminance, this outputs chroma information in the
+// range [-0.5, 0.5]. Since the CL system conversion is non-linear, further
+// processing must be done by the caller. The channel order is CrYCb.
+//
+// Note: For BT.2100 ICtCp, this outputs in the color space L'M'S'. Further
+// non-linear processing must be done by the caller.
+//
+// Note: XYZ system is expected to be in DCDM X'Y'Z' encoding (ST 428-1), in
+// practice this means normalizing by (48.0 / 52.37) factor and applying 2.6 gamma
+PL_API pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
+ const struct pl_color_adjustment *params);
+
+// Common struct to describe an ICC profile
+struct pl_icc_profile {
+ // Points to the in-memory representation of the ICC profile. This is
+ // allowed to be NULL, in which case the `pl_icc_profile` represents "no
+ // profile”.
+ const void *data;
+ size_t len;
+
+ // If a profile is set, this signature must uniquely identify it (including
+ // across restarts, for caching), ideally using a checksum of the profile
+ // contents. The user is free to choose the method of determining this
+ // signature, but note the existence of the
+ // `pl_icc_profile_compute_signature` helper.
+ uint64_t signature;
+};
+
+#define pl_icc_profile(...) &(struct pl_icc_profile) { __VA_ARGS__ }
+
+// This doesn't do a comparison of the actual contents, only of the signature.
+PL_API bool pl_icc_profile_equal(const struct pl_icc_profile *p1,
+ const struct pl_icc_profile *p2);
+
+// Sets `signature` to a hash of `profile->data`, if non-NULL. Provided as a
+// convenience function for the sake of users ingesting arbitrary ICC profiles
+// from sources where they can't reliably detect profile changes.
+//
+// Note: This is based on a very fast hash, and will compute a signature for
+// even large (10 MB) ICC profiles in, typically, a fraction of a millisecond.
+PL_API void pl_icc_profile_compute_signature(struct pl_icc_profile *profile);
+
+PL_API_END
+
+#endif // LIBPLACEBO_COLORSPACE_H_
diff --git a/src/include/libplacebo/common.h b/src/include/libplacebo/common.h
new file mode 100644
index 0000000..806730c
--- /dev/null
+++ b/src/include/libplacebo/common.h
@@ -0,0 +1,244 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_COMMON_H_
+#define LIBPLACEBO_COMMON_H_
+
+#include <stdbool.h>
+
+#include <libplacebo/config.h>
+
+PL_API_BEGIN
+
+// Some common utility types. These are overloaded to support 2D, 3D and
+// integer/float variants.
+typedef struct pl_rect2d {
+ int x0, y0;
+ int x1, y1;
+} pl_rect2d;
+
+typedef struct pl_rect3d {
+ int x0, y0, z0;
+ int x1, y1, z1;
+} pl_rect3d;
+
+typedef struct pl_rect2df {
+ float x0, y0;
+ float x1, y1;
+} pl_rect2df;
+
+typedef struct pl_rect3df {
+ float x0, y0, z0;
+ float x1, y1, z1;
+} pl_rect3df;
+
+// These macros will work for any of the above pl_rect variants (with enough
+// dimensions). Careful: double-evaluation hazard
+#define pl_rect_w(r) ((r).x1 - (r).x0)
+#define pl_rect_h(r) ((r).y1 - (r).y0)
+#define pl_rect_d(r) ((r).z1 - (r).z0)
+
+#define pl_rect2d_eq(a, b) \
+ ((a).x0 == (b).x0 && (a).x1 == (b).x1 && \
+ (a).y0 == (b).y0 && (a).y1 == (b).y1)
+
+#define pl_rect3d_eq(a, b) \
+ ((a).x0 == (b).x0 && (a).x1 == (b).x1 && \
+ (a).y0 == (b).y0 && (a).y1 == (b).y1 && \
+ (a).z0 == (b).z0 && (a).z1 == (b).z1)
+
+// "Normalize" a rectangle: This ensures d1 >= d0 for all dimensions.
+PL_API void pl_rect2d_normalize(pl_rect2d *rc);
+PL_API void pl_rect3d_normalize(pl_rect3d *rc);
+
+PL_API void pl_rect2df_normalize(pl_rect2df *rc);
+PL_API void pl_rect3df_normalize(pl_rect3df *rc);
+
+// Return the rounded form of a rect.
+PL_API pl_rect2d pl_rect2df_round(const pl_rect2df *rc);
+PL_API pl_rect3d pl_rect3df_round(const pl_rect3df *rc);
+
+// Represents a row-major matrix, i.e. the following matrix
+// [ a11 a12 a13 ]
+// [ a21 a22 a23 ]
+// [ a31 a32 a33 ]
+// is represented in C like this:
+// { { a11, a12, a13 },
+// { a21, a22, a23 },
+// { a31, a32, a33 } };
+typedef struct pl_matrix3x3 {
+ float m[3][3];
+} pl_matrix3x3;
+
+PL_API extern const pl_matrix3x3 pl_matrix3x3_identity;
+
+// Applies a matrix to a float vector in-place.
+PL_API void pl_matrix3x3_apply(const pl_matrix3x3 *mat, float vec[3]);
+
+// Applies a matrix to a pl_rect3df
+PL_API void pl_matrix3x3_apply_rc(const pl_matrix3x3 *mat, pl_rect3df *rc);
+
+// Scales a color matrix by a linear factor.
+PL_API void pl_matrix3x3_scale(pl_matrix3x3 *mat, float scale);
+
+// Inverts a matrix. Only use where precision is not that important.
+PL_API void pl_matrix3x3_invert(pl_matrix3x3 *mat);
+
+// Composes/multiplies two matrices. Multiples B into A, i.e.
+// A := A * B
+PL_API void pl_matrix3x3_mul(pl_matrix3x3 *a, const pl_matrix3x3 *b);
+
+// Flipped version of `pl_matrix3x3_mul`.
+// B := A * B
+PL_API void pl_matrix3x3_rmul(const pl_matrix3x3 *a, pl_matrix3x3 *b);
+
+// Represents an affine transformation, which is basically a 3x3 matrix
+// together with a column vector to add onto the output.
+typedef struct pl_transform3x3 {
+ pl_matrix3x3 mat;
+ float c[3];
+} pl_transform3x3;
+
+PL_API extern const pl_transform3x3 pl_transform3x3_identity;
+
+// Applies a transform to a float vector in-place.
+PL_API void pl_transform3x3_apply(const pl_transform3x3 *t, float vec[3]);
+
+// Applies a transform to a pl_rect3df
+PL_API void pl_transform3x3_apply_rc(const pl_transform3x3 *t, pl_rect3df *rc);
+
+// Scales the output of a transform by a linear factor. Since an affine
+// transformation is non-linear, this does not commute. If you want to scale
+// the *input* of a transform, use pl_matrix3x3_scale on `t.mat`.
+PL_API void pl_transform3x3_scale(pl_transform3x3 *t, float scale);
+
+// Inverts a transform. Only use where precision is not that important.
+PL_API void pl_transform3x3_invert(pl_transform3x3 *t);
+
+// 2D analog of the above structs. Since these are featured less prominently,
+// we omit some of the other helper functions.
+typedef struct pl_matrix2x2 {
+ float m[2][2];
+} pl_matrix2x2;
+
+PL_API extern const pl_matrix2x2 pl_matrix2x2_identity;
+PL_API pl_matrix2x2 pl_matrix2x2_rotation(float angle);
+
+PL_API void pl_matrix2x2_apply(const pl_matrix2x2 *mat, float vec[2]);
+PL_API void pl_matrix2x2_apply_rc(const pl_matrix2x2 *mat, pl_rect2df *rc);
+
+PL_API void pl_matrix2x2_mul(pl_matrix2x2 *a, const pl_matrix2x2 *b);
+PL_API void pl_matrix2x2_rmul(const pl_matrix2x2 *a, pl_matrix2x2 *b);
+
+PL_API void pl_matrix2x2_scale(pl_matrix2x2 *mat, float scale);
+PL_API void pl_matrix2x2_invert(pl_matrix2x2 *mat);
+
+typedef struct pl_transform2x2 {
+ pl_matrix2x2 mat;
+ float c[2];
+} pl_transform2x2;
+
+PL_API extern const pl_transform2x2 pl_transform2x2_identity;
+
+PL_API void pl_transform2x2_apply(const pl_transform2x2 *t, float vec[2]);
+PL_API void pl_transform2x2_apply_rc(const pl_transform2x2 *t, pl_rect2df *rc);
+
+PL_API void pl_transform2x2_mul(pl_transform2x2 *a, const pl_transform2x2 *b);
+PL_API void pl_transform2x2_rmul(const pl_transform2x2 *a, pl_transform2x2 *b);
+
+PL_API void pl_transform2x2_scale(pl_transform2x2 *t, float scale);
+PL_API void pl_transform2x2_invert(pl_transform2x2 *t);
+
+// Compute new bounding box of a transformation (as applied to a given rect).
+PL_API pl_rect2df pl_transform2x2_bounds(const pl_transform2x2 *t,
+ const pl_rect2df *rc);
+
+// Helper functions for dealing with aspect ratios and stretched/scaled rects.
+
+// Return the (absolute) aspect ratio (width/height) of a given pl_rect2df.
+// This will always be a positive number, even if `rc` is flipped.
+PL_API float pl_rect2df_aspect(const pl_rect2df *rc);
+
+// Set the aspect of a `rc` to a given aspect ratio with an extra 'panscan'
+// factor choosing the balance between shrinking and growing the `rc` to meet
+// this aspect ratio.
+//
+// Notes:
+// - If `panscan` is 0.0, this function will only ever shrink the `rc`.
+// - If `panscan` is 1.0, this function will only ever grow the `rc`.
+// - If `panscan` is 0.5, this function is area-preserving.
+PL_API void pl_rect2df_aspect_set(pl_rect2df *rc, float aspect, float panscan);
+
+// Set one rect's aspect to that of another
+#define pl_rect2df_aspect_copy(rc, src, panscan) \
+ pl_rect2df_aspect_set((rc), pl_rect2df_aspect(src), (panscan))
+
+// 'Fit' one rect inside another. `rc` will be set to the same size and aspect
+// ratio as `src`, but with the size limited to fit inside the original `rc`.
+// Like `pl_rect2df_aspect_set`, `panscan` controls the pan&scan factor.
+PL_API void pl_rect2df_aspect_fit(pl_rect2df *rc, const pl_rect2df *src, float panscan);
+
+// Scale rect in each direction while keeping it centered.
+PL_API void pl_rect2df_stretch(pl_rect2df *rc, float stretch_x, float stretch_y);
+
+// Offset rect by an arbitrary offset factor. If the corresponding dimension
+// of a rect is flipped, so too is the applied offset.
+PL_API void pl_rect2df_offset(pl_rect2df *rc, float offset_x, float offset_y);
+
+// Scale a rect uniformly in both dimensions.
+#define pl_rect2df_zoom(rc, zoom) pl_rect2df_stretch((rc), (zoom), (zoom))
+
+// Rotation in degrees clockwise
+typedef int pl_rotation;
+enum {
+ PL_ROTATION_0 = 0,
+ PL_ROTATION_90 = 1,
+ PL_ROTATION_180 = 2,
+ PL_ROTATION_270 = 3,
+ PL_ROTATION_360 = 4, // equivalent to PL_ROTATION_0
+
+ // Note: Values outside the range [0,4) are legal, including negatives.
+};
+
+// Constrains to the interval [PL_ROTATION_0, PL_ROTATION_360).
+static inline pl_rotation pl_rotation_normalize(pl_rotation rot)
+{
+ return (rot % PL_ROTATION_360 + PL_ROTATION_360) % PL_ROTATION_360;
+}
+
+// Rotates the coordinate system of a `pl_rect2d(f)` in a certain direction.
+// For example, calling this with PL_ROTATION_90 will correspond to rotating
+// the coordinate system 90° to the right (so the x axis becomes the y axis).
+//
+// The resulting rect is re-normalized in the same coordinate system.
+PL_API void pl_rect2df_rotate(pl_rect2df *rc, pl_rotation rot);
+
+// Returns the aspect ratio in a rotated frame of reference.
+static inline float pl_aspect_rotate(float aspect, pl_rotation rot)
+{
+ return (rot % PL_ROTATION_180) ? 1.0 / aspect : aspect;
+}
+
+#define pl_rect2df_aspect_set_rot(rc, aspect, rot, panscan) \
+ pl_rect2df_aspect_set((rc), pl_aspect_rotate((aspect), (rot)), (panscan))
+
+#define pl_rect2df_aspect_copy_rot(rc, src, panscan, rot) \
+ pl_rect2df_aspect_set_rot((rc), pl_rect2df_aspect(src), (rot), (panscan))
+
+PL_API_END
+
+#endif // LIBPLACEBO_COMMON_H_
diff --git a/src/include/libplacebo/config.h.in b/src/include/libplacebo/config.h.in
new file mode 100644
index 0000000..2ed6290
--- /dev/null
+++ b/src/include/libplacebo/config.h.in
@@ -0,0 +1,102 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_CONFIG_H_
+#define LIBPLACEBO_CONFIG_H_
+
+// Increased any time the library changes in a fundamental/major way.
+#define PL_MAJOR_VER @majorver@
+
+// Increased any time the API changes. (Note: Does not reset when PL_MAJOR_VER
+// is increased)
+#define PL_API_VER @apiver@
+
+// Increased any time a fix is made to a given API version.
+#define PL_FIX_VER (pl_fix_ver())
+
+// Friendly name (`git describe`) for the overall version of the library
+#define PL_VERSION (pl_version())
+
+// Feature tests. These aren't described in further detail, but may be useful
+// for programmers wanting to programmatically check for feature support
+// in their compiled libplacebo versions.
+@extra_defs@
+
+// Extra compiler-specific stuff
+#ifndef PL_DEPRECATED
+# if defined(_MSC_VER)
+# define PL_DEPRECATED
+# else
+# define PL_DEPRECATED __attribute__((deprecated))
+# endif
+#endif
+
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+
+#ifndef PL_DEPRECATED_ENUMERATOR
+# if (defined(__GNUC__) && (__GNUC__ >= 6)) || __has_feature(enumerator_attributes)
+# define PL_DEPRECATED_ENUMERATOR PL_DEPRECATED
+# else
+# define PL_DEPRECATED_ENUMERATOR
+# endif
+#endif
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+# ifdef PL_EXPORT
+# define PL_API __declspec(dllexport)
+# else
+# ifndef PL_STATIC
+# define PL_API __declspec(dllimport)
+# else
+# define PL_API
+# endif
+# endif
+#else
+# define PL_API __attribute__ ((visibility ("default")))
+#endif
+
+// C++ compatibility
+#ifdef __cplusplus
+# define PL_API_BEGIN extern "C" {
+# define PL_API_END }
+#else
+# define PL_API_BEGIN
+# define PL_API_END
+#endif
+
+#ifndef __cplusplus
+// Disable this warning because libplacebo's params macros override fields
+# pragma GCC diagnostic ignored "-Woverride-init"
+#endif
+
+// Extra helper macros
+#define PL_TOSTRING_INNER(x) #x
+#define PL_TOSTRING(x) PL_TOSTRING_INNER(x)
+
+// Deprecated macro for back-compatibility
+#define PL_STRUCT(name) struct name##_t
+
+PL_API_BEGIN
+
+PL_API int pl_fix_ver(void);
+PL_API const char *pl_version(void);
+
+PL_API_END
+
+#endif // LIBPLACEBO_CONFIG_H_
diff --git a/src/include/libplacebo/d3d11.h b/src/include/libplacebo/d3d11.h
new file mode 100644
index 0000000..8ecba30
--- /dev/null
+++ b/src/include/libplacebo/d3d11.h
@@ -0,0 +1,248 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_D3D11_H_
+#define LIBPLACEBO_D3D11_H_
+
+#include <windows.h>
+#include <d3d11.h>
+#include <dxgi1_2.h>
+#include <libplacebo/gpu.h>
+#include <libplacebo/swapchain.h>
+
+PL_API_BEGIN
+
+// Structure representing the actual D3D11 device and associated GPU instance
+typedef const struct pl_d3d11_t {
+ pl_gpu gpu;
+
+ // The D3D11 device in use. The user is free to use this for their own
+ // purposes, including taking a reference to the device (with AddRef) and
+ // using it beyond the lifetime of the pl_d3d11 that created it (though if
+ // this is done with debug enabled, it will confuse the leak checker.)
+ ID3D11Device *device;
+
+ // True if the device is using a software (WARP) adapter
+ bool software;
+} *pl_d3d11;
+
+struct pl_d3d11_params {
+ // The Direct3D 11 device to use. Optional, if NULL then libplacebo will
+ // create its own ID3D11Device using the options below. If set, all the
+ // options below will be ignored.
+ ID3D11Device *device;
+
+ // --- Adapter selection options
+
+ // The adapter to use. This overrides adapter_luid.
+ IDXGIAdapter *adapter;
+
+ // The LUID of the adapter to use. If adapter and adapter_luid are unset,
+ // the default adapter will be used instead.
+ LUID adapter_luid;
+
+ // Allow a software (WARP) adapter when selecting the adapter automatically.
+ // Note that sometimes the default adapter will be a software adapter. This
+ // is because, on Windows 8 and up, if there are no hardware adapters,
+ // Windows will pretend the WARP adapter is the default hardware adapter.
+ bool allow_software;
+
+ // Always use a software adapter. This is mainly for testing purposes.
+ bool force_software;
+
+ // --- Device creation options
+
+ // Enable the debug layer (D3D11_CREATE_DEVICE_DEBUG)
+ // Also logs IDXGIInfoQueue messages
+ bool debug;
+
+ // Extra flags to pass to D3D11CreateDevice (D3D11_CREATE_DEVICE_FLAG).
+ // libplacebo should be compatible with any flags passed here.
+ UINT flags;
+
+ // The minimum and maximum allowable feature levels for the created device.
+ // libplacebo will attempt to create a device with the highest feature level
+ // between min_feature_level and max_feature_level (inclusive.) If there are
+ // no supported feature levels in this range, `pl_d3d11_create` will either
+ // return NULL or fall back to the software adapter, depending on whether
+ // `allow_software` is set.
+ //
+ // Normally there is no reason to set `max_feature_level` other than to test
+ // if a program works at lower feature levels.
+ //
+ // Note that D3D_FEATURE_LEVEL_9_3 and below (known as 10level9) are highly
+ // restrictive. These feature levels are supported on a best-effort basis.
+ // They represent very old DirectX 9 compatible PC and laptop hardware
+ // (2001-2007, GeForce FX, 6, 7, ATI R300-R500, GMA 950-X3000) and some
+ // less-old mobile devices (Surface RT, Surface 2.) Basic video rendering
+ // should work, but the full pl_gpu API will not be available and advanced
+ // shaders will probably fail. The hardware is probably too slow for these
+ // anyway.
+ //
+ // Known restrictions of 10level9 devices include:
+ // D3D_FEATURE_LEVEL_9_3 and below:
+ // - `pl_pass_run_params->index_buf` will not work (but `index_data` will)
+ // - Dimensions of 3D textures must be powers of two
+ // - Shaders cannot use gl_FragCoord
+ // - Shaders cannot use texelFetch
+ // D3D_FEATURE_LEVEL_9_2 and below:
+ // - Fragment shaders have no dynamic flow control and very strict limits
+ // on the number of constants, temporary registers and instructions.
+ // Whether a shader meets the requirements will depend on how it's
+ // compiled and optimized, but it's likely that only simple shaders will
+ // work.
+ // D3D_FEATURE_LEVEL_9_1:
+ // - No high-bit-depth formats with PL_FMT_CAP_RENDERABLE or
+ // PL_FMT_CAP_LINEAR
+ //
+ // If these restrictions are undesirable and you don't need to support
+ // ancient hardware, set `min_feature_level` to D3D_FEATURE_LEVEL_10_0.
+ int min_feature_level; // Defaults to D3D_FEATURE_LEVEL_9_1 if unset
+ int max_feature_level; // Defaults to D3D_FEATURE_LEVEL_12_1 if unset
+
+ // Allow up to N in-flight frames. Similar to swapchain_depth for Vulkan and
+ // OpenGL, though with DXGI this is a device-wide setting that affects all
+ // swapchains (except for waitable swapchains.) See the documentation for
+ // `pl_swapchain_latency` for more information.
+ int max_frame_latency;
+};
+
+// Default/recommended parameters. Should generally be safe and efficient.
+#define PL_D3D11_DEFAULTS \
+ .allow_software = true,
+
+#define pl_d3d11_params(...) (&(struct pl_d3d11_params) { PL_D3D11_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_d3d11_params pl_d3d11_default_params;
+
+// Creates a new Direct3D 11 device based on the given parameters, or wraps an
+// existing device, and initializes a new GPU instance. If params is left as
+// NULL, it defaults to &pl_d3d11_default_params. If an existing device is
+// provided in params->device, `pl_d3d11_create` will take a reference to it
+// that will be released in `pl_d3d11_destroy`.
+PL_API pl_d3d11 pl_d3d11_create(pl_log log, const struct pl_d3d11_params *params);
+
+// Release the D3D11 device.
+//
+// Note that all libplacebo objects allocated from this pl_d3d11 object (e.g.
+// via `d3d11->gpu` or using `pl_d3d11_create_swapchain`) *must* be explicitly
+// destroyed by the user before calling this.
+PL_API void pl_d3d11_destroy(pl_d3d11 *d3d11);
+
+// For a `pl_gpu` backed by `pl_d3d11`, this function can be used to retrieve
+// the underlying `pl_d3d11`. Returns NULL for any other type of `gpu`.
+PL_API pl_d3d11 pl_d3d11_get(pl_gpu gpu);
+
+struct pl_d3d11_swapchain_params {
+ // The Direct3D 11 swapchain to wrap. Optional. If NULL, libplacebo will
+ // create its own swapchain using the options below. If set, all the
+ // swapchain creation options will be ignored.
+ //
+ // The provided swapchain must have been created by the same device used
+ // by `gpu` and must not have multisampled backbuffers.
+ IDXGISwapChain *swapchain;
+
+ // --- Swapchain creation options
+
+ // Initial framebuffer width and height. If both width and height are set to
+ // 0 and window is non-NULL, the client area of the window is used instead.
+ // For convenience, if either component would be 0, it is set to 1 instead.
+ // This is because Windows can have 0-sized windows, but not 0-sized
+ // swapchains.
+ int width;
+ int height;
+
+ // The handle of the output window. In Windows 8 and up this is optional
+ // because you can output to a CoreWindow or create a composition swapchain
+ // instead.
+ HWND window;
+
+ // A pointer to the CoreWindow to output to. If both this and `window` are
+ // NULL, CreateSwapChainForComposition will be used to create the swapchain.
+ IUnknown *core_window;
+
+ // If set, libplacebo will create a swapchain that uses the legacy bitblt
+ // presentation model (with the DXGI_SWAP_EFFECT_DISCARD swap effect.) This
+ // tends to give worse performance and frame pacing in windowed mode and it
+ // prevents borderless fullscreen optimizations, but it might be necessary
+ // to work around buggy drivers, especially with DXGI 1.2 in the Platform
+ // Update for Windows 7. When unset, libplacebo will try to use the flip
+ // presentation model and only fall back to bitblt if flip is unavailable.
+ bool blit;
+
+ // additional swapchain flags
+ // No validation on these flags is being performed, and swapchain creation
+ // may fail if an unsupported combination is requested.
+ UINT flags;
+
+ // --- Swapchain usage behavior options
+
+ // Disable using a 10-bit swapchain format for SDR output
+ bool disable_10bit_sdr;
+};
+
+#define pl_d3d11_swapchain_params(...) (&(struct pl_d3d11_swapchain_params) { __VA_ARGS__ })
+
+// Creates a new Direct3D 11 swapchain, or wraps an existing one. If an existing
+// swapchain is provided in params->swapchain, `pl_d3d11_create_swapchain` will
+// take a reference to it that will be released in `pl_swapchain_destroy`.
+PL_API pl_swapchain pl_d3d11_create_swapchain(pl_d3d11 d3d11,
+ const struct pl_d3d11_swapchain_params *params);
+
+// Takes a `pl_swapchain` created by pl_d3d11_create_swapchain and returns a
+// reference to the underlying IDXGISwapChain. This increments the refcount, so
+// call IDXGISwapChain::Release when finished with it.
+PL_API IDXGISwapChain *pl_d3d11_swapchain_unwrap(pl_swapchain sw);
+
+struct pl_d3d11_wrap_params {
+ // The D3D11 texture to wrap, or a texture array containing the texture to
+ // wrap. Must be a ID3D11Texture1D, ID3D11Texture2D or ID3D11Texture3D
+ // created by the same device used by `gpu`, must have D3D11_USAGE_DEFAULT,
+ // and must not be mipmapped or multisampled.
+ ID3D11Resource *tex;
+
+ // If tex is a texture array, this is the array member to use as the pl_tex.
+ int array_slice;
+
+ // If tex is a video resource (eg. DXGI_FORMAT_AYUV, DXGI_FORMAT_NV12,
+ // DXGI_FORMAT_P010, etc.,) it can be wrapped as a pl_tex by specifying the
+ // type and size of the shader view. For planar video formats, the plane
+ // that is wrapped depends on the chosen format.
+ //
+ // If tex is not a video resource, these fields are unnecessary. The correct
+ // format will be determined automatically. If tex is not 2D, these fields
+ // are ignored.
+ //
+ // For a list of supported video formats and their corresponding view
+ // formats and sizes, see:
+ // https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#VideoViews
+ DXGI_FORMAT fmt;
+ int w;
+ int h;
+};
+
+#define pl_d3d11_wrap_params(...) (&(struct pl_d3d11_wrap_params) { __VA_ARGS__ })
+
+// Wraps an external texture into a pl_tex abstraction. `pl_d3d11_wrap` takes a
+// reference to the texture, which is released when `pl_tex_destroy` is called.
+//
+// This function may fail due to incompatible formats, incompatible flags or
+// other reasons, in which case it will return NULL.
+PL_API pl_tex pl_d3d11_wrap(pl_gpu gpu, const struct pl_d3d11_wrap_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_D3D11_H_
diff --git a/src/include/libplacebo/dispatch.h b/src/include/libplacebo/dispatch.h
new file mode 100644
index 0000000..7d43794
--- /dev/null
+++ b/src/include/libplacebo/dispatch.h
@@ -0,0 +1,239 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DISPATCH_H_
+#define LIBPLACEBO_DISPATCH_H_
+
+#include <libplacebo/shaders.h>
+#include <libplacebo/gpu.h>
+
+PL_API_BEGIN
+
+// Thread-safety: Safe
+typedef struct pl_dispatch_t *pl_dispatch;
+
+// Creates a new shader dispatch object. This object provides a translation
+// layer between generated shaders (pl_shader) and the ra context such that it
+// can be used to execute shaders. This dispatch object will also provide
+// shader caching (for efficient re-use).
+PL_API pl_dispatch pl_dispatch_create(pl_log log, pl_gpu gpu);
+PL_API void pl_dispatch_destroy(pl_dispatch *dp);
+
+// Reset/increments the internal counters of the pl_dispatch. This must be
+// called whenever the user is going to begin with a new frame, in order to
+// perform garbage collection and advance the state of the internal PRNG.
+//
+// Note that shaders generated by `pl_dispatch` are therefore entirely
+// deterministic, as long as the sequence of calls (and inputs to the shader)
+// are the same.
+PL_API void pl_dispatch_reset_frame(pl_dispatch dp);
+
+// Returns a blank pl_shader object, suitable for recording rendering commands.
+// For more information, see the header documentation in `shaders/*.h`.
+PL_API pl_shader pl_dispatch_begin(pl_dispatch dp);
+
+// Struct passed to `info_callback`. Only valid until that function returns.
+struct pl_dispatch_info {
+ // Information about the shader for this shader execution, as well as a
+ // 64-bit signature uniquely identifying it.
+ pl_shader_info shader;
+ uint64_t signature;
+
+ // A list of execution times for this pass, in nanoseconds. May be empty.
+ uint64_t samples[256];
+ int num_samples;
+
+ // As a convenience, this contains the last, average and peak of the above
+ // list of samples. If `num_samples` is 0, these values are also 0.
+ uint64_t last;
+ uint64_t peak;
+ uint64_t average;
+};
+
+// Helper function to make a copy of `pl_dispatch_info`, while overriding
+// (and dereferencing) whatever was previously stored there.
+static inline void pl_dispatch_info_move(struct pl_dispatch_info *dst,
+ const struct pl_dispatch_info *src)
+{
+ pl_shader_info_deref(&dst->shader);
+ *dst = *src;
+ dst->shader = pl_shader_info_ref(src->shader);
+}
+
+// Set up a dispatch callback for this `pl_dispatch` object. The given callback
+// will be run for every successfully dispatched shader. Call this again with
+// `cb == NULL` to disable.
+PL_API void pl_dispatch_callback(pl_dispatch dp, void *priv,
+ void (*cb)(void *priv,
+ const struct pl_dispatch_info *));
+
+struct pl_dispatch_params {
+ // The shader to execute. The pl_dispatch will take over ownership
+ // of this shader, and return it back to the internal pool.
+ //
+ // This shader must have a compatible signature, i.e. inputs
+ // `PL_SHADER_SIG_NONE` and outputs `PL_SHADER_SIG_COLOR`.
+ pl_shader *shader;
+
+ // The texture to render to. This must have params compatible with the
+ // shader, i.e. `target->params.renderable` for fragment shaders and
+ // `target->params.storable` for compute shaders.
+ //
+ // Note: Even when not using compute shaders, users are advised to always
+ // set `target->params.storable` if permitted by the `pl_fmt`, since this
+ // allows the use of compute shaders instead of full-screen quads, which is
+ // faster on some platforms.
+ pl_tex target;
+
+ // The target rect to render to. Optional, if left as {0}, then the
+ // entire texture will be rendered to.
+ pl_rect2d rect;
+
+ // If set, enables and controls the blending for this pass. Optional. When
+ // using this with fragment shaders, `target->params.fmt->caps` must
+ // include `PL_FMT_CAP_BLENDABLE`.
+ const struct pl_blend_params *blend_params;
+
+ // If set, records the execution time of this dispatch into the given
+ // timer object. Optional.
+ //
+ // Note: If this is set, `pl_dispatch` cannot internally measure the
+ // execution time of the shader, which means `pl_dispatch_info.samples` may
+ // be empty as a result.
+ pl_timer timer;
+};
+
+#define pl_dispatch_params(...) (&(struct pl_dispatch_params) { __VA_ARGS__ })
+
+// Dispatch a generated shader (via the pl_shader mechanism). Returns whether
+// or not the dispatch was successful.
+PL_API bool pl_dispatch_finish(pl_dispatch dp, const struct pl_dispatch_params *params);
+
+struct pl_dispatch_compute_params {
+ // The shader to execute. This must be a compute shader with the input
+ // set to PL_SHADER_SIG_NONE. The output, if it has any, is ignored.
+ pl_shader *shader;
+
+ // The number of work groups to dispatch in each dimension. If this is left
+ // as [0} and `width/height` are both set, the number of work groups will
+ // be inferred from the shader's `compute_group_sizes`.
+ int dispatch_size[3];
+
+ // If set, simulate vertex attributes (similar to `pl_dispatch_finish`)
+ // according to the given dimensions. The first two components of the
+ // thread's global ID will be interpreted as the X and Y locations.
+ //
+ // Optional, ignored if either component is left as 0.
+ int width, height;
+
+ // If set, records the execution time of this dispatch into the given
+ // timer object. Optional.
+ //
+ // Note: If this is set, `pl_dispatch` cannot internally measure the
+ // execution time of the shader, which means `pl_dispatch_info.samples` may
+ // be empty as a result.
+ pl_timer timer;
+};
+
+#define pl_dispatch_compute_params(...) (&(struct pl_dispatch_compute_params) { __VA_ARGS__ })
+
+// A variant of `pl_dispatch_finish`, this one only dispatches a compute shader
+// while ignoring its output (if it has one). It's only useful for shaders
+// which have otherwise observable side effects (such as updating state
+// objects).
+PL_API bool pl_dispatch_compute(pl_dispatch dp, const struct pl_dispatch_compute_params *params);
+
+enum pl_vertex_coords {
+ PL_COORDS_ABSOLUTE, // Absolute/integer `target` coordinates
+ PL_COORDS_RELATIVE, // Relative `target` coordinates in range [0, 1]
+ PL_COORDS_NORMALIZED, // GL-normalized coordinates in range [-1, 1]
+};
+
+struct pl_dispatch_vertex_params {
+ // The shader to execute. This must be a raster shader with the input set
+ // to `PL_SHADER_SIG_NONE` and the output set to `PL_SHADER_SIG_COLOR`.
+ //
+ // Additionally, the shader must not have any attached vertex attributes.
+ pl_shader *shader;
+
+ // The texture to render to. Requires `target->params.renderable`.
+ pl_tex target;
+
+ // The target rect to clip the rendering to. (Optional)
+ pl_rect2d scissors;
+
+ // If set, enables and controls the blending for this pass. Optional. When
+ // enabled, `target->params.fmt->caps` must include `PL_FMT_CAP_BLENDABLE`.
+ const struct pl_blend_params *blend_params;
+
+ // The description of the vertex format, including offsets.
+ //
+ // Note: `location` is ignored and can safely be left unset.
+ const struct pl_vertex_attrib *vertex_attribs;
+ int num_vertex_attribs;
+ size_t vertex_stride;
+
+ // The index of the vertex position in `vertex_attribs`, as well as the
+ // interpretation of its contents.
+ int vertex_position_idx;
+ enum pl_vertex_coords vertex_coords;
+ bool vertex_flipped; // flip all vertex y coordinates
+
+ // Type and number of vertices to render.
+ enum pl_prim_type vertex_type;
+ int vertex_count;
+
+ // Vertex data. See `pl_pass_run_params.vertex_data`.
+ const void *vertex_data;
+ pl_buf vertex_buf;
+ size_t buf_offset;
+
+ // Index data. See `pl_pass_run_params.index_data`. Optional.
+ const void *index_data;
+ enum pl_index_format index_fmt;
+ pl_buf index_buf;
+ size_t index_offset;
+
+ // If set, records the execution time of this dispatch into the given
+ // timer object. Optional.
+ //
+ // Note: If this is set, `pl_dispatch` cannot internally measure the
+ // execution time of the shader, which means `pl_dispatch_info.samples` may
+ // be empty as a result.
+ pl_timer timer;
+};
+
+#define pl_dispatch_vertex_params(...) (&(struct pl_dispatch_vertex_params) { __VA_ARGS__ })
+
+// Dispatch a generated shader using custom vertices, rather than using a quad
+// generated by the dispatch. This allows the use of e.g. custom fragment
+// shaders for things like rendering custom UI elements, or possibly doing
+// advanced things like sampling from a cube map or spherical video.
+PL_API bool pl_dispatch_vertex(pl_dispatch dp, const struct pl_dispatch_vertex_params *params);
+
+// Cancel an active shader without submitting anything. Useful, for example,
+// if the shader was instead merged into a different shader.
+PL_API void pl_dispatch_abort(pl_dispatch dp, pl_shader *sh);
+
+// Deprecated in favor of `pl_cache_save/pl_cache_load` on the `pl_cache`
+// associated with the `pl_gpu` this dispatch is using.
+PL_DEPRECATED PL_API size_t pl_dispatch_save(pl_dispatch dp, uint8_t *out_cache);
+PL_DEPRECATED PL_API void pl_dispatch_load(pl_dispatch dp, const uint8_t *cache);
+
+PL_API_END
+
+#endif // LIBPLACEBO_DISPATCH_H
diff --git a/src/include/libplacebo/dither.h b/src/include/libplacebo/dither.h
new file mode 100644
index 0000000..84f17c7
--- /dev/null
+++ b/src/include/libplacebo/dither.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DITHER_H_
+#define LIBPLACEBO_DITHER_H_
+
+#include <libplacebo/common.h>
+
+PL_API_BEGIN
+
+// Generates a deterministic NxN bayer (ordered) dither matrix, storing the
+// result in `data`. `size` must be a power of two. The resulting matrix will
+// be roughly uniformly distributed within the range [0,1).
+PL_API void pl_generate_bayer_matrix(float *data, int size);
+
+// Generates a random NxN blue noise texture. storing the result in `data`.
+// `size` must be a positive power of two no larger than 256. The resulting
+// texture will be roughly uniformly distributed within the range [0,1).
+//
+// Note: This function is very, *very* slow for large sizes. Generating a
+// dither matrix with size 256 can take several seconds on a modern processor.
+PL_API void pl_generate_blue_noise(float *data, int size);
+
+// Defines the border of all error diffusion kernels
+#define PL_EDF_MIN_DX (-2)
+#define PL_EDF_MAX_DX (2)
+#define PL_EDF_MAX_DY (2)
+
+struct pl_error_diffusion_kernel {
+ const char *name; // Short and concise identifier
+ const char *description; // Longer / friendly name
+
+ // The minimum value such that a (y, x) -> (y, x + y * shift) mapping will
+ // make all error pushing operations affect next column (and after it)
+ // only.
+ //
+ // Higher shift values are significantly more computationally intensive.
+ int shift;
+
+ // The diffusion factor for (y, x) is pattern[y][x - PL_EDF_MIN_DX] / divisor.
+ int pattern[PL_EDF_MAX_DY + 1][PL_EDF_MAX_DX - PL_EDF_MIN_DX + 1];
+ int divisor;
+};
+
+// Algorithms with shift=1:
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_simple;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_false_fs;
+// Algorithms with shift=2:
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_sierra_lite;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_floyd_steinberg;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_atkinson;
+// Algorithms with shift=3, probably too heavy for low end GPUs:
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_jarvis_judice_ninke;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_stucki;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_burkes;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_sierra2;
+PL_API extern const struct pl_error_diffusion_kernel pl_error_diffusion_sierra3;
+
+// A list of built-in error diffusion kernels, terminated by NULL
+PL_API extern const struct pl_error_diffusion_kernel * const pl_error_diffusion_kernels[];
+PL_API extern const int pl_num_error_diffusion_kernels; // excluding trailing NULL
+
+// Find the error diffusion kernel with the given name, or NULL on failure.
+PL_API const struct pl_error_diffusion_kernel *pl_find_error_diffusion_kernel(const char *name);
+
+PL_API_END
+
+#endif // LIBPLACEBO_DITHER_H_
diff --git a/src/include/libplacebo/dummy.h b/src/include/libplacebo/dummy.h
new file mode 100644
index 0000000..c298438
--- /dev/null
+++ b/src/include/libplacebo/dummy.h
@@ -0,0 +1,131 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DUMMY_H_
+#define LIBPLACEBO_DUMMY_H_
+
+#include <libplacebo/gpu.h>
+
+PL_API_BEGIN
+
+// The functions in this file allow creating and manipulating "dummy" contexts.
+// A dummy context isn't actually mapped by the GPU, all data exists purely on
+// the CPU. It also isn't capable of compiling or executing any shaders, any
+// attempts to do so will simply fail.
+//
+// The main use case for this dummy context is for users who want to generate
+// advanced shaders that depend on specific GLSL features or support for
+// certain types of GPU resources (e.g. LUTs). This dummy context allows such
+// shaders to be generated, with all of the referenced shader objects and
+// textures simply containing their data in a host-accessible way.
+
+struct pl_gpu_dummy_params {
+ // These GPU parameters correspond to their equivalents in `pl_gpu`, and
+ // must obey the same rules as documented there. The values from
+ // `pl_gpu_dummy_default_params` are set to support pretty much everything
+ // and are set for GLSL version 450.
+ //
+ // Individual fields such as `glsl.compute` or `glsl.version` description
+ // can and should be overridden by the user based on their requirements.
+ // Individual limits should ideally be set based on the corresponding
+ // `glGet` queries etc.
+ struct pl_glsl_version glsl;
+ struct pl_gpu_limits limits;
+};
+
+#define PL_GPU_DUMMY_DEFAULTS \
+ .glsl = { \
+ .version = 450, \
+ .gles = false, \
+ .vulkan = false, \
+ .compute = true, \
+ .max_shmem_size = SIZE_MAX, \
+ .max_group_threads = 1024, \
+ .max_group_size = { 1024, 1024, 1024 }, \
+ .subgroup_size = 32, \
+ .min_gather_offset = INT16_MIN, \
+ .max_gather_offset = INT16_MAX, \
+ }, \
+ .limits = { \
+ /* pl_gpu */ \
+ .callbacks = false, \
+ .thread_safe = true, \
+ /* pl_buf */ \
+ .max_buf_size = SIZE_MAX, \
+ .max_ubo_size = SIZE_MAX, \
+ .max_ssbo_size = SIZE_MAX, \
+ .max_vbo_size = SIZE_MAX, \
+ .max_mapped_size = SIZE_MAX, \
+ .max_buffer_texels = UINT64_MAX, \
+ /* pl_tex */ \
+ .max_tex_1d_dim = UINT32_MAX, \
+ .max_tex_2d_dim = UINT32_MAX, \
+ .max_tex_3d_dim = UINT32_MAX, \
+ .buf_transfer = true, \
+ .align_tex_xfer_pitch = 1, \
+ .align_tex_xfer_offset = 1, \
+ /* pl_pass */ \
+ .max_variable_comps = SIZE_MAX, \
+ .max_constants = SIZE_MAX, \
+ .max_pushc_size = SIZE_MAX, \
+ .max_dispatch = { UINT32_MAX, UINT32_MAX, UINT32_MAX }, \
+ .fragment_queues = 0, \
+ .compute_queues = 0, \
+ },
+
+#define pl_gpu_dummy_params(...) (&(struct pl_gpu_dummy_params) { PL_GPU_DUMMY_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_gpu_dummy_params pl_gpu_dummy_default_params;
+
+// Create a dummy GPU context based on the given parameters. This GPU will have
+// a format for each host-representable type (i.e. intN_t, floats and doubles),
+// in the canonical channel order RGBA. These formats will have every possible
+// capability activated, respectively.
+//
+// If `params` is left as NULL, it defaults to `&pl_gpu_dummy_params`.
+PL_API pl_gpu pl_gpu_dummy_create(pl_log log, const struct pl_gpu_dummy_params *params);
+PL_API void pl_gpu_dummy_destroy(pl_gpu *gpu);
+
+// Back-doors into the `pl_tex` and `pl_buf` representations. These allow you
+// to access the raw data backing this object. Textures are always laid out in
+// a tightly packed manner.
+//
+// For "placeholder" dummy textures, this always returns NULL.
+PL_API uint8_t *pl_buf_dummy_data(pl_buf buf);
+PL_API uint8_t *pl_tex_dummy_data(pl_tex tex);
+
+// Skeleton of `pl_tex_params` containing only the fields relevant to
+// `pl_tex_dummy_create`, plus the extra `sampler_type` field.
+struct pl_tex_dummy_params {
+ int w, h, d;
+ pl_fmt format;
+ enum pl_sampler_type sampler_type;
+ void *user_data;
+};
+
+#define pl_tex_dummy_params(...) (&(struct pl_tex_dummy_params) { __VA_ARGS__ })
+
+// Allows creating a "placeholder" dummy texture. This is basically a texture
+// that isn't even backed by anything. All `pl_tex_*` operations (other than
+// `pl_tex_destroy`) performed on it will simply fail.
+//
+// All of the permissions will be set to `false`, except `sampleable`, which is
+// set to `true`. (So you can use it as an input to shader sampling functions)
+PL_API pl_tex pl_tex_dummy_create(pl_gpu gpu, const struct pl_tex_dummy_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_DUMMY_H_
diff --git a/src/include/libplacebo/filters.h b/src/include/libplacebo/filters.h
new file mode 100644
index 0000000..a95649d
--- /dev/null
+++ b/src/include/libplacebo/filters.h
@@ -0,0 +1,415 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_FILTER_KERNELS_H_
+#define LIBPLACEBO_FILTER_KERNELS_H_
+
+#include <stdbool.h>
+#include <libplacebo/log.h>
+
+PL_API_BEGIN
+
+#define PL_FILTER_MAX_PARAMS 2
+
+// Invocation parameters for a given kernel
+struct pl_filter_ctx {
+ float radius;
+ float params[PL_FILTER_MAX_PARAMS];
+};
+
+// Represents a single filter function, i.e. kernel or windowing function.
+struct pl_filter_function {
+ // The cosmetic name associated with this filter function.
+ const char *name;
+
+ // The radius of the filter function. For resizable filters, this gives
+ // the radius needed to represent a single filter lobe (tap).
+ float radius;
+
+ // If true, the filter function is resizable (see pl_filter_config.radius)
+ bool resizable;
+
+ // If true, the filter function is tunable (see pl_filter_config.params)
+ bool tunable[PL_FILTER_MAX_PARAMS];
+
+ // If the relevant parameter is tunable, this contains the default values.
+ float params[PL_FILTER_MAX_PARAMS];
+
+ // The underlying filter function itself: Computes the weight as a function
+ // of the offset. All filter functions must be normalized such that x=0 is
+ // the center point, and in particular weight(0) = 1.0. The functions may
+ // be undefined for values of x outside [0, radius].
+ double (*weight)(const struct pl_filter_ctx *f, double x);
+
+ // If true, this filter represents an opaque placeholder for a more
+ // sophisticated filter function which does not fit into the pl_filter
+ // framework. `weight()` will always return 0.0.
+ bool opaque;
+};
+
+// Deprecated function, merely checks a->weight == b->weight
+PL_DEPRECATED PL_API bool
+pl_filter_function_eq(const struct pl_filter_function *a,
+ const struct pl_filter_function *b);
+
+// Box filter: Entirely 1.0 within the radius, entirely 0.0 outside of it.
+// This is also sometimes called a Dirichlet window
+PL_API extern const struct pl_filter_function pl_filter_function_box;
+
+// Triangle filter: Linear transitions from 1.0 at x=0 to 0.0 at x=radius.
+// This is also sometimes called a Bartlett window.
+PL_API extern const struct pl_filter_function pl_filter_function_triangle;
+
+// Cosine filter: Ordinary cosine function, single lobe.
+PL_API extern const struct pl_filter_function pl_filter_function_cosine;
+
+// Hann function: Cosine filter named after Julius von Hann. Also commonly
+// mislabeled as a "Hanning" function, due to its similarly to the Hamming
+// function.
+PL_API extern const struct pl_filter_function pl_filter_function_hann;
+
+// Hamming function: Cosine filter named after Richard Hamming.
+PL_API extern const struct pl_filter_function pl_filter_function_hamming;
+
+// Welch filter: Polynomial function consisting of a single parabolic section.
+PL_API extern const struct pl_filter_function pl_filter_function_welch;
+
+// Kaiser filter: Approximation of the DPSS window using Bessel functions.
+// Also sometimes called a Kaiser-Bessel window.
+// Parameter [0]: Shape (alpha). Determines the trade-off between the main lobe
+// and the side lobes.
+PL_API extern const struct pl_filter_function pl_filter_function_kaiser;
+
+// Blackman filter: Cosine filter named after Ralph Beebe Blackman.
+// Parameter [0]: Scale (alpha). Influences the shape. The defaults result in
+// zeros at the third and fourth sidelobes.
+PL_API extern const struct pl_filter_function pl_filter_function_blackman;
+
+// Bohman filter: 2nd order Cosine filter.
+PL_API extern const struct pl_filter_function pl_filter_function_bohman;
+
+// Gaussian function: Similar to the Gaussian distribution, this defines a
+// bell curve function.
+// Parameter [0]: Scale (t), increasing makes the result blurrier.
+PL_API extern const struct pl_filter_function pl_filter_function_gaussian;
+
+// Quadratic function: 2nd order approximation of the gaussian function. Also
+// sometimes called a "quadric" window.
+PL_API extern const struct pl_filter_function pl_filter_function_quadratic;
+
+// Sinc function: Widely used for both kernels and windows, sinc(x) = sin(x)/x.
+PL_API extern const struct pl_filter_function pl_filter_function_sinc;
+
+// Jinc function: Similar to sinc, but extended to the 2D domain. Widely
+// used as the kernel of polar (EWA) filters. Also sometimes called a Sombrero
+// function.
+PL_API extern const struct pl_filter_function pl_filter_function_jinc;
+
+// Sphinx function: Similar to sinc and jinx, but extended to the 3D domain.
+// The name is derived from "spherical" sinc. Can be used to filter 3D signals
+// in theory.
+PL_API extern const struct pl_filter_function pl_filter_function_sphinx;
+
+// B/C-tunable Spline function: This is a family of commonly used spline
+// functions with two tunable parameters. Does not need to be windowed.
+// Parameter [0]: "B"
+// Parameter [1]: "C"
+// Some popular variants of this function are:
+// B = 1.0, C = 0.0: "base" Cubic (blurry)
+// B = 0.0, C = 0.0: Hermite filter (blocky)
+// B = 0.0, C = 0.5: Catmull-Rom filter (sharp)
+// B = 1/3, C = 1/3: Mitchell-Netravali filter (soft, doesn't ring)
+// B ≈ 0.37, C ≈ 0.31: Robidoux filter (used by ImageMagick)
+// B ≈ 0.26, C ≈ 0.37: RobidouxSharp filter (sharper variant of Robidoux)
+PL_API extern const struct pl_filter_function pl_filter_function_cubic;
+PL_API extern const struct pl_filter_function pl_filter_function_hermite;
+#define pl_filter_function_bicubic pl_filter_function_cubic
+#define pl_filter_function_bcspline pl_filter_function_cubic
+
+// Cubic splines with 2/3/4 taps. Referred to as "spline16", "spline36", and
+// "spline64" mainly for historical reasons, based on the number of pixels in
+// their window when using them as 2D orthogonal filters. Do not need to be
+// windowed.
+PL_API extern const struct pl_filter_function pl_filter_function_spline16;
+PL_API extern const struct pl_filter_function pl_filter_function_spline36;
+PL_API extern const struct pl_filter_function pl_filter_function_spline64;
+
+// Special filter function for the built-in oversampling algorithm. This is an
+// opaque filter with no meaningful representation. though it has one tunable
+// parameter controlling the threshold at which to switch back to ordinary
+// nearest neighbour sampling. (See `pl_shader_sample_oversample`)
+PL_API extern const struct pl_filter_function pl_filter_function_oversample;
+
+// A list of built-in filter functions, terminated by NULL
+//
+// Note: May contain extra aliases for the above functions.
+PL_API extern const struct pl_filter_function * const pl_filter_functions[];
+PL_API extern const int pl_num_filter_functions; // excluding trailing NULL
+
+// Find the filter function with the given name, or NULL on failure.
+PL_API const struct pl_filter_function *pl_find_filter_function(const char *name);
+
+// Backwards compatibility with the older configuration API. Redundant with
+// `pl_filter_function.name`. May be formally deprecated in the future.
+
+struct pl_filter_function_preset {
+ const char *name;
+ const struct pl_filter_function *function;
+};
+
+// A list of built-in filter function presets, terminated by {0}
+PL_API extern const struct pl_filter_function_preset pl_filter_function_presets[];
+PL_API extern const int pl_num_filter_function_presets; // excluding trailing {0}
+
+// Find the filter function preset with the given name, or NULL on failure.
+PL_API const struct pl_filter_function_preset *pl_find_filter_function_preset(const char *name);
+
+// Different usage domains for a filter
+enum pl_filter_usage {
+ PL_FILTER_UPSCALING = (1 << 0),
+ PL_FILTER_DOWNSCALING = (1 << 1),
+ PL_FILTER_FRAME_MIXING = (1 << 2),
+
+ PL_FILTER_SCALING = PL_FILTER_UPSCALING | PL_FILTER_DOWNSCALING,
+ PL_FILTER_ALL = PL_FILTER_SCALING | PL_FILTER_FRAME_MIXING,
+};
+
+// Represents a tuned combination of filter functions, plus parameters
+struct pl_filter_config {
+ // The cosmetic name associated with this filter config. Optional for
+ // user-provided configs, but always set by built-in configurations.
+ const char *name;
+
+ // Longer / friendly name. Always set for built-in configurations,
+ // except for names which are merely aliases of other filters.
+ const char *description;
+
+ // Allowed and recommended usage domains (respectively)
+ //
+ // When it is desired to maintain a simpler user interface, it may be
+ // recommended to include only scalers whose recommended usage domains
+ // includes the relevant context in which it will be used.
+ enum pl_filter_usage allowed;
+ enum pl_filter_usage recommended;
+
+ // The kernel function and (optionally) windowing function.
+ const struct pl_filter_function *kernel;
+ const struct pl_filter_function *window;
+
+ // The radius. Ignored if !kernel->resizable. Optional, defaults to
+ // kernel->radius if unset.
+ float radius;
+
+ // Parameters for the respective filter function. Ignored if not tunable.
+ float params[PL_FILTER_MAX_PARAMS];
+ float wparams[PL_FILTER_MAX_PARAMS];
+
+ // Represents a clamping coefficient for negative weights. A value of 0.0
+ // (the default) represents no clamping. A value of 1.0 represents full
+ // clamping, i.e. all negative weights will be clamped to 0. Values in
+ // between will be linearly scaled.
+ float clamp;
+
+ // Additional blur coefficient. This effectively stretches the kernel,
+ // without changing the effective radius of the filter radius. Setting this
+ // to a value of 0.0 is equivalent to disabling it. Values significantly
+ // below 1.0 may seriously degrade the visual output, and should be used
+ // with care.
+ float blur;
+
+ // Additional taper coefficient. This essentially flattens the function's
+ // center. The values within [-taper, taper] will return 1.0, with the
+ // actual function being squished into the remainder of [taper, radius].
+ // Defaults to 0.0.
+ float taper;
+
+ // If true, this filter is intended to be used as a polar/2D filter (EWA)
+ // instead of a separable/1D filter. Does not affect the actual sampling,
+ // but provides information about how the results are to be interpreted.
+ bool polar;
+
+ // Antiringing strength. A value of 0.0 disables antiringing, and a value
+ // of 1.0 enables full-strength antiringing. Defaults to 0.0 if
+ // unspecified.
+ //
+ // Note: This is only included in `pl_filter_config` for convenience. Does
+ // not affect the actual filter sampling, but provides information to the
+ // downstream consumer of the `pl_filter`.
+ float antiring;
+};
+
+PL_API bool pl_filter_config_eq(const struct pl_filter_config *a,
+ const struct pl_filter_config *b);
+
+// Samples a given filter configuration at a given x coordinate, while
+// respecting all parameters of the configuration.
+PL_API double pl_filter_sample(const struct pl_filter_config *c, double x);
+
+// A list of built-in filter configurations. Since they are just combinations
+// of the above filter functions, they are not described in much further
+// detail.
+PL_API extern const struct pl_filter_config pl_filter_spline16; // 2 taps
+PL_API extern const struct pl_filter_config pl_filter_spline36; // 3 taps
+PL_API extern const struct pl_filter_config pl_filter_spline64; // 4 taps
+PL_API extern const struct pl_filter_config pl_filter_nearest;
+PL_API extern const struct pl_filter_config pl_filter_box;
+PL_API extern const struct pl_filter_config pl_filter_bilinear;
+PL_API extern const struct pl_filter_config pl_filter_gaussian;
+// Sinc family (all configured to 3 taps):
+PL_API extern const struct pl_filter_config pl_filter_sinc; // unwindowed
+PL_API extern const struct pl_filter_config pl_filter_lanczos; // sinc-sinc
+PL_API extern const struct pl_filter_config pl_filter_ginseng; // sinc-jinc
+PL_API extern const struct pl_filter_config pl_filter_ewa_jinc; // unwindowed
+PL_API extern const struct pl_filter_config pl_filter_ewa_lanczos; // jinc-jinc
+PL_API extern const struct pl_filter_config pl_filter_ewa_lanczossharp;
+PL_API extern const struct pl_filter_config pl_filter_ewa_lanczos4sharpest;
+PL_API extern const struct pl_filter_config pl_filter_ewa_ginseng; // jinc-sinc
+PL_API extern const struct pl_filter_config pl_filter_ewa_hann; // jinc-hann
+// Spline family
+PL_API extern const struct pl_filter_config pl_filter_bicubic;
+PL_API extern const struct pl_filter_config pl_filter_hermite;
+PL_API extern const struct pl_filter_config pl_filter_catmull_rom;
+PL_API extern const struct pl_filter_config pl_filter_mitchell;
+PL_API extern const struct pl_filter_config pl_filter_mitchell_clamp; // clamp = 1.0
+PL_API extern const struct pl_filter_config pl_filter_robidoux;
+PL_API extern const struct pl_filter_config pl_filter_robidouxsharp;
+PL_API extern const struct pl_filter_config pl_filter_ewa_robidoux;
+PL_API extern const struct pl_filter_config pl_filter_ewa_robidouxsharp;
+// Special/opaque filters
+PL_API extern const struct pl_filter_config pl_filter_oversample;
+
+// Backwards compatibility
+#define pl_filter_triangle pl_filter_bilinear
+#define pl_oversample_frame_mixer pl_filter_oversample
+
+// A list of built-in filter configs, terminated by NULL
+PL_API extern const struct pl_filter_config * const pl_filter_configs[];
+PL_API extern const int pl_num_filter_configs; // excluding trailing NULL
+
+// Find the filter config with the given name, or NULL on failure.
+// `usage` restricts the valid usage (based on `pl_filter_config.allowed`).
+PL_API const struct pl_filter_config *
+pl_find_filter_config(const char *name, enum pl_filter_usage usage);
+
+// Backward compatibility with the previous filter configuration API. Redundant
+// with pl_filter_config.name/description. May be deprecated in the future.
+struct pl_filter_preset {
+ const char *name;
+ const struct pl_filter_config *filter;
+
+ // Longer / friendly name, or NULL for aliases
+ const char *description;
+};
+
+// A list of built-in filter presets, terminated by {0}
+PL_API extern const struct pl_filter_preset pl_filter_presets[];
+PL_API extern const int pl_num_filter_presets; // excluding trailing {0}
+
+// Find the filter preset with the given name, or NULL on failure.
+PL_API const struct pl_filter_preset *pl_find_filter_preset(const char *name);
+
+// Parameters for filter generation.
+struct pl_filter_params {
+ // The particular filter configuration to be sampled. config.kernel must
+ // be set to a valid pl_filter_function.
+ struct pl_filter_config config;
+
+ // The precision of the resulting LUT. A value of 64 should be fine for
+ // most practical purposes, but higher or lower values may be justified
+ // depending on the use case. This value must be set to something > 0.
+ int lut_entries;
+
+ // --- Polar filers only (config.polar)
+
+ // As a micro-optimization, all samples below this cutoff value will be
+ // ignored when updating the cutoff radius. Setting it to a value of 0.0
+ // disables this optimization.
+ float cutoff;
+
+ // --- Separable filters only (!config.polar)
+
+ // Indicates the maximum row size that is supported by the calling code, or
+ // 0 for no limit.
+ int max_row_size;
+
+ // Indicates the row stride alignment. For some use cases (e.g. uploading
+ // the weights as a texture), there are certain alignment requirements for
+ // each row. The chosen row_size will always be a multiple of this value.
+ // Specifying 0 indicates no alignment requirements.
+ int row_stride_align;
+
+ // --- Deprecated options
+ float filter_scale PL_DEPRECATED; // no effect, use `config.blur` instead
+};
+
+#define pl_filter_params(...) (&(struct pl_filter_params) { __VA_ARGS__ })
+
+// Represents an initialized instance of a particular filter, with a
+// precomputed LUT. The interpretation of the LUT depends on the type of the
+// filter (polar or separable).
+typedef const struct pl_filter_t {
+ // Deep copy of the parameters, for convenience.
+ struct pl_filter_params params;
+
+ // Contains the true radius of the computed filter. This may be
+ // smaller than the configured radius depending on the exact filter
+ // parameters used. Mainly relevant for polar filters, since
+ // it affects the value range of *weights.
+ float radius;
+
+ // Radius of the first zero crossing (main lobe size).
+ float radius_zero;
+
+ // The computed look-up table (LUT). For polar filters, this is interpreted
+ // as a 1D array with dimensions [lut_entries] containing the raw filter
+ // samples on the scale [0, radius]. For separable (non-polar) filters,
+ // this is interpreted as a 2D array with dimensions
+ // [lut_entries][row_stride]. The inner rows contain the `row_size` samples
+ // to convolve with the corresponding input pixels. The outer coordinate is
+ // used to very the fractional offset (phase). So for example, if the
+ // sample position to reconstruct is directly aligned with the source
+ // texels, you would use the values from weights[0]. If the sample position
+ // to reconstruct is exactly half-way between two source texels (180° out
+ // of phase), you would use the values from weights[lut_entries/2].
+ const float *weights;
+
+ // --- separable filters only (!params.config.polar)
+
+ // The number of source texels to convolve over for each row. This value
+ // will never exceed the given `max_row_size`. If the filter ends up
+ // cut off because of this, the bool `insufficient` will be set to true.
+ int row_size;
+ bool insufficient;
+
+ // The separation (in *weights) between each row of the filter. Always
+ // a multiple of params.row_stride_align.
+ int row_stride;
+
+ // --- deprecated / removed fields
+ float radius_cutoff PL_DEPRECATED; // identical to `radius`
+} *pl_filter;
+
+// Generate (compute) a filter instance based on a given filter configuration.
+// The resulting pl_filter must be freed with `pl_filter_free` when no longer
+// needed. Returns NULL if filter generation fails due to invalid parameters
+// (i.e. missing a required parameter).
+PL_API pl_filter pl_filter_generate(pl_log log, const struct pl_filter_params *params);
+PL_API void pl_filter_free(pl_filter *filter);
+
+PL_API_END
+
+#endif // LIBPLACEBO_FILTER_KERNELS_H_
diff --git a/src/include/libplacebo/gamut_mapping.h b/src/include/libplacebo/gamut_mapping.h
new file mode 100644
index 0000000..a92a73b
--- /dev/null
+++ b/src/include/libplacebo/gamut_mapping.h
@@ -0,0 +1,182 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_GAMUT_MAPPING_H_
+#define LIBPLACEBO_GAMUT_MAPPING_H_
+
+#include <libplacebo/common.h>
+#include <libplacebo/colorspace.h>
+
+PL_API_BEGIN
+
+struct pl_gamut_map_params;
+struct pl_gamut_map_function {
+ const char *name; // Identifier
+ const char *description; // Friendly / longer name
+
+ // The gamut-mapping function itself. Iterates over all values in `lut`,
+ // and adapts them as needed.
+ void (*map)(float *lut, const struct pl_gamut_map_params *params);
+
+ // Returns true if `map` supports both stretching and contracting the
+ // gamut. In this case, `map` is always executed, even if the output gamut
+ // is larger than the input gamut.
+ bool bidirectional;
+
+ // Private data. Unused by libplacebo, but may be accessed by `map`.
+ void *priv;
+};
+
+struct pl_gamut_map_constants {
+ // (Relative) chromaticity protection zone for perceptual mapping [0,1]
+ float perceptual_deadzone;
+
+ // Strength of the perceptual saturation mapping component [0,1]
+ float perceptual_strength;
+
+ // I vs C curve gamma to use for colorimetric clipping [0,10]
+ float colorimetric_gamma;
+
+ // Knee point to use for softclipping methods (perceptual, softclip) [0,1]
+ float softclip_knee;
+
+ // Desaturation strength (for softclip only) [0,1]
+ float softclip_desat;
+};
+
+#define PL_GAMUT_MAP_CONSTANTS \
+ .colorimetric_gamma = 1.80f, \
+ .softclip_knee = 0.70f, \
+ .softclip_desat = 0.35f, \
+ .perceptual_deadzone = 0.30f, \
+ .perceptual_strength = 0.80f,
+
+struct pl_gamut_map_params {
+ // If `function` is NULL, defaults to `pl_gamut_map_clip`.
+ const struct pl_gamut_map_function *function;
+
+ // The desired input/output primaries. This affects the subjective color
+ // volume in which the desired mapping shall take place.
+ struct pl_raw_primaries input_gamut;
+ struct pl_raw_primaries output_gamut;
+
+ // Minimum/maximum luminance (PQ) of the target display. Note that the same
+ // value applies to both the input and output, since it's assumed that tone
+ // mapping has already happened by this stage. This effectively defines the
+ // legal gamut boundary in RGB space.
+ //
+ // This also defines the I channel value range, for `pl_gamut_map_generate`
+ float min_luma;
+ float max_luma;
+
+ // Common constants, should be initialized to PL_GAMUT_MAP_CONSTANTS if
+ // not intending to override them further.
+ struct pl_gamut_map_constants constants;
+
+ // -- LUT generation options (for `pl_gamut_map_generate` only)
+
+ // The size of the resulting LUT, per channel.
+ //
+ // Note: For quality, it's generally best to increase h > I > C
+ int lut_size_I;
+ int lut_size_C;
+ int lut_size_h;
+
+ // The stride (in number of floats) between elements in the resulting LUT.
+ int lut_stride;
+
+ // -- Removed parameters
+ float chroma_margin PL_DEPRECATED; // non-functional
+};
+
+#define pl_gamut_map_params(...) (&(struct pl_gamut_map_params) { \
+ .constants = { PL_GAMUT_MAP_CONSTANTS }, \
+ __VA_ARGS__ \
+})
+
+// Note: Only does pointer equality testing on `function`
+PL_API bool pl_gamut_map_params_equal(const struct pl_gamut_map_params *a,
+ const struct pl_gamut_map_params *b);
+
+// Returns true if the given gamut mapping configuration effectively represents
+// a no-op configuration. Gamut mapping can be skipped in this case.
+PL_API bool pl_gamut_map_params_noop(const struct pl_gamut_map_params *params);
+
+// Generate a gamut-mapping LUT for a given configuration. LUT samples are
+// stored as IPTPQc4 values, but the LUT itself is indexed by IChPQc4,spanning
+// the effective range [min_luma, max_luma] × [0, 0.5] × [-pi,pi].
+//
+// This ordering is designed to keep frequently co-occurring values close in
+// memory, while permitting simple wrapping of the 'h' component.
+PL_API void pl_gamut_map_generate(float *out, const struct pl_gamut_map_params *params);
+
+// Samples a gamut mapping function for a single IPTPQc4 value. The input
+// values are updated in-place.
+PL_API void pl_gamut_map_sample(float x[3], const struct pl_gamut_map_params *params);
+
+// Performs no gamut-mapping, just hard clips out-of-range colors per-channel.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_clip;
+
+// Performs a perceptually balanced (saturation) gamut mapping, using a soft
+// knee function to preserve in-gamut colors, followed by a final softclip
+// operation. This works bidirectionally, meaning it can both compress and
+// expand the gamut. Behaves similar to a blend of `saturation` and `softclip`.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_perceptual;
+
+// Performs a perceptually balanced gamut mapping using a soft knee function to
+// roll-off clipped regions, and a hue shifting function to preserve saturation.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_softclip;
+
+// Performs relative colorimetric clipping, while maintaining an exponential
+// relationship between brightness and chromaticity.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_relative;
+
+// Performs simple RGB->RGB saturation mapping. The input R/G/B channels are
+// mapped directly onto the output R/G/B channels. Will never clip, but will
+// distort all hues and/or result in a faded look.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_saturation;
+
+// Performs absolute colorimetric clipping. Like pl_gamut_map_relative, but
+// does not adapt the white point.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_absolute;
+
+// Performs constant-luminance colorimetric clipping, desaturing colors
+// towards white until they're in-range.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_desaturate;
+
+// Uniformly darkens the input slightly to prevent clipping on blown-out
+// highlights, then clamps colorimetrically to the input gamut boundary,
+// biased slightly to preserve chromaticity over luminance.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_darken;
+
+// Performs no gamut mapping, but simply highlights out-of-gamut pixels.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_highlight;
+
+// Linearly/uniformly desaturates the image in order to bring the entire
+// image into the target gamut.
+PL_API extern const struct pl_gamut_map_function pl_gamut_map_linear;
+
+// A list of built-in gamut mapping functions, terminated by NULL
+PL_API extern const struct pl_gamut_map_function * const pl_gamut_map_functions[];
+PL_API extern const int pl_num_gamut_map_functions; // excluding trailing NULL
+
+// Find the gamut mapping function with the given name, or NULL on failure.
+PL_API const struct pl_gamut_map_function *pl_find_gamut_map_function(const char *name);
+
+PL_API_END
+
+#endif // LIBPLACEBO_GAMUT_MAPPING_H_
diff --git a/src/include/libplacebo/gpu.h b/src/include/libplacebo/gpu.h
new file mode 100644
index 0000000..a63fdf7
--- /dev/null
+++ b/src/include/libplacebo/gpu.h
@@ -0,0 +1,1464 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_GPU_H_
+#define LIBPLACEBO_GPU_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <libplacebo/common.h>
+#include <libplacebo/cache.h>
+#include <libplacebo/log.h>
+
+PL_API_BEGIN
+
+// These are not memory managed, and should represent compile-time constants
+typedef const char *pl_debug_tag;
+#define PL_DEBUG_TAG (__FILE__ ":" PL_TOSTRING(__LINE__))
+
+// Type of a shader input descriptor.
+enum pl_desc_type {
+ PL_DESC_INVALID = 0,
+ PL_DESC_SAMPLED_TEX, // C: pl_tex* GLSL: combined texture sampler
+ // (`pl_tex->params.sampleable` must be set)
+ PL_DESC_STORAGE_IMG, // C: pl_tex* GLSL: storage image
+ // (`pl_tex->params.storable` must be set)
+ PL_DESC_BUF_UNIFORM, // C: pl_buf* GLSL: uniform buffer
+ // (`pl_buf->params.uniform` must be set)
+ PL_DESC_BUF_STORAGE, // C: pl_buf* GLSL: storage buffer
+ // (`pl_buf->params.storable` must be set)
+ PL_DESC_BUF_TEXEL_UNIFORM,// C: pl_buf* GLSL: uniform samplerBuffer
+ // (`pl_buf->params.uniform` and `format` must be set)
+ PL_DESC_BUF_TEXEL_STORAGE,// C: pl_buf* GLSL: uniform imageBuffer
+ // (`pl_buf->params.uniform` and `format` must be set)
+ PL_DESC_TYPE_COUNT
+};
+
+// This file contains the definition of an API which is designed to abstract
+// away from platform-specific APIs like the various OpenGL variants, Direct3D
+// and Vulkan in a common way. It is a much more limited API than those APIs,
+// since it tries targeting a very small common subset of features that is
+// needed to implement libplacebo's rendering.
+//
+// NOTE: Most, but not all, parameter conditions (phrases such as "must" or
+// "valid usage" are explicitly tested and result in error messages followed by
+// graceful failure. Exceptions are noted where they exist.
+
+// Structure which wraps metadata describing GLSL capabilities.
+struct pl_glsl_version {
+ int version; // GLSL version (e.g. 450), for #version
+ bool gles; // GLSL ES semantics (ESSL)
+ bool vulkan; // GL_KHR_vulkan_glsl semantics
+
+ // Compute shader support and limits. If `compute` is false, then all
+ // of the remaining fields in this section are {0}.
+ bool compute;
+ size_t max_shmem_size; // maximum compute shader shared memory size
+ uint32_t max_group_threads; // maximum number of local threads per work group
+ uint32_t max_group_size[3]; // maximum work group size per dimension
+
+ // If nonzero, signals availability of shader subgroups. This guarantess
+ // availability of all of the following extensions:
+ // - GL_KHR_shader_subgroup_basic
+ // - GL_KHR_shader_subgroup_vote
+ // - GL_KHR_shader_subgroup_arithmetic
+ // - GL_KHR_shader_subgroup_ballot
+ // - GL_KHR_shader_subgroup_shuffle
+ uint32_t subgroup_size;
+
+ // Miscellaneous shader limits
+ int16_t min_gather_offset; // minimum `textureGatherOffset` offset
+ int16_t max_gather_offset; // maximum `textureGatherOffset` offset
+};
+
+// Backwards compatibility alias
+#define pl_glsl_desc pl_glsl_version
+
+// Structure defining the physical limits and capabilities of this GPU
+// instance. If a limit is given as 0, that means that feature is unsupported.
+struct pl_gpu_limits {
+ // --- pl_gpu
+ bool thread_safe; // `pl_gpu` calls are thread-safe
+ bool callbacks; // supports asynchronous GPU callbacks
+
+ // --- pl_buf
+ size_t max_buf_size; // maximum size of any buffer
+ size_t max_ubo_size; // maximum size of a `uniform` buffer
+ size_t max_ssbo_size; // maximum size of a `storable` buffer
+ size_t max_vbo_size; // maximum size of a `drawable` buffer
+ size_t max_mapped_size; // maximum size of a `host_mapped` buffer
+ uint64_t max_buffer_texels; // maximum number of texels in a texel buffer
+ bool host_cached; // if true, PL_BUF_MEM_HOST buffers are cached
+
+ // Required alignment for PL_HANDLE_HOST_PTR imports. This is provided
+ // merely as a hint to the user. If the host pointer being imported is
+ // misaligned, libplacebo will internally round (over-map) the region.
+ size_t align_host_ptr;
+
+ // --- pl_tex
+ uint32_t max_tex_1d_dim; // maximum width for a 1D texture
+ uint32_t max_tex_2d_dim; // maximum width/height for a 2D texture (required)
+ uint32_t max_tex_3d_dim; // maximum width/height/depth for a 3D texture
+ bool blittable_1d_3d; // supports blittable 1D/3D textures
+ bool buf_transfer; // supports `pl_tex_transfer_params.buf`
+
+ // These don't represent hard limits but indicate performance hints for
+ // optimal alignment. For best performance, the corresponding field
+ // should be aligned to a multiple of these. They will always be a power
+ // of two.
+ size_t align_tex_xfer_pitch; // optimal `pl_tex_transfer_params.row_pitch`
+ size_t align_tex_xfer_offset; // optimal `pl_tex_transfer_params.buf_offset`
+
+ // --- pl_pass
+ size_t max_variable_comps; // maximum components passed in variables
+ size_t max_constants; // maximum `pl_pass_params.num_constants`
+ bool array_size_constants; // push constants can be used to size arrays
+ size_t max_pushc_size; // maximum `push_constants_size`
+ size_t align_vertex_stride; // alignment of `pl_pass_params.vertex_stride`
+ uint32_t max_dispatch[3]; // maximum dispatch size per dimension
+
+ // Note: At least one of `max_variable_comps` or `max_ubo_size` is
+ // guaranteed to be nonzero.
+
+ // As a performance hint, the GPU may signal the number of command queues
+ // it has for fragment and compute shaders, respectively. Users may use
+ // this information to decide the appropriate type of shader to dispatch.
+ uint32_t fragment_queues;
+ uint32_t compute_queues;
+};
+
+// Backwards compatibility aliases
+#define max_xfer_size max_buf_size
+#define align_tex_xfer_stride align_tex_xfer_pitch
+
+// Some `pl_gpu` operations allow sharing GPU resources with external APIs -
+// examples include interop with other graphics APIs such as CUDA, and also
+// various hardware decoding APIs. This defines the mechanism underpinning the
+// communication of such an interoperation.
+typedef uint64_t pl_handle_caps;
+enum pl_handle_type {
+ PL_HANDLE_FD = (1 << 0), // `int fd` for POSIX-style APIs
+ PL_HANDLE_WIN32 = (1 << 1), // `HANDLE` for win32 API
+ PL_HANDLE_WIN32_KMT = (1 << 2), // `HANDLE` for pre-Windows-8 win32 API
+ PL_HANDLE_DMA_BUF = (1 << 3), // 'int fd' for a dma_buf fd
+ PL_HANDLE_HOST_PTR = (1 << 4), // `void *` for a host-allocated pointer
+ PL_HANDLE_MTL_TEX = (1 << 5), // `MTLTexture*` for Apple platforms
+ PL_HANDLE_IOSURFACE = (1 << 6), // `IOSurfaceRef` for Apple platforms
+};
+
+struct pl_gpu_handle_caps {
+ pl_handle_caps tex; // supported handles for `pl_tex` + `pl_shared_mem`
+ pl_handle_caps buf; // supported handles for `pl_buf` + `pl_shared_mem`
+ pl_handle_caps sync; // supported handles for `pl_sync` / semaphores
+};
+
+// Wrapper for the handle used to communicate a shared resource externally.
+// This handle is owned by the `pl_gpu` - if a user wishes to use it in a way
+// that takes over ownership (e.g. importing into some APIs), they must clone
+// the handle before doing so (e.g. using `dup` for fds). It is important to
+// read the external API documentation _very_ carefully as different handle
+// types may be managed in different ways. (eg: CUDA takes ownership of an fd,
+// but does not take ownership of a win32 handle).
+union pl_handle {
+ int fd; // PL_HANDLE_FD / PL_HANDLE_DMA_BUF
+ void *handle; // PL_HANDLE_WIN32 / PL_HANDLE_WIN32_KMT / PL_HANDLE_MTL_TEX / PL_HANDLE_IOSURFACE
+ void *ptr; // PL_HANDLE_HOST_PTR
+};
+
+// Structure encapsulating memory that is shared between libplacebo and the
+// user. This memory can be imported into external APIs using the handle.
+//
+// If the object a `pl_shared_mem` belongs to is destroyed (e.g. via
+// `pl_buf_destroy`), the handle becomes undefined, as do the contents of the
+// memory it points to, as well as any external API objects imported from it.
+struct pl_shared_mem {
+ union pl_handle handle;
+ size_t size; // the total size of the memory referenced by this handle
+ size_t offset; // the offset of the object within the referenced memory
+
+ // Note: `size` is optional for some APIs and handle types, in particular
+ // when importing DMABUFs or D3D11 textures.
+
+ // For PL_HANDLE_DMA_BUF, this specifies the DRM format modifier that
+ // describes this resource. Note that when importing `pl_buf`, this must
+ // be DRM_FORMAT_MOD_LINEAR. For importing `pl_tex`, it can be any
+ // format modifier supported by the implementation.
+ uint64_t drm_format_mod;
+
+ // When importing a `pl_tex` of type PL_HANDLE_DMA_BUF, this can be used to
+ // set the image stride (AKA pitch) in memory. If left as 0, defaults to
+ // the image width/height.
+ size_t stride_w;
+ size_t stride_h;
+
+ // When importing a `pl_tex` of type PL_HANDLE_MTL_TEX, this determines
+ // which plane is imported (0 - 2).
+ unsigned plane;
+};
+
+// Structure grouping PCI bus address fields for GPU devices
+struct pl_gpu_pci_address {
+ uint32_t domain;
+ uint32_t bus;
+ uint32_t device;
+ uint32_t function;
+};
+
+typedef const struct pl_fmt_t *pl_fmt;
+
+// Abstract device context which wraps an underlying graphics context and can
+// be used to dispatch rendering commands.
+//
+// Thread-safety: Depends on `pl_gpu_limits.thread_safe`
+typedef const struct pl_gpu_t {
+ pl_log log;
+
+ struct pl_glsl_version glsl; // GLSL features supported by this GPU
+ struct pl_gpu_limits limits; // physical device limits and capabilities
+
+ // Fields relevant to external API interop. If the underlying device does
+ // not support interop with other APIs, these will all be {0}.
+ struct pl_gpu_handle_caps export_caps; // supported handles for exporting
+ struct pl_gpu_handle_caps import_caps; // supported handles for importing
+ uint8_t uuid[16]; // underlying device UUID
+
+ // Supported texture formats, in preference order. (If there are multiple
+ // similar formats, the "better" ones come first)
+ pl_fmt *formats;
+ int num_formats;
+
+ // PCI Bus address of the underlying device, to help with interop.
+ // This will only be filled in if interop is supported.
+ struct pl_gpu_pci_address pci;
+} *pl_gpu;
+
+// Attach a pl_cache object to this GPU instance. This cache will be
+// used to cache all compiled shaders, as well as several other shader objects
+// (e.g. cached 3DLUTs). Calling this with `cache = NULL` disables the cache.
+//
+// Note: Calling this after shaders have already been compiled will not
+// retroactively add those shaders to the cache, so it's recommended to set
+// this early, before creating any passes.
+PL_API void pl_gpu_set_cache(pl_gpu gpu, pl_cache cache);
+
+enum pl_fmt_type {
+ PL_FMT_UNKNOWN = 0, // also used for inconsistent multi-component formats
+ PL_FMT_UNORM, // unsigned, normalized integer format (sampled as float)
+ PL_FMT_SNORM, // signed, normalized integer format (sampled as float)
+ PL_FMT_UINT, // unsigned integer format (sampled as integer)
+ PL_FMT_SINT, // signed integer format (sampled as integer)
+ PL_FMT_FLOAT, // (signed) float formats, any bit size
+ PL_FMT_TYPE_COUNT,
+};
+
+enum pl_fmt_caps {
+ PL_FMT_CAP_SAMPLEABLE = 1 << 0, // may be sampled from (PL_DESC_SAMPLED_TEX)
+ PL_FMT_CAP_STORABLE = 1 << 1, // may be used as storage image (PL_DESC_STORAGE_IMG)
+ PL_FMT_CAP_LINEAR = 1 << 2, // may be linearly samplied from (PL_TEX_SAMPLE_LINEAR)
+ PL_FMT_CAP_RENDERABLE = 1 << 3, // may be rendered to (pl_pass_params.target_fmt)
+ PL_FMT_CAP_BLENDABLE = 1 << 4, // may be blended to (pl_pass_params.enable_blend)
+ PL_FMT_CAP_BLITTABLE = 1 << 5, // may be blitted from/to (pl_tex_blit)
+ PL_FMT_CAP_VERTEX = 1 << 6, // may be used as a vertex attribute
+ PL_FMT_CAP_TEXEL_UNIFORM = 1 << 7, // may be used as a texel uniform buffer
+ PL_FMT_CAP_TEXEL_STORAGE = 1 << 8, // may be used as a texel storage buffer
+ PL_FMT_CAP_HOST_READABLE = 1 << 9, // may be used with `host_readable` textures
+ PL_FMT_CAP_READWRITE = 1 << 10, // may be used with PL_DESC_ACCESS_READWRITE
+
+ // Notes:
+ // - PL_FMT_CAP_LINEAR also implies PL_FMT_CAP_SAMPLEABLE
+ // - PL_FMT_CAP_STORABLE also implies `pl_gpu.glsl.compute`
+ // - PL_FMT_CAP_BLENDABLE implies PL_FMT_CAP_RENDERABLE
+ // - PL_FMT_CAP_VERTEX implies that the format is non-opaque
+ // - PL_FMT_CAP_HOST_READABLE implies that the format is non-opaque
+};
+
+struct pl_fmt_plane {
+ // Underlying format of this particular sub-plane. This describes the
+ // components, texel size and host representation for the purpose of
+ // e.g. transfers, blits, and sampling.
+ pl_fmt format;
+
+ // X/Y subsampling shift factor for this plane.
+ uint8_t shift_x, shift_y;
+};
+
+// Structure describing a texel/vertex format.
+struct pl_fmt_t {
+ const char *name; // symbolic name for this format (e.g. rgba32f)
+ uint64_t signature; // unique but stable signature (for pass reusability)
+
+ enum pl_fmt_type type; // the format's data type and interpretation
+ enum pl_fmt_caps caps; // the features supported by this format
+ int num_components; // number of components for this format
+ int component_depth[4]; // meaningful bits per component, texture precision
+ size_t internal_size; // internal texel size (for blit compatibility)
+
+ // For planar formats, this provides a description of each sub-plane.
+ //
+ // Note on planar formats: Planar formats are always opaque and typically
+ // support only a limit subset of capabilities (or none at all). Access
+ // should be done via sub-planes. (See `pl_tex.planes`)
+ struct pl_fmt_plane planes[4];
+ int num_planes; // or 0 for non-planar textures
+
+ // This controls the relationship between the data as seen by the host and
+ // the way it's interpreted by the texture. The host representation is
+ // always tightly packed (no padding bits in between each component).
+ //
+ // This representation assumes little endian ordering, i.e. components
+ // being ordered from LSB to MSB in memory. Note that for oddly packed
+ // formats like rgb10a2 or rgb565, this is inconsistent with the naming.
+ // (That is to say, rgb565 has sample order {2, 1, 0} under this convention
+ // - because rgb565 treats the R channel as the *most* significant bits)
+ //
+ // If `opaque` is true, then there's no meaningful correspondence between
+ // the two, and all of the remaining fields in this section are unset.
+ //
+ // If `emulated` is true, then this format doesn't actually exist on the
+ // GPU as an uploadable texture format - and any apparent support is being
+ // emulated (typically using compute shaders in the upload path).
+ bool opaque;
+ bool emulated;
+ size_t texel_size; // total size in bytes per texel
+ size_t texel_align; // texel alignment requirements (bytes)
+ int host_bits[4]; // number of meaningful bits in host memory
+ int sample_order[4]; // sampled index for each component, e.g.
+ // {2, 1, 0, 3} for BGRA textures
+
+ // For sampleable formats, this bool indicates whether or not the format
+ // is compatible with `textureGather()`
+ bool gatherable;
+
+ // If usable as a vertex or texel buffer format, this gives the GLSL type
+ // corresponding to the data. (e.g. vec4)
+ const char *glsl_type;
+
+ // If usable as a storage image or texel storage buffer
+ // (PL_FMT_CAP_STORABLE / PL_FMT_CAP_TEXEL_STORAGE), this gives the GLSL
+ // texel format corresponding to the format (e.g. rgba16ui), if any. This
+ // field may be NULL, in which case the format modifier may be left
+ // unspecified.
+ const char *glsl_format;
+
+ // If available, this gives the fourcc associated with the host
+ // representation. In particular, this is intended for use with
+ // PL_HANDLE_DMA_BUF, where this field will match the DRM format from
+ // <drm_fourcc.h>. May be 0, for formats without matching DRM fourcc.
+ uint32_t fourcc;
+
+ // If `fourcc` is set, this contains the list of supported drm format
+ // modifiers for this format.
+ const uint64_t *modifiers;
+ int num_modifiers;
+};
+
+// Returns whether or not a pl_fmt's components are ordered sequentially
+// in memory in the order RGBA.
+PL_API bool pl_fmt_is_ordered(pl_fmt fmt);
+
+// Returns whether or not a pl_fmt is sampled as a float (e.g. UNORM)
+PL_API bool pl_fmt_is_float(pl_fmt fmt);
+
+// Returns whether or not a pl_fmt supports a given DRM modifier.
+PL_API bool pl_fmt_has_modifier(pl_fmt fmt, uint64_t modifier);
+
+// Helper function to find a format with a given number of components and
+// minimum effective precision per component. If `host_bits` is set, then the
+// format will always be non-opaque, unpadded, ordered and have exactly this
+// bit depth for each component. Finally, all `caps` must be supported.
+PL_API pl_fmt pl_find_fmt(pl_gpu gpu, enum pl_fmt_type type, int num_components,
+ int min_depth, int host_bits, enum pl_fmt_caps caps);
+
+// Finds a vertex format for a given configuration. The resulting vertex will
+// have a component depth equivalent to the sizeof() the equivalent host type.
+// (e.g. PL_FMT_FLOAT will always have sizeof(float))
+PL_API pl_fmt pl_find_vertex_fmt(pl_gpu gpu, enum pl_fmt_type type, int num_components);
+
+// Find a format based on its name.
+PL_API pl_fmt pl_find_named_fmt(pl_gpu gpu, const char *name);
+
+// Find a format based on its fourcc.
+PL_API pl_fmt pl_find_fourcc(pl_gpu gpu, uint32_t fourcc);
+
+// A generic 'timer query' object. These can be used to measure an
+// approximation of the GPU execution time of a given operation. Due to the
+// highly asynchronous nature of GPUs, the actual results of any individual
+// timer query may be delayed by quite a bit. As such, users should avoid
+// trying to pair any particular GPU command with any particular timer query
+// result, and only reuse `pl_timer` objects with identical operations. The
+// results of timer queries are guaranteed to be in-order, but individual
+// queries may be dropped, and some operations might not record timer results
+// at all. (For example, if the underlying hardware does not support timer
+// queries for a given operation type)
+//
+// Thread-safety: Unsafe
+typedef struct pl_timer_t *pl_timer;
+
+// Creates a new timer object. This may return NULL, for example if the
+// implementation does not support timers, but since passing NULL to
+// `pl_timer_destroy` and `pl_timer_query` is safe, users generally need not
+// concern themselves with handling this.
+PL_API pl_timer pl_timer_create(pl_gpu gpu);
+PL_API void pl_timer_destroy(pl_gpu gpu, pl_timer *);
+
+// Queries any results that have been measured since the last execution of
+// `pl_timer_query`. There may be more than one result, in which case the user
+// should simply call the function again to get the subsequent values. This
+// function returns a value of 0 in the event that there are no more
+// unprocessed results.
+//
+// The results are reported in nanoseconds, but the actual precision of the
+// timestamp queries may be significantly lower.
+//
+// Note: Results do not queue up indefinitely. Generally, the implementation
+// will only keep track of a small, fixed number of results internally. Make
+// sure to include this function as part of your main rendering loop to process
+// all of its results, or older results will be overwritten by newer ones.
+PL_API uint64_t pl_timer_query(pl_gpu gpu, pl_timer);
+
+enum pl_buf_mem_type {
+ PL_BUF_MEM_AUTO = 0, // use whatever seems most appropriate
+ PL_BUF_MEM_HOST, // try allocating from host memory (RAM)
+ PL_BUF_MEM_DEVICE, // try allocating from device memory (VRAM)
+ PL_BUF_MEM_TYPE_COUNT,
+
+ // Note: This distinction only matters for discrete GPUs
+};
+
+// Structure describing a buffer.
+struct pl_buf_params {
+ size_t size; // size in bytes (must be <= `pl_gpu_limits.max_buf_size`)
+ bool host_writable; // contents may be updated via pl_buf_write()
+ bool host_readable; // contents may be read back via pl_buf_read()
+ bool host_mapped; // create a persistent, RW mapping (pl_buf.data)
+
+ // May be used as PL_DESC_BUF_UNIFORM or PL_DESC_BUF_TEXEL_UNIFORM.
+ // Requires `size <= pl_gpu_limits.max_ubo_size`
+ bool uniform;
+
+ // May be used as PL_DESC_BUF_STORAGE or PL_DESC_BUF_TEXEL_STORAGE.
+ // Requires `size <= pl_gpu_limits.max_ssbo_size`
+ bool storable;
+
+ // May be used as the source of vertex data for `pl_pass_run`.
+ bool drawable;
+
+ // Provide a hint for the memory type you want to use when allocating
+ // this buffer's memory.
+ //
+ // Note: Restrictions may apply depending on the usage flags. In
+ // particular, allocating buffers with `uniform` or `storable` enabled from
+ // non-device memory will almost surely fail.
+ enum pl_buf_mem_type memory_type;
+
+ // Setting this to a format with the `PL_FMT_CAP_TEXEL_*` capability allows
+ // this buffer to be used as a `PL_DESC_BUF_TEXEL_*`, when `uniform` and
+ // `storage` are respectively also enabled.
+ pl_fmt format;
+
+ // At most one of `export_handle` and `import_handle` can be set for a
+ // buffer.
+
+ // Setting this indicates that the memory backing this buffer should be
+ // shared with external APIs, If so, this must be exactly *one* of
+ // `pl_gpu.export_caps.buf`.
+ enum pl_handle_type export_handle;
+
+ // Setting this indicates that the memory backing this buffer will be
+ // imported from an external API. If so, this must be exactly *one* of
+ // `pl_gpu.import_caps.buf`.
+ enum pl_handle_type import_handle;
+
+ // If the shared memory is being imported, the import handle must be
+ // specified here. Otherwise, this is ignored.
+ struct pl_shared_mem shared_mem;
+
+ // If non-NULL, the buffer will be created with these contents. Otherwise,
+ // the initial data is undefined. Using this does *not* require setting
+ // host_writable.
+ const void *initial_data;
+
+ // Arbitrary user data. libplacebo does not use this at all.
+ void *user_data;
+
+ // Arbitrary identifying tag. Used only for debugging purposes.
+ pl_debug_tag debug_tag;
+};
+
+#define pl_buf_params(...) (&(struct pl_buf_params) { \
+ .debug_tag = PL_DEBUG_TAG, \
+ __VA_ARGS__ \
+ })
+
+// A generic buffer, which can be used for multiple purposes (texture transfer,
+// storage buffer, uniform buffer, etc.)
+//
+// Note on efficiency: A pl_buf does not necessarily represent a true "buffer"
+// object on the underlying graphics API. It may also refer to a sub-slice of
+// a larger buffer, depending on the implementation details of the GPU. The
+// bottom line is that users do not need to worry about the efficiency of using
+// many small pl_buf objects. Having many small pl_bufs, even lots of few-byte
+// vertex buffers, is designed to be completely fine.
+//
+// Thread-safety: Unsafe
+typedef const struct pl_buf_t {
+ struct pl_buf_params params;
+ uint8_t *data; // for persistently mapped buffers, points to the first byte
+
+ // If `params.handle_type` is set, this structure references the shared
+ // memory backing this buffer, via the requested handle type.
+ //
+ // While this buffer is not in an "exported" state, the contents of the
+ // memory are undefined. (See: `pl_buf_export`)
+ struct pl_shared_mem shared_mem;
+} *pl_buf;
+
+// Create a buffer. The type of buffer depends on the parameters. The buffer
+// parameters must adhere to the restrictions imposed by the pl_gpu_limits.
+// Returns NULL on failure.
+//
+// For buffers with shared memory, the buffer is considered to be in an
+// "exported" state by default, and may be used directly by the external API
+// after being created (until the first libplacebo operation on the buffer).
+PL_API pl_buf pl_buf_create(pl_gpu gpu, const struct pl_buf_params *params);
+PL_API void pl_buf_destroy(pl_gpu gpu, pl_buf *buf);
+
+// This behaves like `pl_buf_create`, but if the buffer already exists and has
+// incompatible parameters, it will get destroyed first. A buffer is considered
+// "compatible" if it has the same buffer type and texel format, a size greater
+// than or equal to the requested size, and it has a superset of the features
+// the user requested. After this operation, the contents of the buffer are
+// undefined.
+//
+// Note: Due to its unpredictability, it's not allowed to use this with
+// `params->initial_data` being set. Similarly, it's not allowed on a buffer
+// with `params->export_handle`. since this may invalidate the corresponding
+// external API's handle. Conversely, it *is* allowed on a buffer with
+// `params->host_mapped`, and the corresponding `buf->data` pointer *may*
+// change as a result of doing so.
+//
+// Note: If the `user_data` alone changes, this does not trigger a buffer
+// recreation. In theory, this can be used to detect when the buffer ended
+// up being recreated.
+PL_API bool pl_buf_recreate(pl_gpu gpu, pl_buf *buf, const struct pl_buf_params *params);
+
+// Update the contents of a buffer, starting at a given offset (must be a
+// multiple of 4) and up to a given size, with the contents of *data.
+//
+// This function will block until the buffer is no longer in use. Use
+// `pl_buf_poll` to perform non-blocking queries of buffer availability.
+//
+// Note: This function can incur synchronization overhead, so it shouldn't be
+// used in tight loops. If you do need to loop (e.g. to perform a strided
+// write), consider using host-mapped buffers, or fixing the memory in RAM,
+// before calling this function.
+PL_API void pl_buf_write(pl_gpu gpu, pl_buf buf, size_t buf_offset,
+ const void *data, size_t size);
+
+// Read back the contents of a buffer, starting at a given offset, storing the
+// data into *dest. Returns whether successful.
+//
+// This function will block until the buffer is no longer in use. Use
+// `pl_buf_poll` to perform non-blocking queries of buffer availability.
+PL_API bool pl_buf_read(pl_gpu gpu, pl_buf buf, size_t buf_offset,
+ void *dest, size_t size);
+
+// Copy `size` bytes from one buffer to another, reading from and writing to
+// the respective offsets.
+PL_API void pl_buf_copy(pl_gpu gpu, pl_buf dst, size_t dst_offset,
+ pl_buf src, size_t src_offset, size_t size);
+
+// Initiates a buffer export operation, allowing a buffer to be accessed by an
+// external API. This is only valid for buffers with `params.handle_type`.
+// Calling this twice in a row is a harmless no-op. Returns whether successful.
+//
+// There is no corresponding "buffer import" operation, the next libplacebo
+// operation that touches the buffer (e.g. pl_tex_upload, but also pl_buf_write
+// and pl_buf_read) will implicitly import the buffer back to libplacebo. Users
+// must ensure that all pending operations made by the external API are fully
+// completed before using it in libplacebo again. (Otherwise, the behaviour
+// is undefined)
+//
+// Please note that this function returning does not mean the memory is
+// immediately available as such. In general, it will mark a buffer as "in use"
+// in the same way any other buffer operation would, and it is the user's
+// responsibility to wait until `pl_buf_poll` returns false before accessing
+// the memory from the external API.
+//
+// In terms of the access performed by this operation, it is not considered a
+// "read" or "write" and therefore does not technically conflict with reads or
+// writes to the buffer performed by the host (via mapped memory - any use of
+// `pl_buf_read` or `pl_buf_write` would defeat the purpose of the export).
+// However, restrictions made by the external API may apply that prevent this.
+//
+// The recommended use pattern is something like this:
+//
+// while (loop) {
+// pl_buf buf = get_free_buffer(); // or block on pl_buf_poll
+// // write to the buffer using the external API
+// pl_tex_upload(gpu, /* ... buf ... */); // implicitly imports
+// pl_buf_export(gpu, buf);
+// }
+//
+// i.e. perform an external API operation, then use and immediately export the
+// buffer in libplacebo, and finally wait until `pl_buf_poll` is false before
+// re-using it in the external API. (Or get a new buffer in the meantime)
+PL_API bool pl_buf_export(pl_gpu gpu, pl_buf buf);
+
+// Returns whether or not a buffer is currently "in use". This can either be
+// because of a pending read operation, a pending write operation or a pending
+// buffer export operation. Any access to the buffer by external APIs or via
+// the host pointer (for host-mapped buffers) is forbidden while a buffer is
+// "in use". The only exception to this rule is multiple reads, for example
+// reading from a buffer with `pl_tex_upload` while simultaneously reading from
+// it using mapped memory.
+//
+// The `timeout`, specified in nanoseconds, indicates how long to block for
+// before returning. If set to 0, this function will never block, and only
+// returns the current status of the buffer. The actual precision of the
+// timeout may be significantly longer than one nanosecond, and has no upper
+// bound. This function does not provide hard latency guarantees. This function
+// may also return at any time, even if the buffer is still in use. If the user
+// wishes to block until the buffer is definitely no longer in use, the
+// recommended usage is:
+//
+// while (pl_buf_poll(gpu, buf, UINT64_MAX))
+// ; // do nothing
+//
+// Note: libplacebo operations on buffers are always internally synchronized,
+// so this is only needed for host-mapped or externally exported buffers.
+// However, it may be used to do non-blocking queries before calling blocking
+// functions such as `pl_buf_read`.
+//
+// Note: If `pl_gpu_limits.thread_safe` is set, this function is implicitly
+// synchronized, meaning it can safely be called on a `pl_buf` that is in use
+// by another thread.
+PL_API bool pl_buf_poll(pl_gpu gpu, pl_buf buf, uint64_t timeout);
+
+enum pl_tex_sample_mode {
+ PL_TEX_SAMPLE_NEAREST, // nearest neighbour sampling
+ PL_TEX_SAMPLE_LINEAR, // linear filtering, requires PL_FMT_CAP_LINEAR
+ PL_TEX_SAMPLE_MODE_COUNT,
+};
+
+enum pl_tex_address_mode {
+ PL_TEX_ADDRESS_CLAMP, // clamp the nearest edge texel
+ PL_TEX_ADDRESS_REPEAT, // repeat (tile) the texture
+ PL_TEX_ADDRESS_MIRROR, // repeat (mirror) the texture
+ PL_TEX_ADDRESS_MODE_COUNT,
+};
+
+// Structure describing a texture.
+struct pl_tex_params {
+ int w, h, d; // physical dimension; unused dimensions must be 0
+ pl_fmt format;
+
+ // The following bools describe what operations can be performed. The
+ // corresponding pl_fmt capability must be set for every enabled
+ // operation type.
+ //
+ // Note: For planar formats, it is also possible to set capabilities only
+ // supported by sub-planes. In this case, the corresponding functionality
+ // will be available for the sub-plane, but not the planar texture itself.
+ bool sampleable; // usable as a PL_DESC_SAMPLED_TEX
+ bool renderable; // usable as a render target (pl_pass_run)
+ // (must only be used with 2D textures)
+ bool storable; // usable as a storage image (PL_DESC_IMG_*)
+ bool blit_src; // usable as a blit source
+ bool blit_dst; // usable as a blit destination
+ bool host_writable; // may be updated with pl_tex_upload()
+ bool host_readable; // may be fetched with pl_tex_download()
+
+ // Note: For `blit_src`, `blit_dst`, the texture must either be
+ // 2-dimensional or `pl_gpu_limits.blittable_1d_3d` must be set.
+
+ // At most one of `export_handle` and `import_handle` can be set for a
+ // texture.
+
+ // Setting this indicates that the memory backing this texture should be
+ // shared with external APIs, If so, this must be exactly *one* of
+ // `pl_gpu.export_caps.tex`.
+ enum pl_handle_type export_handle;
+
+ // Setting this indicates that the memory backing this texture will be
+ // imported from an external API. If so, this must be exactly *one* of
+ // `pl_gpu.import_caps.tex`. Mutually exclusive with `initial_data`.
+ enum pl_handle_type import_handle;
+
+ // If the shared memory is being imported, the import handle must be
+ // specified here. Otherwise, this is ignored.
+ struct pl_shared_mem shared_mem;
+
+ // If non-NULL, the texture will be created with these contents (tightly
+ // packed). Using this does *not* require setting host_writable. Otherwise,
+ // the initial data is undefined. Mutually exclusive with `import_handle`.
+ const void *initial_data;
+
+ // Arbitrary user data. libplacebo does not use this at all.
+ void *user_data;
+
+ // Arbitrary identifying tag. Used only for debugging purposes.
+ pl_debug_tag debug_tag;
+};
+
+#define pl_tex_params(...) (&(struct pl_tex_params) { \
+ .debug_tag = PL_DEBUG_TAG, \
+ __VA_ARGS__ \
+ })
+
+static inline int pl_tex_params_dimension(const struct pl_tex_params params)
+{
+ return params.d ? 3 : params.h ? 2 : 1;
+}
+
+enum pl_sampler_type {
+ PL_SAMPLER_NORMAL, // gsampler2D, gsampler3D etc.
+ PL_SAMPLER_RECT, // gsampler2DRect
+ PL_SAMPLER_EXTERNAL, // gsamplerExternalOES
+ PL_SAMPLER_TYPE_COUNT,
+};
+
+// Conflates the following typical GPU API concepts:
+// - texture itself
+// - sampler state
+// - staging buffers for texture upload
+// - framebuffer objects
+// - wrappers for swapchain framebuffers
+// - synchronization needed for upload/rendering/etc.
+//
+// Essentially a pl_tex can be anything ranging from a normal texture, a wrapped
+// external/real framebuffer, a framebuffer object + texture pair, a mapped
+// texture (via pl_hwdec), or other sorts of things that can be sampled from
+// and/or rendered to.
+//
+// Thread-safety: Unsafe
+typedef const struct pl_tex_t *pl_tex;
+struct pl_tex_t {
+ struct pl_tex_params params;
+
+ // If `params.format` is a planar format, this contains `pl_tex` handles
+ // encapsulating individual texture planes. Conversely, if this is a
+ // sub-plane of a planar texture, `parent` points to the planar texture.
+ //
+ // Note: Calling `pl_tex_destroy` on sub-planes is undefined behavior.
+ pl_tex planes[4];
+ pl_tex parent;
+
+ // If `params.export_handle` is set, this structure references the shared
+ // memory backing this buffer, via the requested handle type.
+ //
+ // While this texture is not in an "exported" state, the contents of the
+ // memory are undefined. (See: `pl_tex_export`)
+ //
+ // Note: Due to vulkan driver limitations, `shared_mem.drm_format_mod` will
+ // currently always be set to DRM_FORMAT_MOD_INVALID. No guarantee can be
+ // made about the cross-driver compatibility of textures exported this way.
+ struct pl_shared_mem shared_mem;
+
+ // If `params.sampleable` is true, this indicates the correct sampler type
+ // to use when sampling from this texture.
+ enum pl_sampler_type sampler_type;
+};
+
+// Create a texture (with undefined contents). Returns NULL on failure. This is
+// assumed to be an expensive/rare operation, and may need to perform memory
+// allocation or framebuffer creation.
+PL_API pl_tex pl_tex_create(pl_gpu gpu, const struct pl_tex_params *params);
+PL_API void pl_tex_destroy(pl_gpu gpu, pl_tex *tex);
+
+// This works like `pl_tex_create`, but if the texture already exists and has
+// incompatible texture parameters, it will get destroyed first. A texture is
+// considered "compatible" if it has the same texture format and sample/address
+// mode and it supports a superset of the features the user requested.
+//
+// Even if the texture is not recreated, calling this function will still
+// invalidate the contents of the texture. (Note: Because of this,
+// `initial_data` may not be used with `pl_tex_recreate`. Doing so is an error)
+//
+// Note: If the `user_data` alone changes, this does not trigger a texture
+// recreation. In theory, this can be used to detect when the texture ended
+// up being recreated.
+PL_API bool pl_tex_recreate(pl_gpu gpu, pl_tex *tex, const struct pl_tex_params *params);
+
+// Invalidates the contents of a texture. After this, the contents are fully
+// undefined.
+PL_API void pl_tex_invalidate(pl_gpu gpu, pl_tex tex);
+
+union pl_clear_color {
+ float f[4];
+ int32_t i[4];
+ uint32_t u[4];
+};
+
+// Clear the dst texture with the given color (rgba). This is functionally
+// identical to a blit operation, which means `dst->params.blit_dst` must be
+// set.
+PL_API void pl_tex_clear_ex(pl_gpu gpu, pl_tex dst, const union pl_clear_color color);
+
+// Wrapper for `pl_tex_clear_ex` which only works for floating point textures.
+PL_API void pl_tex_clear(pl_gpu gpu, pl_tex dst, const float color[4]);
+
+struct pl_tex_blit_params {
+ // The texture to blit from. Must have `params.blit_src` enabled.
+ pl_tex src;
+
+ // The texture to blit to. Must have `params.blit_dst` enabled, and a
+ // format that is loosely compatible with `src`. This essentially means
+ // that they must have the same `internal_size`. Additionally, UINT
+ // textures can only be blitted to other UINT textures, and SINT textures
+ // can only be blitted to other SINT textures.
+ pl_tex dst;
+
+ // The region of the source texture to blit. Must be within the texture
+ // bounds of `src`. May be flipped. (Optional)
+ pl_rect3d src_rc;
+
+ // The region of the destination texture to blit into. Must be within the
+ // texture bounds of `dst`. May be flipped. Areas outside of `dst_rc` in
+ // `dst` are preserved. (Optional)
+ pl_rect3d dst_rc;
+
+ // If `src_rc` and `dst_rc` have different sizes, the texture will be
+ // scaled using the given texture sampling mode.
+ enum pl_tex_sample_mode sample_mode;
+};
+
+#define pl_tex_blit_params(...) (&(struct pl_tex_blit_params) { __VA_ARGS__ })
+
+// Copy a sub-rectangle from one texture to another.
+PL_API void pl_tex_blit(pl_gpu gpu, const struct pl_tex_blit_params *params);
+
+// Structure describing a texture transfer operation.
+struct pl_tex_transfer_params {
+ // Texture to transfer to/from. Depending on the type of the operation,
+ // this must have params.host_writable (uploads) or params.host_readable
+ // (downloads) set, respectively.
+ pl_tex tex;
+
+ // Note: Superfluous parameters are ignored, i.e. for a 1D texture, the y
+ // and z fields of `rc`, as well as the corresponding pitches, are ignored.
+ // In all other cases, the pitch must be large enough to contain the
+ // corresponding dimension of `rc`, and the `rc` must be normalized and
+ // fully contained within the image dimensions. Missing fields in the `rc`
+ // are inferred from the image size. If unset, the pitch is inferred
+ // from `rc` (that is, it's assumed that the data is tightly packed in the
+ // buffer). Otherwise, `row_pitch` *must* be a multiple of
+ // `tex->params.format->texel_align`, and `depth_pitch` must be a multiple
+ // of `row_pitch`.
+ pl_rect3d rc; // region of the texture to transfer
+ size_t row_pitch; // the number of bytes separating image rows
+ size_t depth_pitch; // the number of bytes separating image planes
+
+ // An optional timer to report the approximate duration of the texture
+ // transfer to. Note that this is only an approximation, since the actual
+ // texture transfer may happen entirely in the background (in particular,
+ // for implementations with asynchronous transfer capabilities). It's also
+ // not guaranteed that all GPUs support this.
+ pl_timer timer;
+
+ // An optional callback to fire after the operation completes. If this is
+ // specified, then the operation is performed asynchronously. Note that
+ // transfers to/from buffers are always asynchronous, even without, this
+ // field, so it's more useful for `ptr` transfers. (Though it can still be
+ // helpful to avoid having to manually poll buffers all the time)
+ //
+ // When this is *not* specified, uploads from `ptr` are still asynchronous
+ // but require a host memcpy, while downloads from `ptr` are blocking. As
+ // such, it's recommended to always try using asynchronous texture
+ // transfers wherever possible.
+ //
+ // Note: Requires `pl_gpu_limits.callbacks`
+ //
+ // Note: Callbacks are implicitly synchronized, meaning that callbacks are
+ // guaranteed to never execute concurrently with other callbacks. However,
+ // they may execute from any thread that the `pl_gpu` is used on.
+ void (*callback)(void *priv);
+ void *priv; // arbitrary user data
+
+ // For the data source/target of a transfer operation, there are two valid
+ // options:
+ //
+ // 1. Transferring to/from a buffer: (requires `pl_gpu_limits.buf_transfer`)
+ pl_buf buf; // buffer to use
+ size_t buf_offset; // offset of data within buffer, should be a
+ // multiple of `tex->params.format->texel_size`
+ // 2. Transferring to/from host memory directly:
+ void *ptr; // address of data
+ bool no_import; // always use memcpy, bypassing host ptr import
+
+ // Note: The contents of the memory region / buffer must exactly match the
+ // texture format; i.e. there is no explicit conversion between formats.
+};
+
+#define pl_tex_transfer_params(...) (&(struct pl_tex_transfer_params) { __VA_ARGS__ })
+
+// Upload data to a texture. Returns whether successful.
+PL_API bool pl_tex_upload(pl_gpu gpu, const struct pl_tex_transfer_params *params);
+
+// Download data from a texture. Returns whether successful.
+PL_API bool pl_tex_download(pl_gpu gpu, const struct pl_tex_transfer_params *params);
+
+// Returns whether or not a texture is currently "in use". This can either be
+// because of a pending read operation, a pending write operation or a pending
+// texture export operation. Note that this function's usefulness is extremely
+// limited under ordinary circumstances. In practically all cases, textures do
+// not need to be directly synchronized by the user, except when interfacing
+// with external libraries. This function should NOT, however, be used as a
+// crutch to avoid having to implement semaphore-based synchronization. Use
+// the API-specific functions such as `pl_vulkan_hold/release` for that.
+//
+// A good example of a use case in which this function is required is when
+// interoperating with external memory management that needs to know when an
+// imported texture is safe to free / reclaim internally, in which case
+// semaphores are insufficient because memory management is a host operation.
+//
+// The `timeout`, specified in nanoseconds, indicates how long to block for
+// before returning. If set to 0, this function will never block, and only
+// returns the current status of the texture. The actual precision of the
+// timeout may be significantly longer than one nanosecond, and has no upper
+// bound. This function does not provide hard latency guarantees. This function
+// may also return at any time, even if the texture is still in use. If the
+// user wishes to block until the texture is definitely no longer in use, the
+// recommended usage is:
+//
+// while (pl_tex_poll(gpu, buf, UINT64_MAX))
+// ; // do nothing
+//
+// Note: If `pl_gpu_limits.thread_safe` is set, this function is implicitly
+// synchronized, meaning it can safely be called on a `pl_tex` that is in use
+// by another thread.
+PL_API bool pl_tex_poll(pl_gpu gpu, pl_tex tex, uint64_t timeout);
+
+// Data type of a shader input variable (e.g. uniform, or UBO member)
+enum pl_var_type {
+ PL_VAR_INVALID = 0,
+ PL_VAR_SINT, // C: int GLSL: int/ivec
+ PL_VAR_UINT, // C: unsigned int GLSL: uint/uvec
+ PL_VAR_FLOAT, // C: float GLSL: float/vec/mat
+ PL_VAR_TYPE_COUNT
+};
+
+// Returns the host size (in bytes) of a pl_var_type.
+PL_API size_t pl_var_type_size(enum pl_var_type type);
+
+// Represents a shader input variable (concrete data, e.g. vector, matrix)
+struct pl_var {
+ const char *name; // name as used in the shader
+ enum pl_var_type type;
+ // The total number of values is given by dim_v * dim_m. For example, a
+ // vec2 would have dim_v = 2 and dim_m = 1. A mat3x4 would have dim_v = 4
+ // and dim_m = 3.
+ int dim_v; // vector dimension
+ int dim_m; // matrix dimension (number of columns, see below)
+ int dim_a; // array dimension
+};
+
+// Helper functions for constructing the most common pl_vars, with names
+// corresponding to their corresponding GLSL built-in types.
+PL_API struct pl_var pl_var_float(const char *name);
+PL_API struct pl_var pl_var_vec2(const char *name);
+PL_API struct pl_var pl_var_vec3(const char *name);
+PL_API struct pl_var pl_var_vec4(const char *name);
+PL_API struct pl_var pl_var_mat2(const char *name);
+PL_API struct pl_var pl_var_mat2x3(const char *name);
+PL_API struct pl_var pl_var_mat2x4(const char *name);
+PL_API struct pl_var pl_var_mat3(const char *name);
+PL_API struct pl_var pl_var_mat3x4(const char *name);
+PL_API struct pl_var pl_var_mat4x2(const char *name);
+PL_API struct pl_var pl_var_mat4x3(const char *name);
+PL_API struct pl_var pl_var_mat4(const char *name);
+PL_API struct pl_var pl_var_int(const char *name);
+PL_API struct pl_var pl_var_ivec2(const char *name);
+PL_API struct pl_var pl_var_ivec3(const char *name);
+PL_API struct pl_var pl_var_ivec4(const char *name);
+PL_API struct pl_var pl_var_uint(const char *name);
+PL_API struct pl_var pl_var_uvec2(const char *name);
+PL_API struct pl_var pl_var_uvec3(const char *name);
+PL_API struct pl_var pl_var_uvec4(const char *name);
+
+struct pl_named_var {
+ const char *glsl_name;
+ struct pl_var var;
+};
+
+// The same list as above, tagged by name and terminated with a {0} entry.
+PL_API extern const struct pl_named_var pl_var_glsl_types[];
+
+// Efficient helper function for performing a lookup in the above array.
+// Returns NULL if the variable is not legal. Note that the array dimension is
+// ignored, since it's usually part of the variable name and not the type name.
+PL_API const char *pl_var_glsl_type_name(struct pl_var var);
+
+// Converts a pl_fmt to an "equivalent" pl_var. Equivalent in this sense means
+// that the pl_var's type will be the same as the vertex's sampled type (e.g.
+// PL_FMT_UNORM gets turned into PL_VAR_FLOAT).
+PL_API struct pl_var pl_var_from_fmt(pl_fmt fmt, const char *name);
+
+// Describes the memory layout of a variable, relative to some starting location
+// (typically the offset within a uniform/storage/pushconstant buffer)
+//
+// Note on matrices: All GPUs expect column major matrices, for both buffers and
+// input variables. Care needs to be taken to avoid trying to use e.g. a
+// pl_matrix3x3 (which is row major) directly as a pl_var_update.data!
+//
+// In terms of the host layout, a column-major matrix (e.g. matCxR) with C
+// columns and R rows is treated like an array vecR[C]. The `stride` here refers
+// to the separation between these array elements, i.e. the separation between
+// the individual columns.
+//
+// Visualization of a mat4x3:
+//
+// 0 1 2 3 <- columns
+// 0 [ (A) (D) (G) (J) ]
+// 1 [ (B) (E) (H) (K) ]
+// 2 [ (C) (F) (I) (L) ]
+// ^ rows
+//
+// Layout in GPU memory: (stride=16, size=60)
+//
+// [ A B C ] X <- column 0, offset +0
+// [ D E F ] X <- column 1, offset +16
+// [ G H I ] X <- column 2, offset +32
+// [ J K L ] <- column 3, offset +48
+//
+// Note the lack of padding on the last column in this example.
+// In general: size <= stride * dim_m
+//
+// C representation: (stride=12, size=48)
+//
+// { { A, B, C },
+// { D, E, F },
+// { G, H, I },
+// { J, K, L } }
+//
+// Note on arrays: `stride` represents both the stride between elements of a
+// matrix, and the stride between elements of an array. That is, there is no
+// distinction between the columns of a matrix and the rows of an array. For
+// example, a mat2[10] and a vec2[20] share the same pl_var_layout - the stride
+// would be sizeof(vec2) and the size would be sizeof(vec2) * 2 * 10.
+//
+// For non-array/matrix types, `stride` is equal to `size`.
+
+struct pl_var_layout {
+ size_t offset; // the starting offset of the first byte
+ size_t stride; // the delta between two elements of an array/matrix
+ size_t size; // the total size of the input
+};
+
+// Returns the host layout of an input variable as required for a
+// tightly-packed, byte-aligned C data type, given a starting offset.
+PL_API struct pl_var_layout pl_var_host_layout(size_t offset, const struct pl_var *var);
+
+// Returns the GLSL std140 layout of an input variable given a current buffer
+// offset, as required for a buffer descriptor of type PL_DESC_BUF_UNIFORM
+//
+// The normal way to use this function is when calculating the size and offset
+// requirements of a uniform buffer in an incremental fashion, to calculate the
+// new offset of the next variable in this buffer.
+PL_API struct pl_var_layout pl_std140_layout(size_t offset, const struct pl_var *var);
+
+// Returns the GLSL std430 layout of an input variable given a current buffer
+// offset, as required for a buffer descriptor of type PL_DESC_BUF_STORAGE, and
+// for push constants.
+PL_API struct pl_var_layout pl_std430_layout(size_t offset, const struct pl_var *var);
+
+// Convenience definitions / friendly names for these
+#define pl_buf_uniform_layout pl_std140_layout
+#define pl_buf_storage_layout pl_std430_layout
+#define pl_push_constant_layout pl_std430_layout
+
+// Like memcpy, but copies bytes from `src` to `dst` in a manner governed by
+// the stride and size of `dst_layout` as well as `src_layout`. Also takes
+// into account the respective `offset`.
+PL_API void memcpy_layout(void *dst, struct pl_var_layout dst_layout,
+ const void *src, struct pl_var_layout src_layout);
+
+// Represents a compile-time constant.
+struct pl_constant {
+ enum pl_var_type type; // constant data type
+ uint32_t id; // GLSL `constant_id`
+ size_t offset; // byte offset in `constant_data`
+};
+
+// Represents a vertex attribute.
+struct pl_vertex_attrib {
+ const char *name; // name as used in the shader
+ pl_fmt fmt; // data format (must have PL_FMT_CAP_VERTEX)
+ size_t offset; // byte offset into the vertex struct
+ int location; // vertex location (as used in the shader)
+};
+
+// Returns an abstract namespace index for a given descriptor type. This will
+// always be a value >= 0 and < PL_DESC_TYPE_COUNT. Implementations can use
+// this to figure out which descriptors may share the same value of `binding`.
+// Bindings must only be unique for all descriptors within the same namespace.
+PL_API int pl_desc_namespace(pl_gpu gpu, enum pl_desc_type type);
+
+// Access mode of a shader input descriptor.
+enum pl_desc_access {
+ PL_DESC_ACCESS_READWRITE,
+ PL_DESC_ACCESS_READONLY,
+ PL_DESC_ACCESS_WRITEONLY,
+ PL_DESC_ACCESS_COUNT,
+};
+
+// Returns the GLSL syntax for a given access mode (e.g. "readonly").
+PL_API const char *pl_desc_access_glsl_name(enum pl_desc_access mode);
+
+// Represents a shader descriptor (e.g. texture or buffer binding)
+struct pl_desc {
+ const char *name; // name as used in the shader
+ enum pl_desc_type type;
+
+ // The binding of this descriptor, as used in the shader. All bindings
+ // within a namespace must be unique. (see: pl_desc_namespace)
+ int binding;
+
+ // For storage images and storage buffers, this can be used to restrict
+ // the type of access that may be performed on the descriptor. Ignored for
+ // the other descriptor types (uniform buffers and sampled textures are
+ // always read-only).
+ enum pl_desc_access access;
+};
+
+// Framebuffer blending mode (for raster passes)
+enum pl_blend_mode {
+ PL_BLEND_ZERO,
+ PL_BLEND_ONE,
+ PL_BLEND_SRC_ALPHA,
+ PL_BLEND_ONE_MINUS_SRC_ALPHA,
+ PL_BLEND_MODE_COUNT,
+};
+
+struct pl_blend_params {
+ enum pl_blend_mode src_rgb;
+ enum pl_blend_mode dst_rgb;
+ enum pl_blend_mode src_alpha;
+ enum pl_blend_mode dst_alpha;
+};
+
+#define pl_blend_params(...) (&(struct pl_blend_params) { __VA_ARGS__ })
+
+// Typical alpha compositing
+PL_API extern const struct pl_blend_params pl_alpha_overlay;
+
+enum pl_prim_type {
+ PL_PRIM_TRIANGLE_LIST,
+ PL_PRIM_TRIANGLE_STRIP,
+ PL_PRIM_TYPE_COUNT,
+};
+
+enum pl_index_format {
+ PL_INDEX_UINT16 = 0,
+ PL_INDEX_UINT32,
+ PL_INDEX_FORMAT_COUNT,
+};
+
+enum pl_pass_type {
+ PL_PASS_INVALID = 0,
+ PL_PASS_RASTER, // vertex+fragment shader
+ PL_PASS_COMPUTE, // compute shader (requires `pl_gpu.glsl.compute`)
+ PL_PASS_TYPE_COUNT,
+};
+
+// Description of a rendering pass. It conflates the following:
+// - GLSL shader(s) and its list of inputs
+// - target parameters (for raster passes)
+struct pl_pass_params {
+ enum pl_pass_type type;
+
+ // Input variables.
+ struct pl_var *variables;
+ int num_variables;
+
+ // Input descriptors.
+ struct pl_desc *descriptors;
+ int num_descriptors;
+
+ // Compile-time specialization constants.
+ struct pl_constant *constants;
+ int num_constants;
+
+ // Initial data for the specialization constants. Optional. If NULL,
+ // specialization constants receive the values from the shader text.
+ void *constant_data;
+
+ // Push constant region. Must be be a multiple of 4 <= limits.max_pushc_size
+ size_t push_constants_size;
+
+ // The shader text in GLSL. For PL_PASS_RASTER, this is interpreted
+ // as a fragment shader. For PL_PASS_COMPUTE, this is interpreted as
+ // a compute shader.
+ const char *glsl_shader;
+
+ // --- type==PL_PASS_RASTER only
+
+ // Describes the interpretation and layout of the vertex data.
+ enum pl_prim_type vertex_type;
+ struct pl_vertex_attrib *vertex_attribs;
+ int num_vertex_attribs;
+ size_t vertex_stride; // must be a multiple of limits.align_vertex_stride
+
+ // The vertex shader itself.
+ const char *vertex_shader;
+
+ // Target format. The format must support PL_FMT_CAP_RENDERABLE. The
+ // resulting pass may only be used on textures that have a format with a
+ // `pl_fmt.signature` compatible to this format.
+ pl_fmt target_format;
+
+ // Target blending mode. If this is NULL, blending is disabled. Otherwise,
+ // the `target_format` must also support PL_FMT_CAP_BLENDABLE.
+ const struct pl_blend_params *blend_params;
+
+ // If false, the target's existing contents will be discarded before the
+ // pass is run. (Semantically equivalent to calling pl_tex_invalidate
+ // before every pl_pass_run, but slightly more efficient)
+ //
+ // Specifying `blend_params` requires `load_target` to be true.
+ bool load_target;
+
+ // --- Deprecated / removed fields.
+ PL_DEPRECATED const uint8_t *cached_program; // Non-functional
+ PL_DEPRECATED size_t cached_program_len;
+};
+
+#define pl_pass_params(...) (&(struct pl_pass_params) { __VA_ARGS__ })
+
+// Conflates the following typical GPU API concepts:
+// - various kinds of shaders
+// - rendering pipelines
+// - descriptor sets, uniforms, other bindings
+// - all synchronization necessary
+// - the current values of all inputs
+//
+// Thread-safety: Unsafe
+typedef const struct pl_pass_t {
+ struct pl_pass_params params;
+} *pl_pass;
+
+// Compile a shader and create a render pass. This is a rare/expensive
+// operation and may take a significant amount of time, even if a cached
+// program is used. Returns NULL on failure.
+PL_API pl_pass pl_pass_create(pl_gpu gpu, const struct pl_pass_params *params);
+PL_API void pl_pass_destroy(pl_gpu gpu, pl_pass *pass);
+
+struct pl_desc_binding {
+ const void *object; // pl_* object with type corresponding to pl_desc_type
+
+ // For PL_DESC_SAMPLED_TEX, this can be used to configure the sampler.
+ enum pl_tex_address_mode address_mode;
+ enum pl_tex_sample_mode sample_mode;
+};
+
+struct pl_var_update {
+ int index; // index into params.variables[]
+ const void *data; // pointer to raw byte data corresponding to pl_var_host_layout()
+};
+
+struct pl_pass_run_params {
+ pl_pass pass;
+
+ // If present, the shader will be re-specialized with the new constants
+ // provided. This is a significantly cheaper operation than recompiling a
+ // brand new shader, but should still be avoided if possible.
+ //
+ // Leaving it as NULL re-uses the existing specialization values. Ignored
+ // if the shader has no specialization constants. Guaranteed to be a no-op
+ // if the values have not changed since the last invocation.
+ void *constant_data;
+
+ // This list only contains descriptors/variables which have changed
+ // since the previous invocation. All non-mentioned variables implicitly
+ // preserve their state from the last invocation.
+ struct pl_var_update *var_updates;
+ int num_var_updates;
+
+ // This list contains all descriptors used by this pass. It must
+ // always be filled, even if the descriptors haven't changed. The order
+ // must match that of pass->params.descriptors
+ struct pl_desc_binding *desc_bindings;
+
+ // The push constants for this invocation. This must always be set and
+ // fully defined for every invocation if params.push_constants_size > 0.
+ void *push_constants;
+
+ // An optional timer to report the approximate runtime of this shader pass
+ // invocation to. Note that this is only an approximation, since shaders
+ // may overlap their execution times and contend for GPU time.
+ pl_timer timer;
+
+ // --- pass->params.type==PL_PASS_RASTER only
+
+ // Target must be a 2D texture, `target->params.renderable` must be true,
+ // and `target->params.format->signature` must match the signature provided
+ // in `pass->params.target_format`.
+ //
+ // If the viewport or scissors are left blank, they are inferred from
+ // target->params.
+ //
+ // WARNING: Rendering to a *target that is being read from by the same
+ // shader is undefined behavior. In general, trying to bind the same
+ // resource multiple times to the same shader is undefined behavior.
+ pl_tex target;
+ pl_rect2d viewport; // screen space viewport (must be normalized)
+ pl_rect2d scissors; // target render scissors (must be normalized)
+
+ // Number of vertices to render
+ int vertex_count;
+
+ // Vertex data may be provided in one of two forms:
+ //
+ // 1. Drawing from host memory directly
+ const void *vertex_data;
+ // 2. Drawing from a vertex buffer (requires `vertex_buf->params.drawable`)
+ pl_buf vertex_buf;
+ size_t buf_offset;
+
+ // (Optional) Index data may be provided in the form given by `index_fmt`.
+ // These will be used for instanced rendering. Similar to vertex data, this
+ // can be provided in two forms:
+ // 1. From host memory
+ const void *index_data;
+ enum pl_index_format index_fmt;
+ // 2. From an index buffer (requires `index_buf->params.drawable`)
+ pl_buf index_buf;
+ size_t index_offset;
+ // Note: Drawing from an index buffer requires vertex data to also be
+ // present in buffer form, i.e. it's forbidden to mix `index_buf` with
+ // `vertex_data` (though vice versa is allowed).
+
+ // --- pass->params.type==PL_PASS_COMPUTE only
+
+ // Number of work groups to dispatch per dimension (X/Y/Z). Must be <= the
+ // corresponding index of limits.max_dispatch
+ int compute_groups[3];
+};
+
+#define pl_pass_run_params(...) (&(struct pl_pass_run_params) { __VA_ARGS__ })
+
+// Execute a render pass.
+PL_API void pl_pass_run(pl_gpu gpu, const struct pl_pass_run_params *params);
+
+// This is semantically a no-op, but it provides a hint that you want to flush
+// any partially queued up commands and begin execution. There is normally no
+// need to call this, because queued commands will always be implicitly flushed
+// whenever necessary to make forward progress on commands like `pl_buf_poll`,
+// or when submitting a frame to a swapchain for display. In fact, calling this
+// function can negatively impact performance, because some GPUs rely on being
+// able to re-order and modify queued commands in order to enable optimizations
+// retroactively.
+//
+// The only time this might be beneficial to call explicitly is if you're doing
+// lots of offline processing, i.e. you aren't rendering to a swapchain but to
+// textures that you download from again. In that case you should call this
+// function after each "work item" to ensure good parallelism between them.
+//
+// It's worth noting that this function may block if you're over-feeding the
+// GPU without waiting for existing results to finish.
+PL_API void pl_gpu_flush(pl_gpu gpu);
+
+// This is like `pl_gpu_flush` but also blocks until the GPU is fully idle
+// before returning. Using this in your rendering loop is seriously disadvised,
+// and almost never the right solution. The intended use case is for deinit
+// logic, where users may want to force the all pending GPU operations to
+// finish so they can clean up their state more easily.
+//
+// After this operation is called, it's guaranteed that all pending buffer
+// operations are complete - i.e. `pl_buf_poll` is guaranteed to return false.
+// It's also guaranteed that any outstanding timer query results are available.
+//
+// Note: If you only care about buffer operations, you can accomplish this more
+// easily by using `pl_buf_poll` with the timeout set to `UINT64_MAX`. But if
+// you have many buffers it may be more convenient to call this function
+// instead. The difference is that this function will also affect e.g. renders
+// to a `pl_swapchain`.
+PL_API void pl_gpu_finish(pl_gpu gpu);
+
+// Returns true if the GPU is considered to be in a "failed" state, which
+// during normal operation is typically the result of things like the device
+// being lost (due to e.g. power management).
+//
+// If this returns true, users *should* destroy and recreate the `pl_gpu`,
+// including all associated resources, via the appropriate mechanism.
+PL_API bool pl_gpu_is_failed(pl_gpu gpu);
+
+
+// Deprecated objects and functions:
+
+// A generic synchronization object intended for use with an external API. This
+// is not required when solely using libplacebo API functions, as all required
+// synchronisation is done internally. This comes in the form of a pair of
+// semaphores - one to synchronize access in each direction.
+//
+// Thread-safety: Unsafe
+typedef const struct pl_sync_t {
+ enum pl_handle_type handle_type;
+
+ // This handle is signalled by the `pl_gpu`, and waited on by the user. It
+ // fires when it is safe for the user to access the shared resource.
+ union pl_handle wait_handle;
+
+ // This handle is signalled by the user, and waited on by the `pl_gpu`. It
+ // must fire when the user has finished accessing the shared resource.
+ union pl_handle signal_handle;
+} *pl_sync;
+
+// Create a synchronization object. Returns NULL on failure.
+//
+// `handle_type` must be exactly *one* of `pl_gpu.export_caps.sync`, and
+// indicates which type of handle to generate for sharing this sync object.
+//
+// Deprecated in favor of API-specific semaphore creation operations such as
+// `pl_vulkan_sem_create`.
+PL_DEPRECATED PL_API pl_sync pl_sync_create(pl_gpu gpu, enum pl_handle_type handle_type);
+
+// Destroy a `pl_sync`. Note that this invalidates the externally imported
+// semaphores. Users should therefore make sure that all operations that
+// wait on or signal any of the semaphore have been fully submitted and
+// processed by the external API before destroying the `pl_sync`.
+//
+// Despite this, it's safe to destroy a `pl_sync` if the only pending
+// operations that involve it are internal to libplacebo.
+PL_DEPRECATED PL_API void pl_sync_destroy(pl_gpu gpu, pl_sync *sync);
+
+// Initiates a texture export operation, allowing a texture to be accessed by
+// an external API. Returns whether successful. After this operation
+// successfully returns, it is guaranteed that `sync->wait_handle` will
+// eventually be signalled. For APIs where this is relevant, the image layout
+// should be specified as "general", e.g. `GL_LAYOUT_GENERAL_EXT` for OpenGL.
+//
+// There is no corresponding "import" operation - the next operation that uses
+// a texture will implicitly import the texture. Valid API usage requires that
+// the user *must* submit a semaphore signal operation on `sync->signal_handle`
+// before doing so. Not doing so is undefined behavior and may very well
+// deadlock the calling process and/or the graphics card!
+//
+// Note that despite this restriction, it is always valid to call
+// `pl_tex_destroy`, even if the texture is in an exported state, without
+// having to signal the corresponding sync object first.
+//
+// Deprecated in favor of API-specific synchronization mechanisms such as
+// `pl_vulkan_hold/release_ex`.
+PL_DEPRECATED PL_API bool pl_tex_export(pl_gpu gpu, pl_tex tex, pl_sync sync);
+
+
+PL_API_END
+
+#endif // LIBPLACEBO_GPU_H_
diff --git a/src/include/libplacebo/log.h b/src/include/libplacebo/log.h
new file mode 100644
index 0000000..b24c931
--- /dev/null
+++ b/src/include/libplacebo/log.h
@@ -0,0 +1,113 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_LOG_H_
+#define LIBPLACEBO_LOG_H_
+
+#include <libplacebo/config.h>
+#include <libplacebo/common.h>
+
+PL_API_BEGIN
+
+// The log level associated with a given log message.
+enum pl_log_level {
+ PL_LOG_NONE = 0,
+ PL_LOG_FATAL, // results in total loss of function of a major component
+ PL_LOG_ERR, // serious error; may result in degraded function
+ PL_LOG_WARN, // warning; potentially bad, probably user-relevant
+ PL_LOG_INFO, // informational message, also potentially harmless errors
+ PL_LOG_DEBUG, // verbose debug message, informational
+ PL_LOG_TRACE, // very noisy trace of activity,, usually benign
+ PL_LOG_ALL = PL_LOG_TRACE,
+};
+
+struct pl_log_params {
+ // Logging callback. All messages, informational or otherwise, will get
+ // redirected to this callback. The logged messages do not include trailing
+ // newlines. Optional.
+ void (*log_cb)(void *log_priv, enum pl_log_level level, const char *msg);
+ void *log_priv;
+
+ // The current log level. Controls the level of message that will be
+ // redirected to the log callback. Setting this to PL_LOG_ALL means all
+ // messages will be forwarded, but doing so indiscriminately can result
+ // in increased CPU usage as it may enable extra debug paths based on the
+ // configured log level.
+ enum pl_log_level log_level;
+};
+
+#define pl_log_params(...) (&(struct pl_log_params) { __VA_ARGS__ })
+PL_API extern const struct pl_log_params pl_log_default_params;
+
+// Thread-safety: Safe
+//
+// Note: In any context in which `pl_log` is used, users may also pass NULL
+// to disable logging. In other words, NULL is a valid `pl_log`.
+typedef const struct pl_log_t {
+ struct pl_log_params params;
+} *pl_log;
+
+#define pl_log_glue1(x, y) x##y
+#define pl_log_glue2(x, y) pl_log_glue1(x, y)
+// Force a link error in the case of linking against an incompatible API
+// version.
+#define pl_log_create pl_log_glue2(pl_log_create_, PL_API_VER)
+// Creates a pl_log. `api_ver` is for historical reasons and ignored currently.
+// `params` defaults to `&pl_log_default_params` if left as NULL.
+//
+// Note: As a general rule, any `params` struct used as an argument to a
+// function need only live until the corresponding function returns.
+PL_API pl_log pl_log_create(int api_ver, const struct pl_log_params *params);
+
+// Destroy a `pl_log` object.
+//
+// Note: As a general rule, all `_destroy` functions take the pointer to the
+// object to free as their parameter. This pointer is overwritten by NULL
+// afterwards. Calling a _destroy function on &{NULL} is valid, but calling it
+// on NULL itself is invalid.
+PL_API void pl_log_destroy(pl_log *log);
+
+// Update the parameters of a `pl_log` without destroying it. This can be
+// used to change the log function, log context or log level retroactively.
+// `params` defaults to `&pl_log_default_params` if left as NULL.
+//
+// Returns the previous params, atomically.
+PL_API struct pl_log_params pl_log_update(pl_log log, const struct pl_log_params *params);
+
+// Like `pl_log_update` but only updates the log level, leaving the log
+// callback intact.
+//
+// Returns the previous log level, atomically.
+PL_API enum pl_log_level pl_log_level_update(pl_log log, enum pl_log_level level);
+
+// Two simple, stream-based loggers. You can use these as the log_cb. If you
+// also set log_priv to a FILE* (e.g. stdout or stderr) it will be printed
+// there; otherwise, it will be printed to stdout or stderr depending on the
+// log level.
+//
+// The version with colors will use ANSI escape sequences to indicate the log
+// level. The version without will use explicit prefixes.
+PL_API void pl_log_simple(void *stream, enum pl_log_level level, const char *msg);
+PL_API void pl_log_color(void *stream, enum pl_log_level level, const char *msg);
+
+// Backwards compatibility with older versions of libplacebo
+#define pl_context pl_log
+#define pl_context_params pl_log_params
+
+PL_API_END
+
+#endif // LIBPLACEBO_LOG_H_
diff --git a/src/include/libplacebo/meson.build b/src/include/libplacebo/meson.build
new file mode 100644
index 0000000..2f4631e
--- /dev/null
+++ b/src/include/libplacebo/meson.build
@@ -0,0 +1,6 @@
+sources += configure_file(
+ input: 'config.h.in',
+ output: 'config.h',
+ install_dir: get_option('includedir') / meson.project_name(),
+ configuration: conf_public,
+)
diff --git a/src/include/libplacebo/opengl.h b/src/include/libplacebo/opengl.h
new file mode 100644
index 0000000..46597b2
--- /dev/null
+++ b/src/include/libplacebo/opengl.h
@@ -0,0 +1,230 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_OPENGL_H_
+#define LIBPLACEBO_OPENGL_H_
+
+#include <string.h>
+
+#include <libplacebo/gpu.h>
+#include <libplacebo/swapchain.h>
+
+PL_API_BEGIN
+
+// Note on thread safety: The thread safety of `pl_opengl` and any associated
+// GPU objects follows the same thread safety rules as the underlying OpenGL
+// context. In other words, they must only be called from the thread the OpenGL
+// context is current on.
+
+typedef const struct pl_opengl_t {
+ pl_gpu gpu;
+
+ // Detected GL version
+ int major, minor;
+
+ // List of GL/EGL extensions, provided for convenience
+ const char * const *extensions;
+ int num_extensions;
+} *pl_opengl;
+
+static inline bool pl_opengl_has_ext(pl_opengl gl, const char *ext)
+{
+ for (int i = 0; i < gl->num_extensions; i++)
+ if (!strcmp(ext, gl->extensions[i]))
+ return true;
+ return false;
+}
+
+typedef void (*pl_voidfunc_t)(void);
+
+struct pl_opengl_params {
+ // Main gl*GetProcAddr function. This will be used to load all GL/EGL
+ // functions. Optional - if unspecified, libplacebo will default to an
+ // internal loading logic which should work on most platforms.
+ pl_voidfunc_t (*get_proc_addr_ex)(void *proc_ctx, const char *procname);
+ void *proc_ctx;
+
+ // Simpler API for backwards compatibility / convenience. (This one
+ // directly matches the signature of most gl*GetProcAddr library functions)
+ pl_voidfunc_t (*get_proc_addr)(const char *procname);
+
+ // Enable OpenGL debug report callbacks. May have little effect depending
+ // on whether or not the GL context was initialized with appropriate
+ // debugging enabled.
+ bool debug;
+
+ // Allow the use of (suspected) software rasterizers and renderers. These
+ // can be useful for debugging purposes, but normally, their use is
+ // undesirable when GPU-accelerated processing is expected.
+ bool allow_software;
+
+ // Restrict the maximum allowed GLSL version. (Mainly for testing)
+ int max_glsl_version;
+
+ // Optional. Required when importing/exporting dmabufs as textures.
+ void *egl_display;
+ void *egl_context;
+
+ // Optional callbacks to bind/release the OpenGL context on the current
+ // thread. If these are specified, then the resulting `pl_gpu` will have
+ // `pl_gpu_limits.thread_safe` enabled, and may therefore be used from any
+ // thread without first needing to bind the OpenGL context.
+ //
+ // If the user is re-using the same OpenGL context in non-libplacebo code,
+ // then these callbacks should include whatever synchronization is
+ // necessary to prevent simultaneous use between libplacebo and the user.
+ bool (*make_current)(void *priv);
+ void (*release_current)(void *priv);
+ void *priv;
+};
+
+// Default/recommended parameters
+#define pl_opengl_params(...) (&(struct pl_opengl_params) { __VA_ARGS__ })
+PL_API extern const struct pl_opengl_params pl_opengl_default_params;
+
+// Creates a new OpenGL renderer based on the given parameters. This will
+// internally use whatever platform-defined mechanism (WGL, X11, EGL) is
+// appropriate for loading the OpenGL function calls, so the user doesn't need
+// to pass in a `getProcAddress` callback. If `params` is left as NULL, it
+// defaults to `&pl_opengl_default_params`. The context must be active when
+// calling this function, and must remain active whenever calling any
+// libplacebo function on the resulting `pl_opengl` or `pl_gpu`.
+//
+// Note that creating multiple `pl_opengl` instances from the same OpenGL
+// context is undefined behavior.
+PL_API pl_opengl pl_opengl_create(pl_log log, const struct pl_opengl_params *params);
+
+// All resources allocated from the `pl_gpu` contained by this `pl_opengl` must
+// be explicitly destroyed by the user before calling `pl_opengl_destroy`.
+PL_API void pl_opengl_destroy(pl_opengl *gl);
+
+// For a `pl_gpu` backed by `pl_opengl`, this function can be used to retrieve
+// the underlying `pl_opengl`. Returns NULL for any other type of `gpu`.
+PL_API pl_opengl pl_opengl_get(pl_gpu gpu);
+
+struct pl_opengl_framebuffer {
+ // ID of the framebuffer, or 0 to use the context's default framebuffer.
+ int id;
+
+ // If true, then the framebuffer is assumed to be "flipped" relative to
+ // normal GL semantics, i.e. set this to `true` if the first pixel is the
+ // top left corner.
+ bool flipped;
+};
+
+struct pl_opengl_swapchain_params {
+ // Set this to the platform-specific function to swap buffers, e.g.
+ // glXSwapBuffers, eglSwapBuffers etc. This will be called internally by
+ // `pl_swapchain_swap_buffers`. Required, unless you never call that
+ // function.
+ void (*swap_buffers)(void *priv);
+
+ // Initial framebuffer description. This can be changed later on using
+ // `pl_opengl_swapchain_update_fb`.
+ struct pl_opengl_framebuffer framebuffer;
+
+ // Attempt forcing a specific latency. If this is nonzero, then
+ // `pl_swapchain_swap_buffers` will wait until fewer than N frames are "in
+ // flight" before returning. Setting this to a high number generally
+ // accomplished nothing, because the OpenGL driver typically limits the
+ // number of buffers on its own. But setting it to a low number like 2 or
+ // even 1 can reduce latency (at the cost of throughput).
+ int max_swapchain_depth;
+
+ // Arbitrary user pointer that gets passed to `swap_buffers` etc.
+ void *priv;
+};
+
+#define pl_opengl_swapchain_params(...) (&(struct pl_opengl_swapchain_params) { __VA_ARGS__ })
+
+// Creates an instance of `pl_swapchain` tied to the active context.
+// Note: Due to OpenGL semantics, users *must* call `pl_swapchain_resize`
+// before attempting to use this swapchain, otherwise calls to
+// `pl_swapchain_start_frame` will fail.
+PL_API pl_swapchain pl_opengl_create_swapchain(pl_opengl gl,
+ const struct pl_opengl_swapchain_params *params);
+
+// Update the framebuffer description. After calling this function, users
+// *must* call `pl_swapchain_resize` before attempting to use the swapchain
+// again, otherwise calls to `pl_swapchain_start_frame` will fail.
+PL_API void pl_opengl_swapchain_update_fb(pl_swapchain sw,
+ const struct pl_opengl_framebuffer *fb);
+
+struct pl_opengl_wrap_params {
+ // The GLuint texture object itself. Optional. If no texture is provided,
+ // then only the opaque framebuffer `fbo` will be wrapped, leaving the
+ // resulting `pl_tex` object with some operations (such as sampling) being
+ // unsupported.
+ unsigned int texture;
+
+ // The GLuint associated framebuffer. Optional. If this is not specified,
+ // then libplacebo will attempt creating a framebuffer from the provided
+ // texture object (if possible).
+ //
+ // Note: As a special case, if neither a texture nor an FBO are provided,
+ // this is equivalent to wrapping the OpenGL default framebuffer (id 0).
+ unsigned int framebuffer;
+
+ // The image's dimensions (unused dimensions must be 0)
+ int width;
+ int height;
+ int depth;
+
+ // Texture-specific fields:
+ //
+ // Note: These are only relevant if `texture` is provided.
+
+ // The GLenum for the texture target to use, e.g. GL_TEXTURE_2D. Optional.
+ // If this is left as 0, the target is inferred from the number of
+ // dimensions. Users may want to set this to something specific like
+ // GL_TEXTURE_EXTERNAL_OES depending on the nature of the texture.
+ unsigned int target;
+
+ // The texture's GLint sized internal format (e.g. GL_RGBA16F). Required.
+ int iformat;
+};
+
+#define pl_opengl_wrap_params(...) (&(struct pl_opengl_wrap_params) { __VA_ARGS__ })
+
+// Wraps an external OpenGL object into a `pl_tex` abstraction. Due to the
+// internally synchronized nature of OpenGL, no explicit synchronization
+// is needed between libplacebo `pl_tex_` operations, and host accesses to
+// the texture. Wrapping the same OpenGL texture multiple times is permitted.
+// Note that this function transfers no ownership.
+//
+// This wrapper can be destroyed by simply calling `pl_tex_destroy` on it,
+// which will *not* destroy the user-provided OpenGL texture or framebuffer.
+//
+// This function may fail, in which case it returns NULL.
+PL_API pl_tex pl_opengl_wrap(pl_gpu gpu, const struct pl_opengl_wrap_params *params);
+
+// Analogous to `pl_opengl_wrap`, this function takes any `pl_tex` (including
+// ones created by `pl_tex_create`) and unwraps it to expose the underlying
+// OpenGL texture to the user. Note that this function transfers no ownership,
+// i.e. the texture object and framebuffer shall not be destroyed by the user.
+//
+// Returns the OpenGL texture. `out_target` and `out_iformat` will be updated
+// to hold the target type and internal format, respectively. (Optional)
+//
+// For renderable/blittable textures, `out_fbo` will be updated to the ID of
+// the framebuffer attached to this texture, or 0 if there is none. (Optional)
+PL_API unsigned int pl_opengl_unwrap(pl_gpu gpu, pl_tex tex, unsigned int *out_target,
+ int *out_iformat, unsigned int *out_fbo);
+
+PL_API_END
+
+#endif // LIBPLACEBO_OPENGL_H_
diff --git a/src/include/libplacebo/options.h b/src/include/libplacebo/options.h
new file mode 100644
index 0000000..e40f5e7
--- /dev/null
+++ b/src/include/libplacebo/options.h
@@ -0,0 +1,201 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_OPTIONS_H_
+#define LIBPLACEBO_OPTIONS_H_
+
+#include <libplacebo/renderer.h>
+
+PL_API_BEGIN
+
+// High-level heap-managed struct containing storage for all options implied by
+// pl_render_params, including a high-level interface for serializing,
+// deserializing and interfacing with them in a programmatic way.
+
+typedef const struct pl_opt_t *pl_opt;
+typedef struct pl_options_t {
+ // Non-NULL `params.*_params` pointers must always point into this struct
+ struct pl_render_params params;
+
+ // Backing storage for all of the various rendering parameters. Whether
+ // or not these params are active is determined by whether or not
+ // `params.*_params` is set to this address or NULL.
+ struct pl_deband_params deband_params;
+ struct pl_sigmoid_params sigmoid_params;
+ struct pl_color_adjustment color_adjustment;
+ struct pl_peak_detect_params peak_detect_params;
+ struct pl_color_map_params color_map_params;
+ struct pl_dither_params dither_params;
+ struct pl_icc_params icc_params PL_DEPRECATED;
+ struct pl_cone_params cone_params;
+ struct pl_blend_params blend_params;
+ struct pl_deinterlace_params deinterlace_params;
+ struct pl_distort_params distort_params;
+
+ // Backing storage for "custom" scalers. `params.upscaler` etc. will
+ // always be a pointer either to a built-in pl_filter_config, or one of
+ // these structs. `name`, `description` and `allowed` will always be
+ // valid for the respective type of filter config.
+ struct pl_filter_config upscaler;
+ struct pl_filter_config downscaler;
+ struct pl_filter_config plane_upscaler;
+ struct pl_filter_config plane_downscaler;
+ struct pl_filter_config frame_mixer;
+} *pl_options;
+
+// Allocate a new set of render params, with internally backed storage for
+// all parameters. Initialized to an "empty" config (PL_RENDER_DEFAULTS),
+// equivalent to `&pl_render_fast_params`. To initialize the struct instead to
+// the recommended default parameters, use `pl_options_reset` with
+// `pl_render_default_params`.
+//
+// If `log` is provided, errors related to parsing etc. will be logged there.
+PL_API pl_options pl_options_alloc(pl_log log);
+PL_API void pl_options_free(pl_options *opts);
+
+// Resets all options to their default values from a given struct. If `preset`
+// is NULL, `opts` is instead reset back to the initial "empty" configuration,
+// with all options disabled, as if it was freshly allocated.
+//
+// Note: This function will also reset structs which were not included in
+// `preset`, such as any custom upscalers.
+PL_API void pl_options_reset(pl_options opts, const struct pl_render_params *preset);
+
+typedef const struct pl_opt_data_t {
+ // Original options struct.
+ pl_options opts;
+
+ // Triggering option for this callback invocation.
+ pl_opt opt;
+
+ // The raw data associated with this option. Always some pointer into
+ // `opts`. Note that only PL_OPT_BOOL, PL_OPT_INT and PL_OPT_FLOAT have
+ // a fixed representation, for other fields its usefulness is dubious.
+ const void *value;
+
+ // The underlying data, as a formatted, locale-invariant string. Lifetime
+ // is limited until the return of this callback.
+ const char *text;
+} *pl_opt_data;
+
+// Query a single option from `opts` by key, or NULL if none was found.
+// The resulting pointer is only valid until the next pl_options_* call.
+PL_API pl_opt_data pl_options_get(pl_options opts, const char *key);
+
+// Update an option from a formatted value string (see `pl_opt_data.text`).
+// This can be used for all type of options, even non-string ones. In this case,
+// `value` will be parsed according to the option type.
+//
+// Returns whether successful.
+PL_API bool pl_options_set_str(pl_options opts, const char *key, const char *value);
+
+// Programmatically iterate over options set in a `pl_options`, running the
+// provided callback on each entry.
+PL_API void pl_options_iterate(pl_options opts,
+ void (*cb)(void *priv, pl_opt_data data),
+ void *priv);
+
+// Serialize a `pl_options` structs to a comma-separated key/value string. The
+// returned string has a lifetime valid until either the next call to
+// `pl_options_save`, or until the `pl_options` is freed.
+PL_API const char *pl_options_save(pl_options opts);
+
+// Parse a `pl_options` struct from a key/value string, in standard syntax
+// "key1=value1,key2=value2,...", and updates `opts` with the new values.
+// Valid separators include whitespace, commas (,) and (semi)colons (:;).
+//
+// Returns true if no errors occurred.
+PL_API bool pl_options_load(pl_options opts, const char *str);
+
+// Helpers for interfacing with `opts->params.hooks`. Note that using any of
+// these helpers will overwrite the array by an internally managed pointer,
+// so care must be taken when combining them with external management of
+// this memory. Negative indices are possible and are counted relative to the
+// end of the list.
+//
+// Note: These hooks are *not* included in pl_options_save() and related.
+PL_API void pl_options_add_hook(pl_options opts, const struct pl_hook *hook);
+PL_API void pl_options_insert_hook(pl_options opts, const struct pl_hook *hook, int idx);
+PL_API void pl_options_remove_hook_at(pl_options opts, int idx);
+
+// Underlying options system and list
+//
+// Note: By necessity, this option list does not cover every single field
+// present in `pl_render_params`. In particular, fields like `info_callback`,
+// `lut` and `hooks` cannot be configured through the options system, as doing
+// so would require interop with C code or I/O. (However, see
+// `pl_options_add_hook` and related)
+
+enum pl_option_type {
+ // Accepts `yes/no`, `on/off`, `true/false` and variants
+ PL_OPT_BOOL,
+
+ // Parsed as human-readable locale-invariant (C) numbers, scientific
+ // notation accepted for floats
+ PL_OPT_INT,
+ PL_OPT_FLOAT,
+
+ // Parsed as a short string containing only alphanumerics and _-,
+ // corresponding to some name/identifier. Catch-all bucket for several
+ // other types of options, such as presets, struct pointers, and functions
+ //
+ // Note: These options do not correspond to actual strings in C, the
+ // underlying type of option will determine the values of `size` and
+ // corresponding interpretation of pointers.
+ PL_OPT_STRING,
+
+ PL_OPT_TYPE_COUNT,
+};
+
+struct pl_opt_t {
+ // Programmatic key uniquely identifying this option.
+ const char *key;
+
+ // Longer, human readable friendly name
+ const char *name;
+
+ // Data type of option, affects how it is parsed. This field is purely
+ // informative for the user, the actual implementation may vary.
+ enum pl_option_type type;
+
+ // Minimum/maximum value ranges for numeric options (int / float)
+ // If both are 0.0, these limits are disabled/ignored.
+ float min, max;
+
+ // If true, this option is considered deprecated and may be removed
+ // in the future.
+ bool deprecated;
+
+ // If true, this option is considered a 'preset' (read-only), which can
+ // be loaded but not saved. (The equivalent underlying options this preset
+ // corresponds to will be saved instead)
+ bool preset;
+
+ // Internal implementation details (for parsing/saving), opaque to user
+ const void *priv;
+};
+
+// A list of options, terminated by {0} for convenience
+PL_API extern const struct pl_opt_t pl_option_list[];
+PL_API extern const int pl_option_count; // excluding terminating {0}
+
+// Returns the `pl_option` associated with a given key, or NULL
+PL_API pl_opt pl_find_option(const char *key);
+
+PL_API_END
+
+#endif // LIBPLACEBO_OPTIONS_H_
diff --git a/src/include/libplacebo/renderer.h b/src/include/libplacebo/renderer.h
new file mode 100644
index 0000000..d2e01e4
--- /dev/null
+++ b/src/include/libplacebo/renderer.h
@@ -0,0 +1,847 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_RENDERER_H_
+#define LIBPLACEBO_RENDERER_H_
+
+#include <libplacebo/config.h>
+#include <libplacebo/colorspace.h>
+#include <libplacebo/filters.h>
+#include <libplacebo/gpu.h>
+#include <libplacebo/shaders/colorspace.h>
+#include <libplacebo/shaders/deinterlacing.h>
+#include <libplacebo/shaders/dithering.h>
+#include <libplacebo/shaders/film_grain.h>
+#include <libplacebo/shaders/icc.h>
+#include <libplacebo/shaders/lut.h>
+#include <libplacebo/shaders/sampling.h>
+#include <libplacebo/shaders/custom.h>
+#include <libplacebo/swapchain.h>
+
+PL_API_BEGIN
+
+// Thread-safety: Unsafe
+typedef struct pl_renderer_t *pl_renderer;
+
+// Enum values used in pl_renderer_errors_t as a bit positions for error flags
+enum pl_render_error {
+ PL_RENDER_ERR_NONE = 0,
+ PL_RENDER_ERR_FBO = 1 << 0,
+ PL_RENDER_ERR_SAMPLING = 1 << 1,
+ PL_RENDER_ERR_DEBANDING = 1 << 2,
+ PL_RENDER_ERR_BLENDING = 1 << 3,
+ PL_RENDER_ERR_OVERLAY = 1 << 4,
+ PL_RENDER_ERR_PEAK_DETECT = 1 << 5,
+ PL_RENDER_ERR_FILM_GRAIN = 1 << 6,
+ PL_RENDER_ERR_FRAME_MIXING = 1 << 7,
+ PL_RENDER_ERR_DEINTERLACING = 1 << 8,
+ PL_RENDER_ERR_ERROR_DIFFUSION = 1 << 9,
+ PL_RENDER_ERR_HOOKS = 1 << 10,
+ PL_RENDER_ERR_CONTRAST_RECOVERY = 1 << 11,
+};
+
+// Struct describing current renderer state, including internal processing errors,
+// as well as list of signatures of disabled hooks.
+struct pl_render_errors {
+ enum pl_render_error errors;
+ // List containing signatures of disabled hooks
+ const uint64_t *disabled_hooks;
+ int num_disabled_hooks;
+};
+
+// Creates a new renderer object, which is backed by a GPU context. This is a
+// high-level object that takes care of the rendering chain as a whole, from
+// the source textures to the finished frame.
+PL_API pl_renderer pl_renderer_create(pl_log log, pl_gpu gpu);
+PL_API void pl_renderer_destroy(pl_renderer *rr);
+
+// Returns current renderer state, see pl_render_errors.
+PL_API struct pl_render_errors pl_renderer_get_errors(pl_renderer rr);
+
+// Clears errors state of renderer. If `errors` is NULL, all render errors will
+// be cleared. Otherwise only selected errors/hooks will be cleared.
+// If `PL_RENDER_ERR_HOOKS` is set and `num_disabled_hooks` is 0, clear all hooks.
+// Otherwise only selected hooks will be cleard based on `disabled_hooks` array.
+PL_API void pl_renderer_reset_errors(pl_renderer rr,
+ const struct pl_render_errors *errors);
+
+enum pl_lut_type {
+ PL_LUT_UNKNOWN = 0,
+ PL_LUT_NATIVE, // applied to raw image contents (after fixing bit depth)
+ PL_LUT_NORMALIZED, // applied to normalized (HDR) RGB values
+ PL_LUT_CONVERSION, // LUT fully replaces color conversion
+
+ // Note: When using a PL_LUT_CONVERSION to replace the YUV->RGB conversion,
+ // `pl_render_params.color_adjustment` is no longer applied. Similarly,
+ // when using a PL_LUT_CONVERSION to replace the image->target color space
+ // conversion, `pl_render_params.color_map_params` are ignored.
+ //
+ // Note: For LUTs attached to the output frame, PL_LUT_CONVERSION should
+ // instead perform the inverse (RGB->native) conversion.
+ //
+ // Note: PL_LUT_UNKNOWN tries inferring the meaning of the LUT from the
+ // LUT's tagged metadata, and otherwise falls back to PL_LUT_NATIVE.
+};
+
+enum pl_render_stage {
+ PL_RENDER_STAGE_FRAME, // full frame redraws, for fresh/uncached frames
+ PL_RENDER_STAGE_BLEND, // the output blend pass (only for pl_render_image_mix)
+ PL_RENDER_STAGE_COUNT,
+};
+
+struct pl_render_info {
+ const struct pl_dispatch_info *pass; // information about the shader
+ enum pl_render_stage stage; // the associated render stage
+
+ // This specifies the chronological index of this pass within the frame and
+ // stage (starting at `index == 0`).
+ int index;
+
+ // For PL_RENDER_STAGE_BLEND, this specifies the number of frames
+ // being blended (since that results in a different shader).
+ int count;
+};
+
+// Represents the options used for rendering. These affect the quality of
+// the result.
+struct pl_render_params {
+ // Configures the algorithms used for upscaling and downscaling,
+ // respectively. If left as NULL, then libplacebo will only use inexpensive
+ // sampling (bilinear or nearest neighbour depending on the capabilities
+ // of the hardware / texture).
+ //
+ // Note: Setting `downscaler` to NULL also implies `skip_anti_aliasing`,
+ // since the built-in GPU sampling algorithms can't anti-alias.
+ //
+ // Note: If set to the same address as the built-in `pl_filter_bicubic`,
+ // `pl_filter_nearest` etc.; libplacebo will also use the more efficient
+ // direct sampling algorithm where possible without quality loss.
+ const struct pl_filter_config *upscaler;
+ const struct pl_filter_config *downscaler;
+
+ // If set, this overrides the value of `upscaler`/`downscaling` for
+ // subsampled (chroma) planes. These scalers are used whenever the size of
+ // multiple different `pl_plane`s in a single `pl_frame` differ, requiring
+ // adaptation when converting to/from RGB. Note that a value of NULL simply
+ // means "no override". To force built-in scaling explicitly, set this to
+ // `&pl_filter_bilinear`.
+ const struct pl_filter_config *plane_upscaler;
+ const struct pl_filter_config *plane_downscaler;
+
+ // The anti-ringing strength to apply to filters. See the equivalent option
+ // in `pl_sample_filter_params` for more information.
+ float antiringing_strength;
+
+ // Configures the algorithm used for frame mixing (when using
+ // `pl_render_image_mix`). Ignored otherwise. As a special requirement,
+ // this must be a filter config with `polar` set to false, since it's only
+ // used for 1D mixing and thus only 1D filters are compatible.
+ //
+ // If set to NULL, frame mixing is disabled, in which case
+ // `pl_render_image_mix` will use nearest-neighbour semantics. (Note that
+ // this still goes through the redraw cache, unless you also enable
+ // `skip_caching_single_frame`)
+ const struct pl_filter_config *frame_mixer;
+
+ // Configures the settings used to deband source textures. Leaving this as
+ // NULL disables debanding.
+ //
+ // Note: The `deband_params.grain` setting is automatically adjusted to
+ // prevent blowing up on HDR sources. The user need not account for this.
+ const struct pl_deband_params *deband_params;
+
+ // Configures the settings used to sigmoidize the image before upscaling.
+ // This is not always used. If NULL, disables sigmoidization.
+ const struct pl_sigmoid_params *sigmoid_params;
+
+ // Configures the color adjustment parameters used to decode the color.
+ // This can be used to apply additional artistic settings such as
+ // desaturation, etc. If NULL, defaults to &pl_color_adjustment_neutral.
+ const struct pl_color_adjustment *color_adjustment;
+
+ // Configures the settings used to detect the peak of the source content,
+ // for HDR sources. Has no effect on SDR content. If NULL, peak detection
+ // is disabled.
+ const struct pl_peak_detect_params *peak_detect_params;
+
+ // Configures the settings used to tone map from HDR to SDR, or from higher
+ // gamut to standard gamut content. If NULL, defaults to
+ // `&pl_color_map_default_params`.
+ const struct pl_color_map_params *color_map_params;
+
+ // Configures the settings used to dither to the output depth. Leaving this
+ // as NULL disables dithering.
+ const struct pl_dither_params *dither_params;
+
+ // Configures the error diffusion kernel to use for error diffusion
+ // dithering. If set, this will be used instead of `dither_params` whenever
+ // possible. Leaving this as NULL disables error diffusion.
+ const struct pl_error_diffusion_kernel *error_diffusion;
+
+ // Configures the settings used to simulate color blindness, if desired.
+ // If NULL, this feature is disabled.
+ const struct pl_cone_params *cone_params;
+
+ // Configures output blending. When rendering to the final target, the
+ // framebuffer contents will be blended using this blend mode. Requires
+ // that the target format has PL_FMT_CAP_BLENDABLE. NULL disables blending.
+ const struct pl_blend_params *blend_params;
+
+ // Configures the settings used to deinterlace frames (see
+ // `pl_frame.field`), if required.. If NULL, deinterlacing is "disabled",
+ // meaning interlaced frames are rendered as weaved frames instead.
+ //
+ // Note: As a consequence of how `pl_frame` represents individual fields,
+ // and especially when using the `pl_queue`, this will still result in
+ // frames being redundantly rendered twice. As such, it's highly
+ // recommended to, instead, fully disable deinterlacing by not marking
+ // source frames as interlaced in the first place.
+ const struct pl_deinterlace_params *deinterlace_params;
+
+ // If set, applies an extra distortion matrix to the image, after
+ // scaling and before presenting it to the screen. Can be used for e.g.
+ // fractional rotation.
+ //
+ // Note: The distortion canvas will be set to the size of `target->crop`,
+ // so this cannot effectively draw outside the specified target area,
+ // nor change the aspect ratio of the image.
+ const struct pl_distort_params *distort_params;
+
+ // List of custom user shaders / hooks.
+ // See <libplacebo/shaders/custom.h> for more information.
+ const struct pl_hook * const *hooks;
+ int num_hooks;
+
+ // Color mapping LUT. If present, this will be applied as part of the
+ // image being rendered, in normalized RGB space.
+ //
+ // Note: In this context, PL_LUT_NATIVE means "gamma light" and
+ // PL_LUT_NORMALIZED means "linear light". For HDR signals, normalized LUTs
+ // are scaled so 1.0 corresponds to the `pl_color_transfer_nominal_peak`.
+ //
+ // Note: A PL_LUT_CONVERSION fully replaces the color adaptation from
+ // `image` to `target`, including any tone-mapping (if necessary) and ICC
+ // profiles. It has the same representation as PL_LUT_NATIVE, so in this
+ // case the input and output are (respectively) non-linear light RGB.
+ const struct pl_custom_lut *lut;
+ enum pl_lut_type lut_type;
+
+ // If the image being rendered does not span the entire size of the target,
+ // it will be cleared explicitly using this background color (RGB). To
+ // disable this logic, set `skip_target_clearing`.
+ float background_color[3];
+ float background_transparency; // 0.0 for opaque, 1.0 for fully transparent
+ bool skip_target_clearing;
+
+ // If set to a value above 0.0, the output will be rendered with rounded
+ // corners, as if an alpha transparency mask had been applied. The value
+ // indicates the relative fraction of the side length to round - a value
+ // of 1.0 rounds the corners as much as possible.
+ float corner_rounding;
+
+ // If true, then transparent images will made opaque by painting them
+ // against a checkerboard pattern consisting of alternating colors. If both
+ // colors are left as {0}, they default respectively to 93% and 87% gray.
+ bool blend_against_tiles;
+ float tile_colors[2][3];
+ int tile_size;
+
+ // --- Performance / quality trade-off options:
+ // These should generally be left off where quality is desired, as they can
+ // degrade the result quite noticeably; but may be useful for older or
+ // slower hardware. Note that libplacebo will automatically disable
+ // advanced features on hardware where they are unsupported, regardless of
+ // these settings. So only enable them if you need a performance bump.
+
+ // Disables anti-aliasing on downscaling. This will result in moiré
+ // artifacts and nasty, jagged pixels when downscaling, except for some
+ // very limited special cases (e.g. bilinear downsampling to exactly 0.5x).
+ //
+ // Significantly speeds up downscaling with high downscaling ratios.
+ bool skip_anti_aliasing;
+
+ // Normally, when the size of the `target` used with `pl_render_image_mix`
+ // changes, or the render parameters are updated, the internal cache of
+ // mixed frames must be discarded in order to re-render all required
+ // frames. Setting this option to `true` will skip the cache invalidation
+ // and instead re-use the existing frames (with bilinear scaling to the new
+ // size if necessary), which comes at a quality loss shortly after a
+ // resize, but should make it much more smooth.
+ bool preserve_mixing_cache;
+
+ // --- Performance tuning / debugging options
+ // These may affect performance or may make debugging problems easier,
+ // but shouldn't have any effect on the quality.
+
+ // Normally, `pl_render_image_mix` will also push single frames through the
+ // mixer cache, in order to speed up re-draws. Enabling this option
+ // disables that logic, causing single frames to bypass the cache. (Though
+ // it will still read from, if they happen to already be cached)
+ bool skip_caching_single_frame;
+
+ // Disables linearization / sigmoidization before scaling. This might be
+ // useful when tracking down unexpected image artifacts or excessing
+ // ringing, but it shouldn't normally be necessary.
+ bool disable_linear_scaling;
+
+ // Forces the use of the "general" scaling algorithms even when using the
+ // special-cased built-in presets like `pl_filter_bicubic`. Basically, this
+ // disables the more efficient implementations in favor of the slower,
+ // general-purpose ones.
+ bool disable_builtin_scalers;
+
+ // Forces correction of subpixel offsets (using the configured `upscaler`).
+ bool correct_subpixel_offsets;
+
+ // Forces the use of dithering, even when rendering to 16-bit FBOs. This is
+ // generally pretty pointless because most 16-bit FBOs have high enough
+ // depth that rounding errors are below the human perception threshold,
+ // but this can be used to test the dither code.
+ bool force_dither;
+
+ // Disables the gamma-correct dithering logic which normally applies when
+ // dithering to low bit depths. No real use, outside of testing.
+ bool disable_dither_gamma_correction;
+
+ // Completely overrides the use of FBOs, as if there were no renderable
+ // texture format available. This disables most features.
+ bool disable_fbos;
+
+ // Use only low-bit-depth FBOs (8 bits). Note that this also implies
+ // disabling linear scaling and sigmoidization.
+ bool force_low_bit_depth_fbos;
+
+ // If this is true, all shaders will be generated as "dynamic" shaders,
+ // with any compile-time constants being replaced by runtime-adjustable
+ // values. This is generally a performance loss, but has the advantage of
+ // being able to freely change parameters without triggering shader
+ // recompilations.
+ //
+ // It's a good idea to enable while presenting configurable settings to the
+ // user, but it should be set to false once those values are "dialed in".
+ bool dynamic_constants;
+
+ // This callback is invoked for every pass successfully executed in the
+ // process of rendering a frame. Optional.
+ //
+ // Note: `info` is only valid until this function returns.
+ void (*info_callback)(void *priv, const struct pl_render_info *info);
+ void *info_priv;
+
+ // --- Deprecated/removed fields
+ bool allow_delayed_peak_detect PL_DEPRECATED; // moved to pl_peak_detect_params
+ const struct pl_icc_params *icc_params PL_DEPRECATED; // use pl_frame.icc
+ bool ignore_icc_profiles PL_DEPRECATED; // non-functional, just set pl_frame.icc to NULL
+ int lut_entries PL_DEPRECATED; // hard-coded as 256
+ float polar_cutoff PL_DEPRECATED; // hard-coded as 1e-3
+};
+
+// Bare minimum parameters, with no features enabled. This is the fastest
+// possible configuration, and should therefore be fine on any system.
+#define PL_RENDER_DEFAULTS \
+ .color_map_params = &pl_color_map_default_params, \
+ .color_adjustment = &pl_color_adjustment_neutral, \
+ .tile_colors = {{0.93, 0.93, 0.93}, \
+ {0.87, 0.87, 0.87}}, \
+ .tile_size = 32,
+
+#define pl_render_params(...) (&(struct pl_render_params) { PL_RENDER_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_render_params pl_render_fast_params;
+
+// This contains the default/recommended options for reasonable image quality,
+// while also not being too terribly slow. All of the *_params structs are
+// defaulted to the corresponding *_default_params, except for deband_params,
+// which is disabled by default.
+//
+// This should be fine on most integrated GPUs, but if it's too slow,
+// consider using `pl_render_fast_params` instead.
+PL_API extern const struct pl_render_params pl_render_default_params;
+
+// This contains a higher quality preset for better image quality at the cost
+// of quite a bit of performance. In addition to the settings implied by
+// `pl_render_default_params`, it enables debanding, sets the upscaler to
+// `pl_filter_ewa_lanczossharp`, and uses pl_*_high_quality_params structs where
+// available. This should only really be used with a discrete GPU and where
+// maximum image quality is desired.
+PL_API extern const struct pl_render_params pl_render_high_quality_params;
+
+#define PL_MAX_PLANES 4
+
+// High level description of a single slice of an image. This basically
+// represents a single 2D plane, with any number of components
+struct pl_plane {
+ // The texture underlying this plane. The texture must be 2D, and must
+ // have specific parameters set depending on what the plane is being used
+ // for (see `pl_render_image`).
+ pl_tex texture;
+
+ // The preferred behaviour when sampling outside of this texture. Optional,
+ // since the default (PL_TEX_ADDRESS_CLAMP) is very reasonable.
+ enum pl_tex_address_mode address_mode;
+
+ // Controls whether or not the `texture` will be considered flipped
+ // vertically with respect to the overall image dimensions. It's generally
+ // preferable to flip planes using this setting instead of the crop in
+ // cases where the flipping is the result of e.g. negative plane strides or
+ // flipped framebuffers (OpenGL).
+ //
+ // Note that any planar padding (due to e.g. size mismatch or misalignment
+ // of subsampled planes) is always at the physical end of the texture
+ // (highest y coordinate) - even if this bool is true. However, any
+ // subsampling shift (`shift_y`) is applied with respect to the flipped
+ // direction. This ensures the correct interpretation when e.g. vertically
+ // flipping 4:2:0 sources by flipping all planes.
+ bool flipped;
+
+ // Describes the number and interpretation of the components in this plane.
+ // This defines the mapping from component index to the canonical component
+ // order (RGBA, YCbCrA or XYZA). It's worth pointing out that this is
+ // completely separate from `texture->format.sample_order`. The latter is
+ // essentially irrelevant/transparent for the API user, since it just
+ // determines which order the texture data shows up as inside the GLSL
+ // shader; whereas this field controls the actual meaning of the component.
+ //
+ // Example; if the user has a plane with just {Y} and a plane with just
+ // {Cb Cr}, and a GPU that only supports bgra formats, you would still
+ // specify the component mapping as {0} and {1 2} respectively, even though
+ // the GPU is sampling the data in the order BGRA. Use -1 for "ignored"
+ // components.
+ int components; // number of relevant components
+ int component_mapping[4]; // semantic index of each component
+
+ // Controls the sample offset, relative to the "reference" dimensions. For
+ // an example of what to set here, see `pl_chroma_location_offset`. Note
+ // that this is given in unit of reference pixels. For a graphical example,
+ // imagine you have a 2x2 image with a 1x1 (subsampled) plane. Without any
+ // shift (0.0), the situation looks like this:
+ //
+ // X-------X X = reference pixel
+ // | | P = plane pixel
+ // | P |
+ // | |
+ // X-------X
+ //
+ // For 4:2:0 subsampling, this corresponds to PL_CHROMA_CENTER. If the
+ // shift_x was instead set to -0.5, the `P` pixel would be offset to the
+ // left by half the separation between the reference (`X` pixels), resulting
+ // in the following:
+ //
+ // X-------X X = reference pixel
+ // | | P = plane pixel
+ // P |
+ // | |
+ // X-------X
+ //
+ // For 4:2:0 subsampling, this corresponds to PL_CHROMA_LEFT.
+ //
+ // Note: It's recommended to fill this using `pl_chroma_location_offset` on
+ // the chroma planes.
+ float shift_x, shift_y;
+};
+
+enum pl_overlay_mode {
+ PL_OVERLAY_NORMAL = 0, // treat the texture as a normal, full-color texture
+ PL_OVERLAY_MONOCHROME, // treat the texture as a single-component alpha map
+ PL_OVERLAY_MODE_COUNT,
+};
+
+enum pl_overlay_coords {
+ PL_OVERLAY_COORDS_AUTO = 0, // equal to SRC/DST_FRAME, respectively
+ PL_OVERLAY_COORDS_SRC_FRAME, // relative to the raw src frame
+ PL_OVERLAY_COORDS_SRC_CROP, // relative to the src frame crop
+ PL_OVERLAY_COORDS_DST_FRAME, // relative to the raw dst frame
+ PL_OVERLAY_COORDS_DST_CROP, // relative to the dst frame crop
+ PL_OVERLAY_COORDS_COUNT,
+
+ // Note on rotations: If there is an end-to-end rotation between `src` and
+ // `dst`, then any overlays relative to SRC_FRAME or SRC_CROP will be
+ // rotated alongside the image, while overlays relative to DST_FRAME or
+ // DST_CROP will not.
+};
+
+struct pl_overlay_part {
+ pl_rect2df src; // source coordinate with respect to `pl_overlay.tex`
+ pl_rect2df dst; // target coordinates with respect to `pl_overlay.coords`
+
+ // If `mode` is PL_OVERLAY_MONOCHROME, then this specifies the color of
+ // this overlay part. The color is multiplied into the sampled texture's
+ // first channel.
+ float color[4];
+};
+
+// A struct representing an image overlay (e.g. for subtitles or on-screen
+// status messages, controls, ...)
+struct pl_overlay {
+ // The texture containing the backing data for overlay parts. Must have
+ // `params.sampleable` set.
+ pl_tex tex;
+
+ // This controls the coloring mode of this overlay.
+ enum pl_overlay_mode mode;
+
+ // Controls which coordinates this overlay is addressed relative to.
+ enum pl_overlay_coords coords;
+
+ // This controls the colorspace information for this overlay. The contents
+ // of the texture / the value of `color` are interpreted according to this.
+ struct pl_color_repr repr;
+ struct pl_color_space color;
+
+ // The number of parts for this overlay.
+ const struct pl_overlay_part *parts;
+ int num_parts;
+};
+
+// High-level description of a complete frame, including metadata and planes
+struct pl_frame {
+ // Each frame is split up into some number of planes, each of which may
+ // carry several components and be of any size / offset.
+ int num_planes;
+ struct pl_plane planes[PL_MAX_PLANES];
+
+ // For interlaced frames. If set, this `pl_frame` corresponds to a single
+ // field of the underlying source textures. `first_field` indicates which
+ // of these fields is ordered first in time. `prev` and `next` should point
+ // to the previous/next frames in the file, or NULL if there are none.
+ //
+ // Note: Setting these fields on the render target has no meaning and will
+ // be ignored.
+ enum pl_field field;
+ enum pl_field first_field;
+ const struct pl_frame *prev, *next;
+
+ // If set, will be called immediately before GPU access to this frame. This
+ // function *may* be used to, for example, perform synchronization with
+ // external APIs (e.g. `pl_vulkan_hold/release`). If your mapping requires
+ // a memcpy of some sort (e.g. pl_tex_transfer), users *should* instead do
+ // the memcpy up-front and avoid the use of these callbacks - because they
+ // might be called multiple times on the same frame.
+ //
+ // This function *may* arbitrarily mutate the `pl_frame`, but it *should*
+ // ideally only update `planes` - in particular, color metadata and so
+ // forth should be provided up-front as best as possible. Note that changes
+ // here will not be reflected back to the structs provided in the original
+ // `pl_render_*` call (e.g. via `pl_frame_mix`).
+ //
+ // Note: Unless dealing with interlaced frames, only one frame will ever be
+ // acquired at a time per `pl_render_*` call. So users *can* safely use
+ // this with, for example, hwdec mappers that can only map a single frame
+ // at a time. When using this with, for example, `pl_render_image_mix`,
+ // each frame to be blended is acquired and release in succession, before
+ // moving on to the next frame. For interlaced frames, the previous and
+ // next frames must also be acquired simultaneously.
+ bool (*acquire)(pl_gpu gpu, struct pl_frame *frame);
+
+ // If set, will be called after a plane is done being used by the GPU,
+ // *including* after any errors (e.g. `acquire` returning false).
+ void (*release)(pl_gpu gpu, struct pl_frame *frame);
+
+ // Color representation / encoding / semantics of this frame.
+ struct pl_color_repr repr;
+ struct pl_color_space color;
+
+ // Optional ICC profile associated with this frame.
+ pl_icc_object icc;
+
+ // Alternative to `icc`, this can be used in cases where allocating and
+ // tracking an pl_icc_object externally may be inconvenient. The resulting
+ // profile will be managed internally by the pl_renderer.
+ struct pl_icc_profile profile;
+
+ // Optional LUT associated with this frame.
+ const struct pl_custom_lut *lut;
+ enum pl_lut_type lut_type;
+
+ // The logical crop / rectangle containing the valid information, relative
+ // to the reference plane's dimensions (e.g. luma). Pixels outside of this
+ // rectangle will ostensibly be ignored, but note that this is not a hard
+ // guarantee. In particular, scaler filters may end up sampling outside of
+ // this crop. This rect may be flipped, and may be partially or wholly
+ // outside the bounds of the underlying textures. (Optional)
+ //
+ // Note that `pl_render_image` will map the input crop directly to the
+ // output crop, stretching and scaling as needed. If you wish to preserve
+ // the aspect ratio, use a dedicated function like pl_rect2df_aspect_copy.
+ pl_rect2df crop;
+
+ // Logical rotation of the image, with respect to the underlying planes.
+ // For example, if this is PL_ROTATION_90, then the image will be rotated
+ // to the right by 90° when mapping to `crop`. The actual position on-screen
+ // is unaffected, so users should ensure that the (rotated) aspect ratio
+ // matches the source. (Or use a helper like `pl_rect2df_aspect_set_rot`)
+ //
+ // Note: For `target` frames, this corresponds to a rotation of the
+ // display, for `image` frames, this corresponds to a rotation of the
+ // camera.
+ //
+ // So, as an example, target->rotation = PL_ROTATE_90 means the end user
+ // has rotated the display to the right by 90° (meaning rendering will be
+ // rotated 90° to the *left* to compensate), and image->rotation =
+ // PL_ROTATE_90 means the video provider has rotated the camera to the
+ // right by 90° (so rendering will be rotated 90° to the *right* to
+ // compensate).
+ pl_rotation rotation;
+
+ // A list of additional overlays associated with this frame. Note that will
+ // be rendered directly onto intermediate/cache frames, so changing any of
+ // these overlays may require flushing the renderer cache.
+ const struct pl_overlay *overlays;
+ int num_overlays;
+
+ // Note on subsampling and plane correspondence: All planes belonging to
+ // the same frame will only be stretched by an integer multiple (or inverse
+ // thereof) in order to match the reference dimensions of this image. For
+ // example, suppose you have an 8x4 image. A valid plane scaling would be
+ // 4x2 -> 8x4 or 4x4 -> 4x4, but not 6x4 -> 8x4. So if a 6x4 plane is
+ // given, then it would be treated like a cropped 8x4 plane (since 1.0 is
+ // the closest scaling ratio to the actual ratio of 1.3).
+ //
+ // For an explanation of why this makes sense, consider the relatively
+ // common example of a subsampled, oddly sized (e.g. jpeg) image. In such
+ // cases, for example a 35x23 image, the 4:2:0 subsampled chroma plane
+ // would have to end up as 17.5x11.5, which gets rounded up to 18x12 by
+ // implementations. So in this example, the 18x12 chroma plane would get
+ // treated by libplacebo as an oversized chroma plane - i.e. the plane
+ // would get sampled as if it was 17.5 pixels wide and 11.5 pixels large.
+
+ // Associated film grain data (see <libplacebo/shaders/film_grain.h>).
+ //
+ // Note: This is ignored for the `target` of `pl_render_image`, since
+ // un-applying grain makes little sense.
+ struct pl_film_grain_data film_grain;
+
+ // Ignored by libplacebo. May be useful for users.
+ void *user_data;
+};
+
+// Helper function to infer the chroma location offset for each plane in a
+// frame. This is equivalent to calling `pl_chroma_location_offset` on all
+// subsampled planes' shift_x/shift_y variables.
+PL_API void pl_frame_set_chroma_location(struct pl_frame *frame,
+ enum pl_chroma_location chroma_loc);
+
+// Fills in a `pl_frame` based on a swapchain frame's FBO and metadata.
+PL_API void pl_frame_from_swapchain(struct pl_frame *out_frame,
+ const struct pl_swapchain_frame *frame);
+
+// Helper function to determine if a frame is logically cropped or not. In
+// particular, this is useful in determining whether or not an output frame
+// needs to be cleared before rendering or not.
+PL_API bool pl_frame_is_cropped(const struct pl_frame *frame);
+
+// Helper function to reset a frame to a given RGB color. If the frame's
+// color representation is something other than RGB, the clear color will
+// be adjusted accordingly. `clear_color` should be non-premultiplied.
+PL_API void pl_frame_clear_rgba(pl_gpu gpu, const struct pl_frame *frame,
+ const float clear_color[4]);
+
+// Like `pl_frame_clear_rgba` but without an alpha channel.
+static inline void pl_frame_clear(pl_gpu gpu, const struct pl_frame *frame,
+ const float clear_color[3])
+{
+ const float clear_color_rgba[4] = { clear_color[0], clear_color[1], clear_color[2], 1.0 };
+ pl_frame_clear_rgba(gpu, frame, clear_color_rgba);
+}
+
+// Helper functions to return the fixed/inferred pl_frame parameters used
+// for rendering internally. Mutates `image` and `target` in-place to hold
+// the modified values, which are what will actually be used for rendering.
+//
+// This currently includes:
+// - Defaulting all missing pl_color_space/repr parameters
+// - Coalescing all rotation to the target
+// - Rounding and clamping the target crop to pixel boundaries and adjusting the
+// image crop correspondingly
+//
+// Note: This is idempotent and does not generally alter the effects of a
+// subsequent `pl_render_image` on the same pl_frame pair. (But see the
+// following warning)
+//
+// Warning: This does *not* call pl_frame.acquire/release, and so the returned
+// metadata *may* be incorrect if the acquire callback mutates the pl_frame in
+// nontrivial ways, in particular the crop and color space fields.
+PL_API void pl_frames_infer(pl_renderer rr, struct pl_frame *image,
+ struct pl_frame *target);
+
+
+// Render a single image to a target using the given parameters. This is
+// fully dynamic, i.e. the params can change at any time. libplacebo will
+// internally detect and flush whatever caches are invalidated as a result of
+// changing colorspace, size etc.
+//
+// Required plane capabilities:
+// - Planes in `image` must be `sampleable`
+// - Planes in `target` must be `renderable`
+//
+// Recommended plane capabilities: (Optional, but good for performance)
+// - Planes in `image` should have `sample_mode` PL_TEX_SAMPLE_LINEAR
+// - Planes in `target` should be `storable`
+// - Planes in `target` should have `blit_dst`
+//
+// Note on lifetime: Once this call returns, the passed structures may be
+// freely overwritten or discarded by the caller, even the referenced
+// `pl_tex` objects may be freely reused.
+//
+// Note: `image` may be NULL, in which case `target.overlays` will still be
+// rendered, but nothing else.
+PL_API bool pl_render_image(pl_renderer rr, const struct pl_frame *image,
+ const struct pl_frame *target,
+ const struct pl_render_params *params);
+
+// Flushes the internal state of this renderer. This is normally not needed,
+// even if the image parameters, colorspace or target configuration change,
+// since libplacebo will internally detect such circumstances and recreate
+// outdated resources automatically. Doing this explicitly *may* be useful to
+// purge some state related to things like HDR peak detection or frame mixing,
+// so calling it is a good idea if the content source is expected to change
+// dramatically (e.g. when switching to a different file).
+PL_API void pl_renderer_flush_cache(pl_renderer rr);
+
+// Mirrors `pl_get_detected_hdr_metadata`, giving you the current internal peak
+// detection HDR metadata (when peak detection is active). Returns false if no
+// information is available (e.g. not HDR source, peak detection disabled).
+PL_API bool pl_renderer_get_hdr_metadata(pl_renderer rr,
+ struct pl_hdr_metadata *metadata);
+
+// Represents a mixture of input frames, distributed temporally.
+//
+// NOTE: Frames must be sorted by timestamp, i.e. `timestamps` must be
+// monotonically increasing.
+struct pl_frame_mix {
+ // The number of frames in this mixture. The number of frames should be
+ // sufficient to meet the needs of the configured frame mixer. See the
+ // section below for more information.
+ //
+ // If the number of frames is 0, this call will be equivalent to
+ // `pl_render_image` with `image == NULL`.
+ int num_frames;
+
+ // A list of the frames themselves. The frames can have different
+ // colorspaces, configurations of planes, or even sizes.
+ //
+ // Note: This is a list of pointers, to avoid users having to copy
+ // around `pl_frame` structs when re-organizing this array.
+ const struct pl_frame **frames;
+
+ // A list of unique signatures, one for each frame. These are used to
+ // identify frames across calls to this function, so it's crucial that they
+ // be both unique per-frame but also stable across invocations of
+ // `pl_render_frame_mix`.
+ const uint64_t *signatures;
+
+ // A list of relative timestamps for each frame. These are relative to the
+ // time of the vsync being drawn, i.e. this function will render the frame
+ // that will be made visible at timestamp 0.0. The values are expected to
+ // be normalized such that a separation of 1.0 corresponds to roughly one
+ // nominal source frame duration. So a constant framerate video file will
+ // always have timestamps like e.g. {-2.3, -1.3, -0.3, 0.7, 1.7, 2.7},
+ // using an example radius of 3.
+ //
+ // In cases where the framerate is variable (e.g. VFR video), the choice of
+ // what to scale to use can be difficult to answer. A typical choice would
+ // be either to use the canonical (container-tagged) framerate, or the
+ // highest momentary framerate, as a reference. If all else fails, you
+ // could also use the display's framerate.
+ //
+ // Note: This function assumes zero-order-hold semantics, i.e. the frame at
+ // timestamp 0.7 is intended to remain visible until timestamp 1.7, when
+ // the next frame replaces it.
+ const float *timestamps;
+
+ // The duration for which the vsync being drawn will be held, using the
+ // same scale as `timestamps`. If the display has an unknown or variable
+ // frame-rate (e.g. Adaptive Sync), then you're probably better off not
+ // using this function and instead just painting the frames directly using
+ // `pl_render_frame` at the correct PTS.
+ //
+ // As an example, if `vsync_duration` is 0.4, then it's assumed that the
+ // vsync being painted is visible for the period [0.0, 0.4].
+ float vsync_duration;
+
+ // Explanation of the frame mixing radius: The algorithm chosen in
+ // `pl_render_params.frame_mixer` has a canonical radius equal to
+ // `pl_filter_config.kernel->radius`. This means that the frame mixing
+ // algorithm will (only) need to consult all of the frames that have a
+ // distance within the interval [-radius, radius]. As such, the user should
+ // include all such frames in `frames`, but may prune or omit frames that
+ // lie outside it.
+ //
+ // The built-in frame mixing (`pl_render_params.frame_mixer == NULL`) has
+ // no concept of radius, it just always needs access to the "current" and
+ // "next" frames.
+};
+
+// Helper function to calculate the base frame mixing radius.
+//
+// Note: When the source FPS exceeds the display FPS, this radius must be
+// increased by the corresponding ratio.
+static inline float pl_frame_mix_radius(const struct pl_render_params *params)
+{
+ // For backwards compatibility, allow !frame_mixer->kernel
+ if (!params->frame_mixer || !params->frame_mixer->kernel)
+ return 0.0;
+
+ return params->frame_mixer->kernel->radius;
+}
+
+// Find closest frame to current PTS by zero-order hold semantics, or NULL.
+PL_API const struct pl_frame *pl_frame_mix_current(const struct pl_frame_mix *mix);
+
+// Find closest frame to current PTS by nearest neighbour semantics, or NULL.
+PL_API const struct pl_frame *pl_frame_mix_nearest(const struct pl_frame_mix *mix);
+
+// Render a mixture of images to the target using the given parameters. This
+// functions much like a generalization of `pl_render_image`, for when the API
+// user has more control over the frame queue / vsync loop, and can provide a
+// few frames from the past and future + timestamp information.
+//
+// This allows libplacebo to perform rudimentary frame mixing / interpolation,
+// in order to eliminate judder artifacts typically associated with
+// source/display frame rate mismatch.
+PL_API bool pl_render_image_mix(pl_renderer rr, const struct pl_frame_mix *images,
+ const struct pl_frame *target,
+ const struct pl_render_params *params);
+
+// Analog of `pl_frame_infer` corresponding to `pl_render_image_mix`. This
+// function will *not* mutate the frames contained in `mix`, and instead
+// return an adjusted copy of the "reference" frame for that image mix in
+// `out_refimage`, or {0} if the mix is empty.
+PL_API void pl_frames_infer_mix(pl_renderer rr, const struct pl_frame_mix *mix,
+ struct pl_frame *target, struct pl_frame *out_ref);
+
+// Backwards compatibility with old filters API, may be deprecated.
+// Redundant with pl_filter_configs and masking `allowed` for
+// PL_FILTER_SCALING and PL_FILTER_FRAME_MIXING respectively.
+
+// A list of recommended frame mixer presets, terminated by {0}
+PL_API extern const struct pl_filter_preset pl_frame_mixers[];
+PL_API extern const int pl_num_frame_mixers; // excluding trailing {0}
+
+// A list of recommended scaler presets, terminated by {0}. This is almost
+// equivalent to `pl_filter_presets` with the exception of including extra
+// built-in filters that don't map to the `pl_filter` architecture.
+PL_API extern const struct pl_filter_preset pl_scale_filters[];
+PL_API extern const int pl_num_scale_filters; // excluding trailing {0}
+
+// Deprecated in favor of `pl_cache_save/pl_cache_load` on the `pl_cache`
+// associated with the `pl_gpu` this renderer is using.
+PL_DEPRECATED PL_API size_t pl_renderer_save(pl_renderer rr, uint8_t *out_cache);
+PL_DEPRECATED PL_API void pl_renderer_load(pl_renderer rr, const uint8_t *cache);
+
+PL_API_END
+
+#endif // LIBPLACEBO_RENDERER_H_
diff --git a/src/include/libplacebo/shaders.h b/src/include/libplacebo/shaders.h
new file mode 100644
index 0000000..b8046be
--- /dev/null
+++ b/src/include/libplacebo/shaders.h
@@ -0,0 +1,273 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_H_
+#define LIBPLACEBO_SHADERS_H_
+
+// This function defines the "direct" interface to libplacebo's GLSL shaders,
+// suitable for use in contexts where the user controls GLSL shader compilation
+// but wishes to include functions generated by libplacebo as part of their
+// own rendering process. This API is normally not used for operation with
+// libplacebo's higher-level constructs such as `pl_dispatch` or `pl_renderer`.
+
+#include <libplacebo/gpu.h>
+
+PL_API_BEGIN
+
+// Thread-safety: Unsafe
+typedef struct pl_shader_t *pl_shader;
+
+struct pl_shader_params {
+ // The `id` represents an abstract identifier for the shader, to avoid
+ // collisions with other shaders being used as part of the same larger,
+ // overarching shader. This is relevant for users which want to combine
+ // multiple `pl_shader` objects together, in which case all `pl_shader`
+ // objects should have a unique `id`.
+ uint8_t id;
+
+ // If `gpu` is non-NULL, then this `gpu` will be used to create objects
+ // such as textures and buffers, or check for required capabilities, for
+ // operations which depend on either of those. This is fully optional, i.e.
+ // these GLSL primitives are designed to be used without a dependency on
+ // `gpu` wherever possible - however, some features may not work, and will
+ // be disabled even if requested.
+ pl_gpu gpu;
+
+ // The `index` represents an abstract frame index, which shaders may use
+ // internally to do things like temporal dithering or seeding PRNGs. If the
+ // user does not care about temporal dithering/debanding, or wants
+ // deterministic rendering, this may safely be left as 0. Otherwise, it
+ // should be incremented by 1 on successive frames.
+ uint8_t index;
+
+ // If `glsl.version` is nonzero, then this structure will be used to
+ // determine the effective GLSL mode and capabilities. If `gpu` is also
+ // set, then this overrides `gpu->glsl`.
+ struct pl_glsl_version glsl;
+
+ // If this is true, all constants in the shader will be replaced by
+ // dynamic variables. This is mainly useful to avoid recompilation for
+ // shaders which expect to have their values change constantly.
+ bool dynamic_constants;
+};
+
+#define pl_shader_params(...) (&(struct pl_shader_params) { __VA_ARGS__ })
+
+// Creates a new, blank, mutable pl_shader object.
+//
+// Note: Rather than allocating and destroying many shaders, users are
+// encouraged to reuse them (using `pl_shader_reset`) for efficiency.
+PL_API pl_shader pl_shader_alloc(pl_log log, const struct pl_shader_params *params);
+
+// Frees a pl_shader and all resources associated with it.
+PL_API void pl_shader_free(pl_shader *sh);
+
+// Resets a pl_shader to a blank slate, without releasing internal memory.
+// If you're going to be re-generating shaders often, this function will let
+// you skip the re-allocation overhead.
+PL_API void pl_shader_reset(pl_shader sh, const struct pl_shader_params *params);
+
+// Returns whether or not a shader is in a "failed" state. Trying to modify a
+// shader in illegal ways (e.g. signature mismatch) will result in the shader
+// being marked as "failed". Since most pl_shader_ operations have a void
+// return type, the user can use this function to figure out whether a specific
+// shader operation has failed or not. This function is somewhat redundant
+// since `pl_shader_finalize` will also return NULL in this case.
+PL_API bool pl_shader_is_failed(const pl_shader sh);
+
+// Returns whether or not a pl_shader needs to be run as a compute shader. This
+// will never be the case unless the `pl_glsl_version` this `pl_shader` was
+// created using has `compute` support enabled.
+PL_API bool pl_shader_is_compute(const pl_shader sh);
+
+// Returns whether or not the shader has any particular output size
+// requirements. Some shaders, in particular those that sample from other
+// textures, have specific output size requirements which need to be respected
+// by the caller. If this is false, then the shader is compatible with every
+// output size. If true, the size requirements are stored into *w and *h.
+PL_API bool pl_shader_output_size(const pl_shader sh, int *w, int *h);
+
+// Indicates the type of signature that is associated with a shader result.
+// Every shader result defines a function that may be called by the user, and
+// this enum indicates the type of value that this function takes and/or
+// returns.
+//
+// Which signature a shader ends up with depends on the type of operation being
+// performed by a shader fragment, as determined by the user's calls. See below
+// for more information.
+enum pl_shader_sig {
+ PL_SHADER_SIG_NONE = 0, // no input / void output
+ PL_SHADER_SIG_COLOR, // vec4 color (normalized so that 1.0 is the ref white)
+
+ // The following are only valid as input signatures:
+ PL_SHADER_SIG_SAMPLER, // (gsampler* src_tex, vecN tex_coord) pair,
+ // specifics depend on how the shader was generated
+};
+
+// Structure encapsulating information about a shader. This is internally
+// refcounted, to allow moving it around without having to create deep copies.
+typedef const struct pl_shader_info_t {
+ // A copy of the parameters used to create the shader.
+ struct pl_shader_params params;
+
+ // A list of friendly names for the semantic operations being performed by
+ // this shader, e.g. "color decoding" or "debanding".
+ const char **steps;
+ int num_steps;
+
+ // As a convenience, this contains a pretty-printed version of the
+ // above list, with entries tallied and separated by commas
+ const char *description;
+} *pl_shader_info;
+
+PL_API pl_shader_info pl_shader_info_ref(pl_shader_info info);
+PL_API void pl_shader_info_deref(pl_shader_info *info);
+
+// Represents a finalized shader fragment. This is not a complete shader, but a
+// collection of raw shader text together with description of the input
+// attributes, variables and vertices it expects to be available.
+struct pl_shader_res {
+ // Descriptive information about the shader. Note that this reference is
+ // attached to the shader itself - the user does not need to manually ref
+ // or deref `info` unless they wish to move it elsewhere.
+ pl_shader_info info;
+
+ // The shader text, as literal GLSL. This will always be a function
+ // definition, such that the the function with the indicated name and
+ // signature may be called by the user.
+ const char *glsl;
+ const char *name;
+ enum pl_shader_sig input; // what the function expects
+ enum pl_shader_sig output; // what the function returns
+
+ // For compute shaders (pl_shader_is_compute), this indicates the requested
+ // work group size. Otherwise, both fields are 0. The interpretation of
+ // these work groups is that they're tiled across the output image.
+ int compute_group_size[2];
+
+ // If this pass is a compute shader, this field indicates the shared memory
+ // size requirements for this shader pass.
+ size_t compute_shmem;
+
+ // A set of input vertex attributes needed by this shader fragment.
+ const struct pl_shader_va *vertex_attribs;
+ int num_vertex_attribs;
+
+ // A set of input variables needed by this shader fragment.
+ const struct pl_shader_var *variables;
+ int num_variables;
+
+ // A list of input descriptors needed by this shader fragment,
+ const struct pl_shader_desc *descriptors;
+ int num_descriptors;
+
+ // A list of compile-time constants used by this shader fragment.
+ const struct pl_shader_const *constants;
+ int num_constants;
+
+ // --- Deprecated fields (see `info`)
+ struct pl_shader_params params PL_DEPRECATED;
+ const char **steps PL_DEPRECATED;
+ int num_steps PL_DEPRECATED;
+ const char *description PL_DEPRECATED;
+};
+
+// Represents a vertex attribute. The four values will be bound to the four
+// corner vertices respectively, in row-wise order starting from the top left:
+// data[0] data[1]
+// data[2] data[3]
+struct pl_shader_va {
+ struct pl_vertex_attrib attr; // VA type, excluding `offset` and `location`
+ const void *data[4];
+};
+
+// Represents a bound shared variable / descriptor
+struct pl_shader_var {
+ struct pl_var var; // the underlying variable description
+ const void *data; // the raw data (as per `pl_var_host_layout`)
+ bool dynamic; // if true, the value is expected to change frequently
+};
+
+struct pl_buffer_var {
+ struct pl_var var;
+ struct pl_var_layout layout;
+};
+
+typedef uint16_t pl_memory_qualifiers;
+enum {
+ PL_MEMORY_COHERENT = 1 << 0, // supports synchronization across shader invocations
+ PL_MEMORY_VOLATILE = 1 << 1, // all writes are synchronized automatically
+
+ // Note: All descriptors are also implicitly assumed to have the 'restrict'
+ // memory qualifier. There is currently no way to override this behavior.
+};
+
+struct pl_shader_desc {
+ struct pl_desc desc; // descriptor type, excluding `int binding`
+ struct pl_desc_binding binding; // contents of the descriptor binding
+
+ // For PL_DESC_BUF_UNIFORM/STORAGE, this specifies the layout of the
+ // variables contained by a buffer. Ignored for the other descriptor types
+ struct pl_buffer_var *buffer_vars;
+ int num_buffer_vars;
+
+ // For storage images and buffers, this specifies additional memory
+ // qualifiers on the descriptor. It's highly recommended to always use
+ // at least PL_MEMORY_RESTRICT. Ignored for other descriptor types.
+ pl_memory_qualifiers memory;
+};
+
+// Represents a compile-time constant. This can be lowered to a specialization
+// constant to support cheaper recompilations.
+struct pl_shader_const {
+ enum pl_var_type type;
+ const char *name;
+ const void *data;
+
+ // If true, this constant *must* be a compile-time constant, which
+ // basically just overrides `pl_shader_params.dynamic_constants`. Useful
+ // for constants which will serve as inputs to e.g. array sizes.
+ bool compile_time;
+};
+
+// Finalize a pl_shader. It is no longer mutable at this point, and any further
+// attempts to modify it result in an error. (Functions which take a `const
+// pl_shader` argument do not modify the shader and may be freely
+// called on an already-finalized shader)
+//
+// The returned pl_shader_res is bound to the lifetime of the pl_shader - and
+// will only remain valid until the pl_shader is freed or reset. This function
+// may be called multiple times, and will produce the same result each time.
+//
+// This function will return NULL if the shader is considered to be in a
+// "failed" state (see pl_shader_is_failed).
+PL_API const struct pl_shader_res *pl_shader_finalize(pl_shader sh);
+
+// Shader objects represent abstract resources that shaders need to manage in
+// order to ensure their operation. This could include shader storage buffers,
+// generated lookup textures, or other sorts of configured state. The body
+// of a shader object is fully opaque; but the user is in charge of cleaning up
+// after them and passing them to the right shader passes.
+//
+// Note: pl_shader_obj objects must be initialized to NULL by the caller.
+typedef struct pl_shader_obj_t *pl_shader_obj;
+
+PL_API void pl_shader_obj_destroy(pl_shader_obj *obj);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_H_
diff --git a/src/include/libplacebo/shaders/colorspace.h b/src/include/libplacebo/shaders/colorspace.h
new file mode 100644
index 0000000..ead0958
--- /dev/null
+++ b/src/include/libplacebo/shaders/colorspace.h
@@ -0,0 +1,381 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_COLORSPACE_H_
+#define LIBPLACEBO_SHADERS_COLORSPACE_H_
+
+// Color space transformation shaders. These all input and output a color
+// value (PL_SHADER_SIG_COLOR).
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/gamut_mapping.h>
+#include <libplacebo/tone_mapping.h>
+#include <libplacebo/shaders.h>
+
+// For backwards compatibility
+#include <libplacebo/shaders/dithering.h>
+
+PL_API_BEGIN
+
+// Transform the input color, in its given representation, to ensure
+// compatibility with the indicated alpha mode. Mutates `repr` to reflect the
+// change. Note that this is a no-op if the input is PL_ALPHA_UNKNOWN.
+PL_API void pl_shader_set_alpha(pl_shader sh, struct pl_color_repr *repr,
+ enum pl_alpha_mode mode);
+
+// Colorspace reshaping for PL_COLOR_SYSTEM_DOLBYVISION. Note that this is done
+// automatically by `pl_shader_decode_color` for PL_COLOR_SYSTEM_DOLBYVISION.
+PL_API void pl_shader_dovi_reshape(pl_shader sh, const struct pl_dovi_metadata *data);
+
+// Decode the color into normalized RGB, given a specified color_repr. This
+// also takes care of additional pre- and post-conversions requires for the
+// "special" color systems (XYZ, BT.2020-C, etc.). If `params` is left as NULL,
+// it defaults to &pl_color_adjustment_neutral.
+//
+// Note: This function always returns PC-range RGB with independent alpha.
+// It mutates the pl_color_repr to reflect the change.
+//
+// Note: For DCDM XYZ decoding output is linear
+PL_API void pl_shader_decode_color(pl_shader sh, struct pl_color_repr *repr,
+ const struct pl_color_adjustment *params);
+
+// Encodes a color from normalized, PC-range, independent alpha RGB into a
+// given representation. That is, this performs the inverse operation of
+// `pl_shader_decode_color` (sans color adjustments).
+//
+// Note: For DCDM XYZ encoding input is expected to be linear
+PL_API void pl_shader_encode_color(pl_shader sh, const struct pl_color_repr *repr);
+
+// Linearize (expand) `vec4 color`, given a specified color space. In essence,
+// this corresponds to the ITU-R EOTF.
+//
+// Note: Unlike the ITU-R EOTF, it never includes the OOTF - even for systems
+// where the EOTF includes the OOTF (such as HLG).
+PL_API void pl_shader_linearize(pl_shader sh, const struct pl_color_space *csp);
+
+// Delinearize (compress), given a color space as output. This loosely
+// corresponds to the inverse EOTF (not the OETF) in ITU-R terminology, again
+// assuming a reference monitor.
+PL_API void pl_shader_delinearize(pl_shader sh, const struct pl_color_space *csp);
+
+struct pl_sigmoid_params {
+ // The center (bias) of the sigmoid curve. Must be between 0.0 and 1.0.
+ // If left as NULL, defaults to 0.75
+ float center;
+
+ // The slope (steepness) of the sigmoid curve. Must be between 1.0 and 20.0.
+ // If left as NULL, defaults to 6.5.
+ float slope;
+};
+
+#define PL_SIGMOID_DEFAULTS \
+ .center = 0.75, \
+ .slope = 6.50,
+
+#define pl_sigmoid_params(...) (&(struct pl_sigmoid_params) { PL_SIGMOID_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_sigmoid_params pl_sigmoid_default_params;
+
+// Applies a sigmoidal color transform to all channels. This helps avoid
+// ringing artifacts during upscaling by bringing the color information closer
+// to neutral and away from the extremes. If `params` is NULL, it defaults to
+// &pl_sigmoid_default_params.
+//
+// Warning: This function clamps the input to the interval [0,1]; and as such
+// it should *NOT* be used on already-decoded high-dynamic range content.
+PL_API void pl_shader_sigmoidize(pl_shader sh, const struct pl_sigmoid_params *params);
+
+// This performs the inverse operation to `pl_shader_sigmoidize`.
+PL_API void pl_shader_unsigmoidize(pl_shader sh, const struct pl_sigmoid_params *params);
+
+struct pl_peak_detect_params {
+ // Smoothing coefficient for the detected values. This controls the time
+ // parameter (tau) of an IIR low pass filter. In other words, it represent
+ // the cutoff period (= 1 / cutoff frequency) in frames. Frequencies below
+ // this length will be suppressed. This helps block out annoying
+ // "sparkling" or "flickering" due to small variations in frame-to-frame
+ // brightness. If left as 0.0, this smoothing is completely disabled.
+ float smoothing_period;
+
+ // In order to avoid reacting sluggishly on scene changes as a result of
+ // the low-pass filter, we disable it when the difference between the
+ // current frame brightness and the average frame brightness exceeds a
+ // given threshold difference. But rather than a single hard cutoff, which
+ // would lead to weird discontinuities on fades, we gradually disable it
+ // over a small window of brightness ranges. These parameters control the
+ // lower and upper bounds of this window, in units of 1% PQ.
+ //
+ // Setting either one of these to 0.0 disables this logic.
+ float scene_threshold_low;
+ float scene_threshold_high;
+
+ // Which percentile of the input image brightness histogram to consider as
+ // the true peak of the scene. If this is set to 100 (or 0), the brightest
+ // pixel is measured. Otherwise, the top of the frequency distribution is
+ // progressively cut off. Setting this too low will cause clipping of very
+ // bright details, but can improve the dynamic brightness range of scenes
+ // with very bright isolated highlights.
+ //
+ // A recommended value is 99.995%, which is very conservative and should
+ // cause no major issues in typical content.
+ float percentile;
+
+ // Allows the peak detection result to be delayed by up to a single frame,
+ // which can sometimes improve thoughput, at the cost of introducing the
+ // possibility of 1-frame flickers on transitions. Disabled by default.
+ bool allow_delayed;
+
+ // --- Deprecated / removed fields
+ float overshoot_margin PL_DEPRECATED;
+ float minimum_peak PL_DEPRECATED;
+};
+
+#define PL_PEAK_DETECT_DEFAULTS \
+ .smoothing_period = 20.0f, \
+ .scene_threshold_low = 1.0f, \
+ .scene_threshold_high = 3.0f, \
+ .percentile = 100.0f,
+
+#define PL_PEAK_DETECT_HQ_DEFAULTS \
+ PL_PEAK_DETECT_DEFAULTS \
+ .percentile = 99.995f,
+
+#define pl_peak_detect_params(...) (&(struct pl_peak_detect_params) { PL_PEAK_DETECT_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_peak_detect_params pl_peak_detect_default_params;
+PL_API extern const struct pl_peak_detect_params pl_peak_detect_high_quality_params;
+
+// This function can be used to measure the CLL and FALL of a video
+// source automatically, using a compute shader. The measured values are
+// smoothed automatically (depending on the parameters), so to keep track of
+// the measured results over time, a tone mapping shader state object is used
+// to hold the state. Returns false on failure initializing the tone mapping
+// object, or if compute shaders are not supported.
+//
+// It's important that the same shader object is used for successive frames
+// belonging to the same source. If the source changes (e.g. due to a file
+// change or seek), the user should reset it with `pl_reset_detected_peak` (or
+// destroy it and use a new state object).
+//
+// The parameter `csp` holds the representation of the color values that are
+// the input to this function. (They must already be in decoded RGB form, i.e.
+// alternate color representations are not supported)
+PL_API bool pl_shader_detect_peak(pl_shader sh, struct pl_color_space csp,
+ pl_shader_obj *state,
+ const struct pl_peak_detect_params *params);
+
+// After dispatching the above shader, this function can be used to retrieve
+// the detected dynamic HDR10+ metadata parameters. The other fields of
+// `metadata` are not written to. Returns whether or not any values were
+// written. If not, the values are left untouched, so this can be used to
+// safely update `pl_hdr_metadata` values in-place. This function may or may
+// not block, depending on the previous setting of `allow_delayed`.
+PL_API bool pl_get_detected_hdr_metadata(const pl_shader_obj state,
+ struct pl_hdr_metadata *metadata);
+
+// After dispatching the above shader, this function *may* be used to read out
+// the detected CLL and FALL directly (in PL_HDR_NORM units). If the shader
+// has never been dispatched yet, i.e. no information is available, this will
+// return false.
+//
+// Deprecated in favor of `pl_get_detected_hdr_metadata`
+PL_DEPRECATED PL_API bool pl_get_detected_peak(const pl_shader_obj state,
+ float *out_cll, float *out_fall);
+
+// Resets the peak detection state in a given tone mapping state object. This
+// is not equal to `pl_shader_obj_destroy`, because it does not destroy any
+// state used by `pl_shader_tone_map`.
+PL_API void pl_reset_detected_peak(pl_shader_obj state);
+
+// Feature map extraction (for pl_color_map_args.feature_map). The result
+// of this shader should be downscaled / low-passed to the indicated kernel
+// size before use. (This does not happen automatically)
+PL_API void pl_shader_extract_features(pl_shader sh, struct pl_color_space csp);
+
+// Deprecated and unused. Libplacebo now always performs a variant of the old
+// hybrid tone-mapping, mixing together the intensity (I) and per-channel (LMS)
+// results.
+enum pl_tone_map_mode {
+ PL_TONE_MAP_AUTO PL_DEPRECATED_ENUMERATOR,
+ PL_TONE_MAP_RGB PL_DEPRECATED_ENUMERATOR,
+ PL_TONE_MAP_MAX PL_DEPRECATED_ENUMERATOR,
+ PL_TONE_MAP_HYBRID PL_DEPRECATED_ENUMERATOR,
+ PL_TONE_MAP_LUMA PL_DEPRECATED_ENUMERATOR,
+ PL_TONE_MAP_MODE_COUNT,
+};
+
+// Deprecated by <libplacebo/gamut_mapping.h>
+enum pl_gamut_mode {
+ PL_GAMUT_CLIP PL_DEPRECATED_ENUMERATOR, // pl_gamut_map_clip
+ PL_GAMUT_WARN PL_DEPRECATED_ENUMERATOR, // pl_gamut_map_highlight
+ PL_GAMUT_DARKEN PL_DEPRECATED_ENUMERATOR, // pl_gamut_map_darken
+ PL_GAMUT_DESATURATE PL_DEPRECATED_ENUMERATOR, // pl_gamut_map_desaturate
+ PL_GAMUT_MODE_COUNT,
+};
+
+struct pl_color_map_params {
+ // --- Gamut mapping options
+
+ // Gamut mapping function to use to handle out-of-gamut colors, including
+ // colors which are out-of-gamut as a consequence of tone mapping.
+ const struct pl_gamut_map_function *gamut_mapping;
+
+ // Gamut mapping constants, for expert tuning. Leave as default otherwise.
+ struct pl_gamut_map_constants gamut_constants;
+
+ // Gamut mapping 3DLUT size, for channels ICh. Defaults to {48, 32, 256}
+ int lut3d_size[3];
+
+ // Use higher quality, but slower, tricubic interpolation for gamut mapping
+ // 3DLUTs. May substantially improve the 3DLUT gamut mapping accuracy, in
+ // particular at smaller 3DLUT sizes. Shouldn't have much effect at the
+ // default size.
+ bool lut3d_tricubic;
+
+ // If true, allows the gamut mapping function to expand the gamut, in
+ // cases where the target gamut exceeds that of the source. If false,
+ // the source gamut will never be enlarged, even when using a gamut
+ // mapping function capable of bidirectional mapping.
+ bool gamut_expansion;
+
+ // --- Tone mapping options
+
+ // Tone mapping function to use to handle out-of-range colors.
+ const struct pl_tone_map_function *tone_mapping_function;
+
+ // Tone mapping constants, for expert tuning. Leave as default otherwise.
+ struct pl_tone_map_constants tone_constants;
+
+ // If true, and supported by the given tone mapping function, libplacebo
+ // will perform inverse tone mapping to expand the dynamic range of a
+ // signal. libplacebo is not liable for any HDR-induced eye damage.
+ bool inverse_tone_mapping;
+
+ // Data source to use when tone-mapping. Setting this to a specific
+ // value allows overriding the default metadata preference logic.
+ enum pl_hdr_metadata_type metadata;
+
+ // Tone mapping LUT size. Defaults to 256.
+ int lut_size;
+
+ // HDR contrast recovery strength. If set to a value above 0.0, the source
+ // image will be divided into high-frequency and low-frequency components,
+ // and a portion of the high-frequency image is added back onto the
+ // tone-mapped output. May cause excessive ringing artifacts for some HDR
+ // sources, but can improve the subjective sharpness and detail left over
+ // in the image after tone-mapping.
+ float contrast_recovery;
+
+ // Contrast recovery lowpass kernel size. Defaults to 3.5. Increasing
+ // or decreasing this will affect the visual appearance substantially.
+ float contrast_smoothness;
+
+ // --- Debugging options
+
+ // Force the use of a full tone-mapping LUT even for functions that have
+ // faster pure GLSL replacements (e.g. clip, linear, saturation).
+ bool force_tone_mapping_lut;
+
+ // Visualize the tone-mapping LUT and gamut mapping 3DLUT, in IPT space.
+ bool visualize_lut;
+
+ // Controls where to draw the visualization, relative to the rendered
+ // video (dimensions 0-1). Optional, defaults to the full picture.
+ pl_rect2df visualize_rect;
+
+ // Controls the rotation of the 3DLUT visualization.
+ float visualize_hue; // useful range [-pi, pi]
+ float visualize_theta; // useful range [0, pi/2]
+
+ // Graphically highlight hard-clipped pixels during tone-mapping (i.e.
+ // pixels that exceed the claimed source luminance range).
+ bool show_clipping;
+
+ // --- Deprecated fields
+ enum pl_tone_map_mode tone_mapping_mode PL_DEPRECATED; // removed
+ float tone_mapping_param PL_DEPRECATED; // see `tone_constants`
+ float tone_mapping_crosstalk PL_DEPRECATED; // now hard-coded as 0.04
+ enum pl_rendering_intent intent PL_DEPRECATED; // see `gamut_mapping`
+ enum pl_gamut_mode gamut_mode PL_DEPRECATED; // see `gamut_mapping`
+ float hybrid_mix PL_DEPRECATED; // removed
+};
+
+#define PL_COLOR_MAP_DEFAULTS \
+ .gamut_mapping = &pl_gamut_map_perceptual, \
+ .tone_mapping_function = &pl_tone_map_spline, \
+ .gamut_constants = { PL_GAMUT_MAP_CONSTANTS }, \
+ .tone_constants = { PL_TONE_MAP_CONSTANTS }, \
+ .metadata = PL_HDR_METADATA_ANY, \
+ .lut3d_size = {48, 32, 256}, \
+ .lut_size = 256, \
+ .visualize_rect = {0, 0, 1, 1}, \
+ .contrast_smoothness = 3.5f,
+
+#define PL_COLOR_MAP_HQ_DEFAULTS \
+ PL_COLOR_MAP_DEFAULTS \
+ .contrast_recovery = 0.30f,
+
+#define pl_color_map_params(...) (&(struct pl_color_map_params) { PL_COLOR_MAP_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_color_map_params pl_color_map_default_params;
+PL_API extern const struct pl_color_map_params pl_color_map_high_quality_params;
+
+// Execution arguments for the `pl_shader_color_map_ex` call. Distinct from
+// `pl_color_map_params` because it is filled by internally-provided execution
+// metadata, instead of user-tunable aesthetic parameters.
+struct pl_color_map_args {
+ // Input/output color space for the mapping.
+ struct pl_color_space src;
+ struct pl_color_space dst;
+
+ // If true, the logic will assume the input has already been linearized by
+ // the caller (e.g. as part of a previous linear light scaling operation).
+ bool prelinearized;
+
+ // Object to be used to store generated LUTs. Note that this is the same
+ // state object used by `pl_shader_detect_peak`, and if that function has
+ // been called on `state` prior to `pl_shader_color_map`, the detected
+ // values will be used to guide the tone mapping algorithm. If this is not
+ // provided, tone/gamut mapping are disabled.
+ pl_shader_obj *state;
+
+ // Low-resolution intensity feature map, as generated by
+ // `pl_shader_extract_features`. Optional. No effect if
+ // `params->contrast_recovery` is disabled.
+ pl_tex feature_map;
+};
+
+#define pl_color_map_args(...) (&(struct pl_color_map_args) { __VA_ARGS__ })
+
+// Maps `vec4 color` from one color space to another color space according
+// to the parameters (described in greater depth above). If `params` is left
+// as NULL, it defaults to `&pl_color_map_default_params`
+PL_API void pl_shader_color_map_ex(pl_shader sh,
+ const struct pl_color_map_params *params,
+ const struct pl_color_map_args *args);
+
+// Backwards compatibility wrapper around `pl_shader_color_map_ex`
+PL_API void pl_shader_color_map(pl_shader sh, const struct pl_color_map_params *params,
+ struct pl_color_space src, struct pl_color_space dst,
+ pl_shader_obj *state, bool prelinearized);
+
+// Applies a set of cone distortion parameters to `vec4 color` in a given color
+// space. This can be used to simulate color blindness. See `pl_cone_params`
+// for more information.
+PL_API void pl_shader_cone_distort(pl_shader sh, struct pl_color_space csp,
+ const struct pl_cone_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_COLORSPACE_H_
diff --git a/src/include/libplacebo/shaders/custom.h b/src/include/libplacebo/shaders/custom.h
new file mode 100644
index 0000000..a4eec69
--- /dev/null
+++ b/src/include/libplacebo/shaders/custom.h
@@ -0,0 +1,341 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_CUSTOM_H_
+#define LIBPLACEBO_SHADERS_CUSTOM_H_
+
+#include <stdlib.h>
+
+// Functions for writing custom shaders and hooking them into the `pl_renderer`
+// pipeline, as well as compatibility functions for parsing shaders in mpv
+// format.
+
+#include <libplacebo/shaders.h>
+#include <libplacebo/dispatch.h>
+#include <libplacebo/colorspace.h>
+
+PL_API_BEGIN
+
+// Parameters describing custom shader text to be embedded into a `pl_shader`
+// object. All of the strings are optional and can be left as NULL, but without
+// a `body` in particular, the shader will do nothing useful on its own.
+struct pl_custom_shader {
+ // The prelude contains text such as extra #defines, #extension pragmas,
+ // or other parts of the shader that must be placed at the very
+ // beginning (before input layout declarations etc.)
+ //
+ // Note: #extension pragmas do not need to be emitted to enable support for
+ // resource types already attached to the shader (e.g. SSBOs), compute
+ // shaders, or GPU capabilities known to libplacebo (e.g. subgroups).
+ const char *prelude;
+
+ // The header contains text such as helper function definitions, extra
+ // uniforms, shared memory variables or buffer descriptions.
+ const char *header;
+
+ // A friendly name for the shader. (Optional)
+ const char *description;
+
+ // The "primary" GLSL code. This will be effectively appended to the "main"
+ // function. It lives in an environment given by the `input` signature, and
+ // is expected to return results in a way given by the `output` signature.
+ //
+ // Note: In the case of PL_SHADER_SIG_COLOR, the output `vec4 color` is
+ // allocated by `pl_shader_custom`, the user merely needs to assign to it.
+ //
+ // Note: For ease of development it can be useful to have the main logic
+ // live inside a helper function defined as part of `header`, and specify
+ // the `body` as a single line that simply calls the helper function.
+ const char *body;
+ enum pl_shader_sig input;
+ enum pl_shader_sig output;
+
+ // Extra descriptors, variables and vertex attributes to attach to the
+ // resulting `pl_shader_res`.
+ //
+ // Note: The names inside these will possibly be replaced by fresh
+ // identifiers internally, so users should avoid looking for exact string
+ // matches for the given names inside the `pl_shader_res`.
+ const struct pl_shader_desc *descriptors;
+ int num_descriptors;
+ const struct pl_shader_var *variables;
+ int num_variables;
+ const struct pl_shader_va *vertex_attribs;
+ int num_vertex_attribs;
+ const struct pl_shader_const *constants;
+ int num_constants;
+
+ // If true, this shader must be a compute shader. The desired workgroup
+ // size and shared memory usage can be optionally specified, or 0 if no
+ // specific work group size or shared memory size restrictions apply.
+ //
+ // See also: `pl_shader_res.compute_group_size`
+ bool compute;
+ size_t compute_shmem;
+ int compute_group_size[2];
+
+ // Fixes the output size requirements of the shader to exact dimensions.
+ // Optional, if left as 0, means the shader can be dispatched at any size.
+ int output_w;
+ int output_h;
+};
+
+// Append custom shader code, including extra descriptors and variables, to an
+// existing `pl_shader` object. Returns whether successful. This function may
+// fail in the event that e.g. the custom shader requires compute shaders on
+// an unsupported GPU, or exceeds the GPU's shared memory capabilities.
+PL_API bool pl_shader_custom(pl_shader sh, const struct pl_custom_shader *params);
+
+// Which "rendering stages" are available for user shader hooking purposes.
+// Except where otherwise noted, all stages are "non-resizable", i.e. the
+// shaders already have specific output size requirements.
+enum pl_hook_stage {
+ // Hook stages for the untouched planes, as made available by the source.
+ // These are all resizable, i.e. there are no specific output stage
+ // requirements.
+ PL_HOOK_RGB_INPUT = 1 << 0,
+ PL_HOOK_LUMA_INPUT = 1 << 1,
+ PL_HOOK_CHROMA_INPUT = 1 << 2,
+ PL_HOOK_ALPHA_INPUT = 1 << 3,
+ PL_HOOK_XYZ_INPUT = 1 << 4,
+
+ // Hook stages for the scaled/aligned planes
+ PL_HOOK_CHROMA_SCALED = 1 << 5,
+ PL_HOOK_ALPHA_SCALED = 1 << 6,
+
+ PL_HOOK_NATIVE = 1 << 7, // Combined image in its native color space
+ PL_HOOK_RGB = 1 << 8, // After conversion to RGB (resizable)
+ PL_HOOK_LINEAR = 1 << 9, // After linearization but before scaling
+ PL_HOOK_SIGMOID = 1 << 10, // After sigmoidization
+ PL_HOOK_PRE_KERNEL = 1 << 11, // Immediately before the main scaler kernel
+ PL_HOOK_POST_KERNEL = 1 << 12, // Immediately after the main scaler kernel
+ PL_HOOK_SCALED = 1 << 13, // After scaling, before color management
+ PL_HOOK_PRE_OUTPUT = 1 << 14, // After color management, before blending/rotation
+ PL_HOOK_OUTPUT = 1 << 15, // After blending/rotation, before dithering
+};
+
+// Returns true if a given hook stage is resizable
+static inline bool pl_hook_stage_resizable(enum pl_hook_stage stage) {
+ switch (stage) {
+ case PL_HOOK_RGB_INPUT:
+ case PL_HOOK_LUMA_INPUT:
+ case PL_HOOK_CHROMA_INPUT:
+ case PL_HOOK_ALPHA_INPUT:
+ case PL_HOOK_XYZ_INPUT:
+ case PL_HOOK_NATIVE:
+ case PL_HOOK_RGB:
+ return true;
+
+ case PL_HOOK_CHROMA_SCALED:
+ case PL_HOOK_ALPHA_SCALED:
+ case PL_HOOK_LINEAR:
+ case PL_HOOK_SIGMOID:
+ case PL_HOOK_PRE_KERNEL:
+ case PL_HOOK_POST_KERNEL:
+ case PL_HOOK_SCALED:
+ case PL_HOOK_PRE_OUTPUT:
+ case PL_HOOK_OUTPUT:
+ return false;
+ }
+
+ abort();
+}
+
+// The different forms of communicating image data between the renderer and
+// the hooks
+enum pl_hook_sig {
+ PL_HOOK_SIG_NONE, // No data is passed, no data is received/returned
+ PL_HOOK_SIG_COLOR, // `vec4 color` already pre-sampled in a `pl_shader`
+ PL_HOOK_SIG_TEX, // `pl_tex` containing the image data
+ PL_HOOK_SIG_COUNT,
+};
+
+struct pl_hook_params {
+ // GPU objects associated with the `pl_renderer`, which the user may
+ // use for their own purposes.
+ pl_gpu gpu;
+ pl_dispatch dispatch;
+
+ // Helper function to fetch a new temporary texture, using renderer-backed
+ // storage. This is guaranteed to have sane image usage requirements and a
+ // 16-bit or floating point format. The user does not need to free/destroy
+ // this texture in any way. May return NULL.
+ pl_tex (*get_tex)(void *priv, int width, int height);
+ void *priv;
+
+ // Which stage triggered the hook to run.
+ enum pl_hook_stage stage;
+
+ // For `PL_HOOK_SIG_COLOR`, this contains the existing shader object with
+ // the color already pre-sampled into `vec4 color`. The user may modify
+ // this as much as they want, as long as they don't dispatch/finalize/reset
+ // it.
+ //
+ // Note that this shader might have specific output size requirements,
+ // depending on the exact shader stage hooked by the user, and may already
+ // be a compute shader.
+ pl_shader sh;
+
+ // For `PL_HOOK_SIG_TEX`, this contains the texture that the user should
+ // sample from.
+ //
+ // Note: This texture object is owned by the renderer, and users must not
+ // modify its contents. It will not be touched for the duration of a frame,
+ // but the contents are lost in between frames.
+ pl_tex tex;
+
+ // The effective current rectangle of the image we're rendering in this
+ // shader, i.e. the effective rect of the content we're interested in,
+ // as a crop of either `sh` or `tex` (depending on the signature).
+ //
+ // Note: This is still set even for `PL_HOOK_SIG_NONE`!
+ pl_rect2df rect;
+
+ // The current effective colorspace and representation, of either the
+ // pre-sampled color (in `sh`), or the contents of `tex`, respectively.
+ //
+ // Note: This is still set even for `PL_HOOK_SIG_NONE`!
+ struct pl_color_repr repr;
+ struct pl_color_space color;
+ int components;
+
+ // The representation and colorspace of the original image, for reference.
+ const struct pl_color_repr *orig_repr;
+ const struct pl_color_space *orig_color;
+
+ // The (cropped) source and destination rectangles of the overall
+ // rendering. These are functionallty equivalent to `image.crop` and
+ // `target.crop`, respectively, but `src_rect` in particular may change as
+ // a result of previous hooks being executed. (e.g. prescalers)
+ pl_rect2df src_rect;
+ pl_rect2d dst_rect;
+};
+
+struct pl_hook_res {
+ // If true, the hook is assumed to have "failed" or errored in some way,
+ // and all other fields are ignored.
+ bool failed;
+
+ // What type of output this hook is returning.
+ // Note: If this is `PL_HOOK_SIG_NONE`, all other fields are ignored.
+ enum pl_hook_sig output;
+
+ // For `PL_HOOK_SIG_COLOR`, this *must* be set to a valid `pl_shader`
+ // object containing the sampled color value (i.e. with an output signature
+ // of `PL_SHADER_SIG_COLOR`), and *should* be allocated from the given
+ // `pl_dispatch` object. Ignored otherwise.
+ pl_shader sh;
+
+ // For `PL_HOOK_SIG_TEX`, this *must* contain the texture object containing
+ // the result of rendering the hook. This *should* be a texture allocated
+ // using the given `get_tex` callback, to ensure the format and texture
+ // usage flags are compatible with what the renderer expects.
+ pl_tex tex;
+
+ // For shaders that return some sort of output, this contains the
+ // new/altered versions of the existing "current texture" metadata.
+ struct pl_color_repr repr;
+ struct pl_color_space color;
+ int components;
+
+ // This contains the new effective rect of the contents. This may be
+ // different from the original `rect` for resizable passes. Ignored for
+ // non-resizable passes.
+ pl_rect2df rect;
+};
+
+enum pl_hook_par_mode {
+ PL_HOOK_PAR_VARIABLE, // normal shader variable
+ PL_HOOK_PAR_DYNAMIC, // dynamic shader variable, e.g. per-frame changing
+ PL_HOOK_PAR_CONSTANT, // fixed at compile time (e.g. for array sizes),
+ // must be scalar (non-vector/matrix)
+ PL_HOOK_PAR_DEFINE, // defined in the preprocessor, must be `int`
+ PL_HOOK_PAR_MODE_COUNT,
+};
+
+typedef union pl_var_data {
+ int i;
+ unsigned u;
+ float f;
+} pl_var_data;
+
+struct pl_hook_par {
+ // Name as used in the shader.
+ const char *name;
+
+ // Type of this shader parameter, and how it's manifested in the shader.
+ enum pl_var_type type;
+ enum pl_hook_par_mode mode;
+
+ // Human-readable explanation of this parameter. (Optional)
+ const char *description;
+
+ // Mutable data pointer to current value of variable.
+ pl_var_data *data;
+
+ // Default/initial value, and lower/upper bounds.
+ pl_var_data initial;
+ pl_var_data minimum;
+ pl_var_data maximum;
+
+ // Human-readable names for the variants of an integer option. This array
+ // can be indexed directly by integer values, ranging from `minimum.i` to
+ // `maximum.i`. May be NULL, in which case options are unnamed.
+ const char * const *names;
+};
+
+// Struct describing a hook.
+//
+// Note: Users may freely create their own instances of this struct, there is
+// nothing particularly special about `pl_mpv_user_shader_parse`.
+struct pl_hook {
+ enum pl_hook_stage stages; // Which stages to hook on
+ enum pl_hook_sig input; // Which input signature this hook expects
+ void *priv; // Arbitrary user context
+
+ // Custom tunable shader parameters exported by this hook. These may be
+ // updated at any time by the user, to influence the behavior of the hook.
+ // Contents are arbitrary and subject to the method of hook construction.
+ const struct pl_hook_par *parameters;
+ int num_parameters;
+
+ // Called at the beginning of passes, to reset/initialize the hook. (Optional)
+ void (*reset)(void *priv);
+
+ // The hook function itself. Called by the renderer at any of the indicated
+ // hook stages. See `pl_hook_res` for more info on the return values.
+ struct pl_hook_res (*hook)(void *priv, const struct pl_hook_params *params);
+
+ // Unique signature identifying this hook, used to disable misbehaving hooks.
+ // All hooks with the same signature will be disabled, should they fail to
+ // execute during run-time.
+ uint64_t signature;
+};
+
+// Compatibility layer with `mpv` user shaders. See the mpv man page for more
+// information on the format. Will return `NULL` if the shader fails parsing.
+//
+// The resulting `pl_hook` objects should be destroyed with the corresponding
+// destructor when no longer needed.
+PL_API const struct pl_hook *
+pl_mpv_user_shader_parse(pl_gpu gpu, const char *shader_text, size_t shader_len);
+
+PL_API void pl_mpv_user_shader_destroy(const struct pl_hook **hook);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_CUSTOM_H_
diff --git a/src/include/libplacebo/shaders/deinterlacing.h b/src/include/libplacebo/shaders/deinterlacing.h
new file mode 100644
index 0000000..40e74e8
--- /dev/null
+++ b/src/include/libplacebo/shaders/deinterlacing.h
@@ -0,0 +1,137 @@
+
+/*
+ * This file is part of libplacebo, which is normally licensed under the terms
+ * of the LGPL v2.1+. However, this file (film_grain.h) is also available under
+ * the terms of the more permissive MIT license:
+ *
+ * Copyright (c) 2018-2019 Niklas Haas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_DEINTERLACING_H_
+#define LIBPLACEBO_SHADERS_DEINTERLACING_H_
+
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+enum pl_field {
+ PL_FIELD_NONE = 0, // no deinterlacing
+ PL_FIELD_EVEN, // "top" fields, with even y coordinates
+ PL_FIELD_ODD, // "bottom" fields, with odd y coordinates
+
+ // Convenience aliases
+ PL_FIELD_TOP = PL_FIELD_EVEN,
+ PL_FIELD_BOTTOM = PL_FIELD_ODD,
+};
+
+static inline enum pl_field pl_field_other(enum pl_field field)
+{
+ switch (field) {
+ case PL_FIELD_EVEN: return PL_FIELD_ODD;
+ case PL_FIELD_ODD: return PL_FIELD_EVEN;
+ default: return field;
+ }
+}
+
+struct pl_field_pair {
+ // Top texture. If only this is specified, it's assumed to contain both
+ // fields in an interleaved fashion (MBAFF).
+ //
+ // Note: Support for separate fields (PAFF), is currently pending, so this
+ // is the only way to provide interlaced frames at the moment.
+ pl_tex top;
+};
+
+#define pl_field_pair(...) ((struct pl_field_pair) { __VA_ARGS__ })
+
+struct pl_deinterlace_source {
+ // Previous, current and next source (interlaced) frames. `prev` and `next`
+ // may be NULL, but `cur` is required. If present, they must all have the
+ // exact same texture dimensions.
+ //
+ // Note: `prev` and `next` are only required for PL_DEINTERLACE_YADIF.
+ struct pl_field_pair prev, cur, next;
+
+ // The parity of the current field to output. This field will be unmodified
+ // from `cur`, with the corresponding other field interpolated.
+ //
+ // If this is `PL_FIELD_NONE`, no deinterlacing is performed, and the
+ // texture is merely sampled as-is.
+ enum pl_field field;
+
+ // The parity of the first frame in a stream. Set this the field that is
+ // (conceptually) ordered first in time.
+ //
+ // If this is `PL_FIELD_NONE`, it will instead default to `PL_FIELD_TOP`.
+ enum pl_field first_field;
+
+ // Components to deinterlace. Components not specified will be ignored.
+ // Optional, if left as 0, all components will be deinterlaced.
+ uint8_t component_mask;
+};
+
+#define pl_deinterlace_source(...) (&(struct pl_deinterlace_source) { __VA_ARGS__ })
+
+enum pl_deinterlace_algorithm {
+ // No-op deinterlacing, just sample the weaved frame un-touched.
+ PL_DEINTERLACE_WEAVE = 0,
+
+ // Naive bob deinterlacing. Doubles the field lines vertically.
+ PL_DEINTERLACE_BOB,
+
+ // "Yet another deinterlacing filter". Deinterlacer with temporal and
+ // spatial information. Based on FFmpeg's Yadif filter algorithm, but
+ // adapted slightly for the GPU.
+ PL_DEINTERLACE_YADIF,
+
+ PL_DEINTERLACE_ALGORITHM_COUNT,
+};
+
+// Returns whether or not an algorithm requires `prev`/`next` refs to be set.
+static inline bool pl_deinterlace_needs_refs(enum pl_deinterlace_algorithm algo)
+{
+ return algo == PL_DEINTERLACE_YADIF;
+}
+
+struct pl_deinterlace_params {
+ // Algorithm to use. The recommended default is PL_DEINTERLACE_YADIF, which
+ // provides a good trade-off of quality and speed.
+ enum pl_deinterlace_algorithm algo;
+
+ // Skip the spatial interlacing check. (PL_DEINTERLACE_YADIF only)
+ bool skip_spatial_check;
+};
+
+#define PL_DEINTERLACE_DEFAULTS \
+ .algo = PL_DEINTERLACE_YADIF,
+
+#define pl_deinterlace_params(...) (&(struct pl_deinterlace_params) { PL_DEINTERLACE_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_deinterlace_params pl_deinterlace_default_params;
+
+// Deinterlaces a set of interleaved source frames and outputs the result into
+// `vec4 color`. If `params` is left as NULL, it defaults to
+// `&pl_deinterlace_default_params`.
+PL_API void pl_shader_deinterlace(pl_shader sh, const struct pl_deinterlace_source *src,
+ const struct pl_deinterlace_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_DEINTERLACING_H_
diff --git a/src/include/libplacebo/shaders/dithering.h b/src/include/libplacebo/shaders/dithering.h
new file mode 100644
index 0000000..9146c81
--- /dev/null
+++ b/src/include/libplacebo/shaders/dithering.h
@@ -0,0 +1,140 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_DITHERING_H_
+#define LIBPLACEBO_SHADERS_DITHERING_H_
+
+// Dithering shaders
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/dither.h>
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+enum pl_dither_method {
+ // Dither with blue noise. Very high quality, but requires the use of a
+ // LUT. Warning: Computing a blue noise texture with a large size can be
+ // very slow, however this only needs to be performed once. Even so, using
+ // this with a `lut_size` greater than 6 is generally ill-advised. This is
+ // the preferred/default dither method.
+ PL_DITHER_BLUE_NOISE,
+
+ // Dither with an ordered (bayer) dither matrix, using a LUT. Low quality,
+ // and since this also uses a LUT, there's generally no advantage to picking
+ // this instead of `PL_DITHER_BLUE_NOISE`. It's mainly there for testing.
+ PL_DITHER_ORDERED_LUT,
+
+ // The same as `PL_DITHER_ORDERED_LUT`, but uses fixed function math instead
+ // of a LUT. This is faster, but only supports a fixed dither matrix size
+ // of 16x16 (equal to a `lut_size` of 4).
+ PL_DITHER_ORDERED_FIXED,
+
+ // Dither with white noise. This does not require a LUT and is fairly cheap
+ // to compute. Unlike the other modes it doesn't show any repeating
+ // patterns either spatially or temporally, but the downside is that this
+ // is visually fairly jarring due to the presence of low frequencies in the
+ // noise spectrum.
+ PL_DITHER_WHITE_NOISE,
+
+ PL_DITHER_METHOD_COUNT,
+};
+
+struct pl_dither_params {
+ // The source of the dither noise to use.
+ enum pl_dither_method method;
+
+ // For the dither methods which require the use of a LUT, this controls
+ // the size of the LUT (base 2). If left as NULL, this defaults to 6, which
+ // is equivalent to a 64x64 dither matrix. Must not be larger than 8.
+ int lut_size;
+
+ // Enables temporal dithering. This reduces the persistence of dithering
+ // artifacts by perturbing the dithering matrix per frame.
+ // Warning: This can cause nasty aliasing artifacts on some LCD screens.
+ bool temporal;
+
+ // Gamma function to use for dither gamma correction. This will only have
+ // an effect when dithering to low bit depths (<= 4).
+ enum pl_color_transfer transfer;
+};
+
+#define PL_DITHER_DEFAULTS \
+ .method = PL_DITHER_BLUE_NOISE, \
+ .lut_size = 6, \
+ /* temporal dithering commonly flickers on LCDs */ \
+ .temporal = false,
+
+#define pl_dither_params(...) (&(struct pl_dither_params) { PL_DITHER_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_dither_params pl_dither_default_params;
+
+// Dither the colors to a lower depth, given in bits. This can be used on input
+// colors of any precision. Basically, this rounds the colors to only linear
+// multiples of the stated bit depth. The average intensity of the result
+// will not change (i.e., the dither noise is balanced in both directions).
+// If `params` is NULL, it defaults to &pl_dither_default_params.
+//
+// For the dither methods which require the use of a LUT, `dither_state` must
+// be set to a valid pointer. To avoid thrashing the resource, users should
+// avoid trying to re-use the same LUT for different dither configurations. If
+// passed as NULL, libplacebo will automatically fall back to dither algorithms
+// that don't require the use of a LUT.
+//
+// Warning: This dithering algorithm is not gamma-invariant; so using it for
+// very low bit depths (below 4 or so) will noticeably increase the brightness
+// of the resulting image. When doing low bit depth dithering for aesthetic
+// purposes, it's recommended that the user explicitly (de)linearize the colors
+// before and after this algorithm.
+PL_API void pl_shader_dither(pl_shader sh, int new_depth,
+ pl_shader_obj *dither_state,
+ const struct pl_dither_params *params);
+
+struct pl_error_diffusion_params {
+ // Both the input and output texture must be provided up-front, with the
+ // same size. The output texture must be storable, and the input texture
+ // must be sampleable.
+ pl_tex input_tex;
+ pl_tex output_tex;
+
+ // Depth to dither to. Required.
+ int new_depth;
+
+ // Error diffusion kernel to use. Optional. If unspecified, defaults to
+ // `&pl_error_diffusion_sierra_lite`.
+ const struct pl_error_diffusion_kernel *kernel;
+};
+
+#define pl_error_diffusion_params(...) (&(struct pl_error_diffusion_params) { __VA_ARGS__ })
+
+// Computes the shared memory requirements for a given error diffusion kernel.
+// This can be used to test up-front whether or not error diffusion would be
+// supported or not, before having to initialize textures.
+PL_API size_t pl_error_diffusion_shmem_req(const struct pl_error_diffusion_kernel *kernel,
+ int height);
+
+// Apply an error diffusion dithering kernel. This is a much more expensive and
+// heavy dithering method, and is not generally recommended for realtime usage
+// where performance is critical.
+//
+// Requires compute shader support. Returns false if dithering fail e.g. as a
+// result of shader memory limits being exceeded. The resulting shader must be
+// dispatched with a work group count of exactly 1.
+PL_API bool pl_shader_error_diffusion(pl_shader sh, const struct pl_error_diffusion_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_DITHERING_H_
diff --git a/src/include/libplacebo/shaders/film_grain.h b/src/include/libplacebo/shaders/film_grain.h
new file mode 100644
index 0000000..8a9c78b
--- /dev/null
+++ b/src/include/libplacebo/shaders/film_grain.h
@@ -0,0 +1,137 @@
+/*
+ * This file is part of libplacebo, which is normally licensed under the terms
+ * of the LGPL v2.1+. However, this file (film_grain.h) is also available under
+ * the terms of the more permissive MIT license:
+ *
+ * Copyright (c) 2018-2019 Niklas Haas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_FILM_GRAIN_H_
+#define LIBPLACEBO_SHADERS_FILM_GRAIN_H_
+
+// Film grain synthesis shaders for AV1 / H.274.
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+enum pl_film_grain_type {
+ PL_FILM_GRAIN_NONE = 0,
+ PL_FILM_GRAIN_AV1,
+ PL_FILM_GRAIN_H274,
+ PL_FILM_GRAIN_COUNT,
+};
+
+// AV1 film grain parameters. For the exact meaning of these, see the AV1
+// specification (section 6.8.20).
+struct pl_av1_grain_data {
+ int num_points_y;
+ uint8_t points_y[14][2]; // [n][0] = value, [n][1] = scaling
+ bool chroma_scaling_from_luma;
+ int num_points_uv[2]; // should be {0} for grayscale images
+ uint8_t points_uv[2][10][2]; // like points_y for points_uv[0, 1] = u, v
+ int scaling_shift;
+ int ar_coeff_lag;
+ int8_t ar_coeffs_y[24];
+ int8_t ar_coeffs_uv[2][25];
+ int ar_coeff_shift;
+ int grain_scale_shift;
+ int8_t uv_mult[2];
+ int8_t uv_mult_luma[2];
+ int16_t uv_offset[2]; // 9-bit value, range [-256, 255]
+ bool overlap;
+};
+
+// H.274 film grain parameters. For the exact meaning of these, see the H.274
+// specification (section 8.5).
+struct pl_h274_grain_data {
+ int model_id;
+ int blending_mode_id;
+ int log2_scale_factor;
+ bool component_model_present[3];
+ uint16_t num_intensity_intervals[3];
+ uint8_t num_model_values[3];
+ const uint8_t *intensity_interval_lower_bound[3];
+ const uint8_t *intensity_interval_upper_bound[3];
+ const int16_t (*comp_model_value[3])[6];
+};
+
+// Tagged union for film grain data
+struct pl_film_grain_data {
+ enum pl_film_grain_type type; // film grain type
+ uint64_t seed; // shared seed value
+
+ union {
+ // Warning: These values are not sanity-checked at all, Invalid grain
+ // data results in undefined behavior!
+ struct pl_av1_grain_data av1;
+ struct pl_h274_grain_data h274;
+ } params;
+};
+
+// Options for the `pl_shader_film_grain` call.
+struct pl_film_grain_params {
+ // Required for all film grain types:
+ struct pl_film_grain_data data; // film grain data
+ pl_tex tex; // texture to sample from
+ struct pl_color_repr *repr; // underlying color representation (see notes)
+ int components;
+ int component_mapping[4]; // same as `struct pl_plane`
+
+ // Notes for `repr`:
+ // - repr->bits affects the rounding for grain generation
+ // - repr->levels affects whether or not we clip to full range or not
+ // - repr->sys affects the interpretation of channels
+ // - *repr gets normalized by this shader, which is why it's a pointer
+
+ // Required for PL_FILM_GRAIN_AV1 only:
+ pl_tex luma_tex; // "luma" texture (see notes)
+ int luma_comp; // index of luma in `luma_tex`
+
+ // Notes for `luma_tex`:
+ // - `luma_tex` must be specified if the `tex` does not itself contain the
+ // "luma-like" component. For XYZ systems, the Y channel is the luma
+ // component. For RGB systems, the G channel is.
+};
+
+#define pl_film_grain_params(...) (&(struct pl_film_grain_params) { __VA_ARGS__ })
+
+// Test if film grain needs to be applied. This is a helper function that users
+// can use to decide whether or not `pl_shader_film_grain` needs to be called,
+// based on the given grain metadata.
+PL_API bool pl_needs_film_grain(const struct pl_film_grain_params *params);
+
+// Sample from a texture while applying film grain at the same time.
+// `grain_state` must be unique for every plane configuration, as it may
+// contain plane-dependent state.
+//
+// Returns false on any error, or if film grain generation is not supported
+// due to GLSL limitations.
+PL_API bool pl_shader_film_grain(pl_shader sh, pl_shader_obj *grain_state,
+ const struct pl_film_grain_params *params);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_FILM_GRAIN_H_
diff --git a/src/include/libplacebo/shaders/icc.h b/src/include/libplacebo/shaders/icc.h
new file mode 100644
index 0000000..a4003f4
--- /dev/null
+++ b/src/include/libplacebo/shaders/icc.h
@@ -0,0 +1,135 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_ICC_H_
+#define LIBPLACEBO_SHADERS_ICC_H_
+
+// Functions for generating and applying ICC-derived (3D)LUTs
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+struct pl_icc_params {
+ // The rendering intent to use, for profiles with multiple intents. A
+ // recommended value is PL_INTENT_RELATIVE_COLORIMETRIC for color-accurate
+ // video reproduction, or PL_INTENT_PERCEPTUAL for profiles containing
+ // meaningful perceptual mapping tables for some more suitable color space
+ // like BT.709.
+ //
+ // If this is set to the special value PL_INTENT_AUTO, will use the
+ // preferred intent provided by the profile header.
+ enum pl_rendering_intent intent;
+
+ // The size of the 3DLUT to generate. If left as NULL, these individually
+ // default to values appropriate for the profile. (Based on internal
+ // precision heuristics)
+ //
+ // Note: Setting this manually is strongly discouraged, as it can result
+ // in excessively high 3DLUT sizes where a much smaller LUT would have
+ // sufficed.
+ int size_r, size_g, size_b;
+
+ // This field can be used to override the detected brightness level of the
+ // ICC profile. If you set this to the special value 0 (or a negative
+ // number), libplacebo will attempt reading the brightness value from the
+ // ICC profile's tagging (if available), falling back to PL_COLOR_SDR_WHITE
+ // if unavailable.
+ float max_luma;
+
+ // Force black point compensation. May help avoid crushed or raised black
+ // points on "improper" profiles containing e.g. colorimetric tables that
+ // do not round-trip. Should not be required on well-behaved profiles,
+ // or when using PL_INTENT_PERCEPTUAL, but YMMV.
+ bool force_bpc;
+
+ // If provided, this pl_cache instance will be used, instead of the
+ // GPU-internal cache, to cache the generated 3DLUTs. Note that these can
+ // get large, especially for large values of size_{r,g,b}, so the user may
+ // wish to split this cache off from the main shader cache. (Optional)
+ pl_cache cache;
+
+ // Deprecated legacy caching API. Replaced by `cache`.
+ PL_DEPRECATED void *cache_priv;
+ PL_DEPRECATED void (*cache_save)(void *priv, uint64_t sig, const uint8_t *cache, size_t size);
+ PL_DEPRECATED bool (*cache_load)(void *priv, uint64_t sig, uint8_t *cache, size_t size);
+};
+
+#define PL_ICC_DEFAULTS \
+ .intent = PL_INTENT_RELATIVE_COLORIMETRIC, \
+ .max_luma = PL_COLOR_SDR_WHITE,
+
+#define pl_icc_params(...) (&(struct pl_icc_params) { PL_ICC_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_icc_params pl_icc_default_params;
+
+// This object represents a "parsed" ICC profile.
+typedef const struct pl_icc_object_t {
+ // Provided params, with the `intent` and `size` fields set (as described)
+ struct pl_icc_params params;
+
+ // Signature of the corresponding ICC profile.
+ uint64_t signature;
+
+ // Detected color space (or UNKNOWN for profiles which don't contain an
+ // exact match), with HDR metedata set to the detected gamut and
+ // white/black value ranges.
+ struct pl_color_space csp;
+
+ // Best estimate of profile gamma. This only serves as a rough guideline.
+ float gamma;
+
+ // Smallest containing primary set, always set.
+ enum pl_color_primaries containing_primaries;
+} *pl_icc_object;
+
+// Attempts opening/parsing the contents of an ICC profile. The resulting
+// object is memory managed and may outlive the original profile - access
+// to the underlying profile is no longer needed once this returns.
+PL_API pl_icc_object pl_icc_open(pl_log log, const struct pl_icc_profile *profile,
+ const struct pl_icc_params *params);
+PL_API void pl_icc_close(pl_icc_object *icc);
+
+// Update an existing pl_icc_object, which may be NULL, replacing it by the
+// new profile and parameters (if incompatible).
+//
+// Returns success. `obj` is set to the created profile, or NULL on error.
+//
+// Note: If `profile->signature` matches `(*obj)->signature`, or if `profile` is
+// NULL, then the existing profile is directly reused, with only the effective
+// parameters changing. In this case, `profile->data` is also *not* read from,
+// and may safely be NULL.
+PL_API bool pl_icc_update(pl_log log, pl_icc_object *obj,
+ const struct pl_icc_profile *profile,
+ const struct pl_icc_params *params);
+
+// Decode the input from the colorspace determined by the attached ICC profile
+// to linear light RGB (in the profile's containing primary set). `lut` must be
+// set to a shader object that will store the GPU resources associated with the
+// generated LUT. The resulting color space will be written to `out_csp`.
+PL_API void pl_icc_decode(pl_shader sh, pl_icc_object profile, pl_shader_obj *lut,
+ struct pl_color_space *out_csp);
+
+// Encode the input from linear light RGB (in the profile's containing primary
+// set) into the colorspace determined by the attached ICC profile. `lut` must
+// be set to a shader object that will store the GPU resources associated with
+// the generated LUT.
+PL_API void pl_icc_encode(pl_shader sh, pl_icc_object profile, pl_shader_obj *lut);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_ICC_H_
diff --git a/src/include/libplacebo/shaders/lut.h b/src/include/libplacebo/shaders/lut.h
new file mode 100644
index 0000000..6e30ddc
--- /dev/null
+++ b/src/include/libplacebo/shaders/lut.h
@@ -0,0 +1,78 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_LUT_H_
+#define LIBPLACEBO_SHADERS_LUT_H_
+
+// Shaders for loading and applying arbitrary custom 1D/3DLUTs
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+// Struct defining custom LUTs
+//
+// Note: Users may freely create their own instances of this struct, there is
+// nothing particularly special about `pl_lut_parse_cube`.
+struct pl_custom_lut {
+ // Some unique signature identifying this LUT, needed to detect state
+ // changes (for cache invalidation). This should ideally be a hash of the
+ // file contents. (Which is what `pl_lut_parse_*` will set it to.)
+ uint64_t signature;
+
+ // Size of each dimension, in the order R, G, B. For 1D LUTs, only the R
+ // dimension should be specified (the others left as 0).
+ int size[3];
+
+ // Raw LUT data itself, in properly scaled floating point format. For 3D
+ // LUTs, the innermost dimension is the first dimension (R), and the
+ // outermost dimension is the last dimension (B). Individual color samples
+ // are in the order R, G, B.
+ const float *data;
+
+ // Extra input/output shaper matrices. Ignored if equal to {0}. This is
+ // mostly useful for 1D LUTs, since 3D LUTs can bake the shaper matrix into
+ // the LUT itself - but it can still help optimize LUT precision.
+ pl_matrix3x3 shaper_in, shaper_out;
+
+ // Nominal metadata for the input/output of a LUT. Left as {0} if unknown.
+ // Note: This is purely informative, `pl_shader_custom_lut` ignores it.
+ struct pl_color_repr repr_in, repr_out;
+ struct pl_color_space color_in, color_out;
+};
+
+// Parse a 3DLUT in .cube format. Returns NULL if the file fails parsing.
+PL_API struct pl_custom_lut *pl_lut_parse_cube(pl_log log, const char *str, size_t str_len);
+
+// Frees a LUT created by `pl_lut_parse_*`.
+PL_API void pl_lut_free(struct pl_custom_lut **lut);
+
+// Apply a `pl_custom_lut`. The user is responsible for ensuring colors going
+// into the LUT are in the expected format as informed by the LUT metadata.
+//
+// `lut_state` must be a pointer to a NULL-initialized shader state object that
+// will be used to encapsulate any required GPU state.
+//
+// Note: `lut` does not have to be allocated by `pl_lut_parse_*`. It can be a
+// struct filled out by the user.
+PL_API void pl_shader_custom_lut(pl_shader sh, const struct pl_custom_lut *lut,
+ pl_shader_obj *lut_state);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_LUT_H_
diff --git a/src/include/libplacebo/shaders/sampling.h b/src/include/libplacebo/shaders/sampling.h
new file mode 100644
index 0000000..5221e44
--- /dev/null
+++ b/src/include/libplacebo/shaders/sampling.h
@@ -0,0 +1,257 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SHADERS_SAMPLING_H_
+#define LIBPLACEBO_SHADERS_SAMPLING_H_
+
+// Sampling operations. These shaders perform some form of sampling operation
+// from a given pl_tex. In order to use these, the pl_shader *must* have been
+// created using the same `gpu` as the originating `pl_tex`. Otherwise, this
+// is undefined behavior. They require nothing (PL_SHADER_SIG_NONE) and return
+// a color (PL_SHADER_SIG_COLOR).
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/filters.h>
+#include <libplacebo/shaders.h>
+
+PL_API_BEGIN
+
+// Common parameters for sampling operations
+struct pl_sample_src {
+ // There are two mutually exclusive ways of providing the source to sample
+ // from:
+ //
+ // 1. Provide the texture and sampled region directly. This generates
+ // a shader with input signature `PL_SHADER_SIG_NONE`, which binds the
+ // texture as a descriptor (and the coordinates as a vertex attribute)
+ pl_tex tex; // texture to sample
+ pl_rect2df rect; // sub-rect to sample from (optional)
+ enum pl_tex_address_mode address_mode; // preferred texture address mode
+
+ // 2. Have the shader take it as an argument. Doing this requires
+ // specifying the missing metadata of the texture backing the sampler, so
+ // that the shader generation can generate the correct code.
+ int tex_w, tex_h; // dimensions of the actual texture
+ enum pl_fmt_type format; // format of the sampler being accepted
+ enum pl_sampler_type sampler; // type of the sampler being accepted
+ enum pl_tex_sample_mode mode; // sample mode of the sampler being accepted
+ float sampled_w, sampled_h; // dimensions of the sampled region (optional)
+
+ // Common metadata for both sampler input types:
+ int components; // number of components to sample (optional)
+ uint8_t component_mask; // bitmask of components to sample (optional)
+ int new_w, new_h; // dimensions of the resulting output (optional)
+ float scale; // factor to multiply into sampled signal (optional)
+
+ // Note: `component_mask` and `components` are mutually exclusive, the
+ // former is preferred if both are specified.
+};
+
+#define pl_sample_src(...) (&(struct pl_sample_src) { __VA_ARGS__ })
+
+struct pl_deband_params {
+ // The number of debanding steps to perform per sample. Each step reduces a
+ // bit more banding, but takes time to compute. Note that the strength of
+ // each step falls off very quickly, so high numbers (>4) are practically
+ // useless. Defaults to 1.
+ int iterations;
+
+ // The debanding filter's cut-off threshold. Higher numbers increase the
+ // debanding strength dramatically, but progressively diminish image
+ // details. Defaults to 3.0.
+ float threshold;
+
+ // The debanding filter's initial radius. The radius increases linearly
+ // for each iteration. A higher radius will find more gradients, but a
+ // lower radius will smooth more aggressively. Defaults to 16.0.
+ float radius;
+
+ // Add some extra noise to the image. This significantly helps cover up
+ // remaining quantization artifacts. Higher numbers add more noise.
+ // Note: When debanding HDR sources, even a small amount of grain can
+ // result in a very big change to the brightness level. It's recommended to
+ // either scale this value down or disable it entirely for HDR.
+ //
+ // Defaults to 4.0, which is very mild.
+ float grain;
+
+ // 'Neutral' grain value for each channel being debanded (sorted in order
+ // from low to high index). Grain application will be modulated to avoid
+ // disturbing colors close to this value. Set this to a value corresponding
+ // to black in the relevant colorspace.
+ float grain_neutral[3];
+};
+
+#define PL_DEBAND_DEFAULTS \
+ .iterations = 1, \
+ .threshold = 3.0, \
+ .radius = 16.0, \
+ .grain = 4.0,
+
+#define pl_deband_params(...) (&(struct pl_deband_params) {PL_DEBAND_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_deband_params pl_deband_default_params;
+
+// Debands a given texture and returns the sampled color in `vec4 color`. If
+// `params` is left as NULL, it defaults to &pl_deband_default_params. Note
+// that `tex->params.format` must have PL_FMT_CAP_LINEAR. When the given
+// `pl_sample_src` implies scaling, this effectively performs bilinear
+// sampling on the input (but not the output).
+//
+// Note: This can also be used as a pure grain function, by setting the number
+// of iterations to 0.
+PL_API void pl_shader_deband(pl_shader sh, const struct pl_sample_src *src,
+ const struct pl_deband_params *params);
+
+// Performs direct / native texture sampling, using whatever texture filter is
+// available (linear for linearly sampleable sources, nearest otherwise).
+//
+// Note: This is generally very low quality and should be avoided if possible,
+// for both upscaling and downscaling.
+PL_API bool pl_shader_sample_direct(pl_shader sh, const struct pl_sample_src *src);
+
+// Performs hardware-accelerated nearest neighbour sampling. This is similar to
+// `pl_shader_sample_direct`, but forces nearest neighbour interpolation.
+PL_API bool pl_shader_sample_nearest(pl_shader sh, const struct pl_sample_src *src);
+
+// Performs hardware-accelerated bilinear sampling. This is similar to
+// `pl_shader_sample_direct`, but forces bilinear interpolation.
+PL_API bool pl_shader_sample_bilinear(pl_shader sh, const struct pl_sample_src *src);
+
+// Optimized versions of specific, strictly positive scaler kernels that take
+// adantage of linear texture sampling to reduce the number of fetches needed
+// by a factor of four. This family of functions performs radius-2 scaling
+// with only four texture fetches, which is far more efficient than using
+// the generalized 1D scaling method. Only works well for upscaling.
+PL_API bool pl_shader_sample_bicubic(pl_shader sh, const struct pl_sample_src *src);
+PL_API bool pl_shader_sample_hermite(pl_shader sh, const struct pl_sample_src *src);
+PL_API bool pl_shader_sample_gaussian(pl_shader sh, const struct pl_sample_src *src);
+
+// A sampler that is similar to nearest neighbour sampling, but tries to
+// preserve pixel aspect ratios. This is mathematically equivalent to taking an
+// idealized image with square pixels, sampling it at an infinite resolution,
+// and then downscaling that to the desired resolution. (Hence it being called
+// "oversample"). Good for pixel art.
+//
+// The threshold provides a cutoff threshold below which the contribution of
+// pixels should be ignored, trading some amount of aspect ratio distortion for
+// a slightly crisper image. A value of `threshold == 0.5` makes this filter
+// equivalent to regular nearest neighbour sampling.
+PL_API bool pl_shader_sample_oversample(pl_shader sh, const struct pl_sample_src *src,
+ float threshold);
+
+struct pl_sample_filter_params {
+ // The filter to use for sampling.
+ struct pl_filter_config filter;
+
+ // Antiringing strength. A value of 0.0 disables antiringing, and a value
+ // of 1.0 enables full-strength antiringing. Defaults to 0.0 if
+ // unspecified.
+ //
+ // Note: Ignored if `filter.antiring` is already set to something nonzero.
+ float antiring;
+
+ // Disable the use of compute shaders (e.g. if rendering to non-storable tex)
+ bool no_compute;
+ // Disable the use of filter widening / anti-aliasing (for downscaling)
+ bool no_widening;
+
+ // This shader object is used to store the LUT, and will be recreated
+ // if necessary. To avoid thrashing the resource, users should avoid trying
+ // to re-use the same LUT for different filter configurations or scaling
+ // ratios. Must be set to a valid pointer, and the target NULL-initialized.
+ pl_shader_obj *lut;
+
+ // Deprecated / removed fields
+ int lut_entries PL_DEPRECATED; // hard-coded as 256
+ float cutoff PL_DEPRECATED; // hard-coded as 1e-3
+};
+
+#define pl_sample_filter_params(...) (&(struct pl_sample_filter_params) { __VA_ARGS__ })
+
+// Performs polar sampling. This internally chooses between an optimized compute
+// shader, and various fragment shaders, depending on the supported GLSL version
+// and GPU features. Returns whether or not it was successful.
+//
+// Note: `params->filter.polar` must be true to use this function.
+PL_API bool pl_shader_sample_polar(pl_shader sh, const struct pl_sample_src *src,
+ const struct pl_sample_filter_params *params);
+
+// Performs orthogonal (1D) sampling. Using this twice in a row (once vertical
+// and once horizontal) effectively performs a 2D upscale. This is lower
+// quality than polar sampling, but significantly faster, and therefore the
+// recommended default. Returns whether or not it was successful.
+//
+// `src` must represent a scaling operation that only scales in one direction,
+// i.e. either only X or only Y. The other direction must be left unscaled.
+//
+// Note: Due to internal limitations, this may currently only be used on 2D
+// textures - even though the basic principle would work for 1D and 3D textures
+// as well.
+PL_API bool pl_shader_sample_ortho2(pl_shader sh, const struct pl_sample_src *src,
+ const struct pl_sample_filter_params *params);
+
+struct pl_distort_params {
+ // An arbitrary 2x2 affine transformation to apply to the input image.
+ // For simplicity, the input image is explicitly centered and scaled such
+ // that the longer dimension is in [-1,1], before applying this.
+ pl_transform2x2 transform;
+
+ // If true, the texture is placed inside the center of the canvas without
+ // scaling. If false, it is effectively stretched to the canvas size.
+ bool unscaled;
+
+ // If true, the transformation is automatically scaled down and shifted to
+ // ensure that the resulting image fits inside the output canvas.
+ bool constrain;
+
+ // If true, use bicubic interpolation rather than faster bilinear
+ // interpolation. Higher quality but slower.
+ bool bicubic;
+
+ // Specifies the texture address mode to use when sampling out of bounds.
+ enum pl_tex_address_mode address_mode;
+
+ // If set, all out-of-bounds accesses will instead be treated as
+ // transparent, according to the given alpha mode. (Which should match the
+ // alpha mode of the texture)
+ //
+ // Note: `address_mode` has no effect when this is specified.
+ enum pl_alpha_mode alpha_mode;
+};
+
+#define PL_DISTORT_DEFAULTS \
+ .transform.mat.m = {{ 1, 0 }, {0, 1}},
+
+#define pl_distort_params(...) (&(struct pl_distort_params) {PL_DISTORT_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_distort_params pl_distort_default_params;
+
+// Distorts the input image using a given set of transformation parameters.
+// `out_w` and `out_h` determine the size of the effective canvas inside which
+// the distorted result may be rendered. Areas outside of this canvas will
+// be implicitly cut off.
+PL_API void pl_shader_distort(pl_shader sh, pl_tex tex, int out_w, int out_h,
+ const struct pl_distort_params *params);
+
+enum PL_DEPRECATED { // for `int pass`
+ PL_SEP_VERT = 0,
+ PL_SEP_HORIZ,
+ PL_SEP_PASSES
+};
+
+PL_API_END
+
+#endif // LIBPLACEBO_SHADERS_SAMPLING_H_
diff --git a/src/include/libplacebo/swapchain.h b/src/include/libplacebo/swapchain.h
new file mode 100644
index 0000000..b53aa5c
--- /dev/null
+++ b/src/include/libplacebo/swapchain.h
@@ -0,0 +1,171 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_SWAPCHAIN_H_
+#define LIBPLACEBO_SWAPCHAIN_H_
+
+#include <libplacebo/common.h>
+#include <libplacebo/colorspace.h>
+#include <libplacebo/gpu.h>
+
+PL_API_BEGIN
+
+// This abstraction represents a low-level interface to visible surfaces
+// exposed by a graphics API (and accompanying GPU instance), allowing users to
+// directly present frames to the screen (or window, typically). This is a
+// sister API to gpu.h and follows the same convention w.r.t undefined behavior.
+//
+// Thread-safety: Safe
+typedef const struct pl_swapchain_t {
+ pl_log log;
+ pl_gpu gpu;
+} *pl_swapchain;
+
+// Destroys this swapchain. May be used at any time, and may block until the
+// completion of all outstanding rendering commands. The swapchain and any
+// resources retrieved from it must not be used afterwards.
+PL_API void pl_swapchain_destroy(pl_swapchain *sw);
+
+// Returns the approximate current swapchain latency in vsyncs, or 0 if
+// unknown. A latency of 1 means that `submit_frame` followed by `swap_buffers`
+// will block until the just-submitted frame has finished rendering. Typical
+// values are 2 or 3, which enable better pipelining by allowing the GPU to be
+// processing one or two frames at the same time as the user is preparing the
+// next for submission.
+PL_API int pl_swapchain_latency(pl_swapchain sw);
+
+// Update/query the swapchain size. This function performs both roles: it tries
+// setting the swapchain size to the values requested by the user, and returns
+// in the same variables what width/height the swapchain was actually set to -
+// which may be (substantially) different from the values requested by the
+// user. A value of 0 means "unknown/none" (in which case, libplacebo won't try
+// updating the size - it will simply return the current state of the
+// swapchain). It's also possible for libplacebo to return values of 0, such as
+// in the case that the swapchain doesn't exist yet.
+//
+// Returns false on significant errors (e.g. dead surface). This function can
+// effectively be used to probe if creating a swapchain works.
+PL_API bool pl_swapchain_resize(pl_swapchain sw, int *width, int *height);
+
+// Backwards compatibility
+#define pl_swapchain_colors pl_color_space
+
+// Inform the swapchain about the input color space. This API deliberately
+// provides no feedback, because the swapchain can internally decide what to do
+// with this information, including ignoring it entirely, or applying it
+// asynchronously. Users must still base their rendering on the value of
+// `pl_swapchain_frame.color_space`.
+//
+// Note: Calling this function a second time completely overrides any
+// previously specified hint. So calling this on {0} or NULL resets the
+// swapchain back to its initial/preferred colorspace.
+//
+// Note: If `csp->transfer` is a HDR transfer curve but HDR metadata is left
+// unspecified, the HDR metadata defaults to `pl_hdr_metadata_hdr10`.
+// Conversely, if the HDR metadata is non-empty but `csp->transfer` is left as
+// PL_COLOR_TRC_UNKNOWN, then it instead defaults to PL_COLOR_TRC_PQ.
+PL_API void pl_swapchain_colorspace_hint(pl_swapchain sw, const struct pl_color_space *csp);
+
+// The struct used to hold the results of `pl_swapchain_start_frame`
+struct pl_swapchain_frame {
+ // A texture representing the framebuffer users should use for rendering.
+ // It's guaranteed that `fbo->params.renderable` and `fbo->params.blit_dst`
+ // will be true, but no other guarantees are made - not even that
+ // `fbo->params.format` is a real format.
+ pl_tex fbo;
+
+ // If true, the user should assume that this framebuffer will be flipped
+ // as a result of presenting it on-screen. If false, nothing special needs
+ // to be done - but if true, users should flip the coordinate system of
+ // the `pl_pass` that is rendering to this framebuffer.
+ //
+ // Note: Normally, libplacebo follows the convention that (0,0) represents
+ // the top left of the image/screen. So when flipped is true, this means
+ // (0,0) on this framebuffer gets displayed as the bottom left of the image.
+ bool flipped;
+
+ // Indicates the color representation this framebuffer will be interpreted
+ // as by the host system / compositor / display, including the bit depth
+ // and alpha handling (where available).
+ struct pl_color_repr color_repr;
+ struct pl_color_space color_space;
+};
+
+// Retrieve a new frame from the swapchain. Returns whether successful. It's
+// worth noting that this function can fail sporadically for benign reasons,
+// for example the window being invisible or inaccessible. This function may
+// block until an image is available, which may be the case if the GPU is
+// rendering frames significantly faster than the display can output them. It
+// may also be non-blocking, so users shouldn't rely on this call alone in
+// order to meter rendering speed. (Specifics depend on the underlying graphics
+// API)
+PL_API bool pl_swapchain_start_frame(pl_swapchain sw, struct pl_swapchain_frame *out_frame);
+
+// Submits the previously started frame. Non-blocking. This must be issued in
+// lockstep with pl_swapchain_start_frame - there is no way to start multiple
+// frames and submit them out-of-order. The frames submitted this way will
+// generally be made visible in a first-in first-out fashion, although
+// specifics depend on the mechanism used to create the pl_swapchain. (See the
+// platform-specific APIs for more info).
+//
+// Returns whether successful. This should normally never fail, unless the
+// GPU/surface has been lost or some other critical error has occurred. The
+// "started" frame is consumed even in the event of failure.
+//
+// Note that `start_frame` and `submit_frame` form a lock pair, i.e. trying to
+// call e.g. `pl_swapchain_resize` from another thread will block until
+// `pl_swapchain_submit_frame` is finished.
+PL_API bool pl_swapchain_submit_frame(pl_swapchain sw);
+
+// Performs a "buffer swap", or some generalization of the concept. In layman's
+// terms, this blocks until the execution of the Nth previously submitted frame
+// has been "made complete" in some sense. (The N derives from the swapchain's
+// built-in latency. See `pl_swapchain_latency` for more information).
+//
+// Users should include this call in their rendering loops in order to make
+// sure they aren't submitting rendering commands faster than the GPU can
+// process them, which would potentially lead to a queue overrun or exhaust
+// memory.
+//
+// An example loop might look like this:
+//
+// while (rendering) {
+// struct pl_swapchain_frame frame;
+// bool ok = pl_swapchain_start_frame(swapchain, &frame);
+// if (!ok) {
+// /* wait some time, or decide to stop rendering */
+// continue;
+// }
+//
+// /* do some rendering with frame.fbo */
+//
+// ok = pl_swapchain_submit_frame(swapchain);
+// if (!ok)
+// break;
+//
+// pl_swapchain_swap_buffers(swapchain);
+// }
+//
+// The duration this function blocks for, if at all, may be very inconsistent
+// and should not be used as an authoritative source of vsync timing
+// information without sufficient smoothing/filtering (and if so, the time that
+// `start_frame` blocked for should also be included).
+PL_API void pl_swapchain_swap_buffers(pl_swapchain sw);
+
+PL_API_END
+
+#endif // LIBPLACEBO_SWAPCHAIN_H_
diff --git a/src/include/libplacebo/tone_mapping.h b/src/include/libplacebo/tone_mapping.h
new file mode 100644
index 0000000..48f1eb7
--- /dev/null
+++ b/src/include/libplacebo/tone_mapping.h
@@ -0,0 +1,268 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_TONE_MAPPING_H_
+#define LIBPLACEBO_TONE_MAPPING_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include <libplacebo/common.h>
+#include <libplacebo/colorspace.h>
+
+PL_API_BEGIN
+
+struct pl_tone_map_params;
+struct pl_tone_map_function {
+ const char *name; // Identifier
+ const char *description; // Friendly / longer name
+
+ // This controls the type of values input/output to/from `map`
+ enum pl_hdr_scaling scaling;
+
+ // The tone-mapping function itself. Iterates over all values in `lut`, and
+ // adapts them as needed.
+ //
+ // Note that the `params` struct fed into this function is guaranteed to
+ // satisfy `params->input_scaling == params->output_scaling == scaling`,
+ // and also obeys `params->input_max >= params->output_max`.
+ void (*map)(float *lut, const struct pl_tone_map_params *params);
+
+ // Inverse tone mapping function. Optional. If absent, this tone mapping
+ // curve only works in the forwards direction.
+ //
+ // For this function, `params->input_max <= params->output_max`.
+ void (*map_inverse)(float *lut, const struct pl_tone_map_params *params);
+
+ // Private data. Unused by libplacebo, but may be accessed by `map`.
+ void *priv;
+
+ // --- Deprecated fields
+ const char *param_desc PL_DEPRECATED;
+ float param_min PL_DEPRECATED;
+ float param_def PL_DEPRECATED;
+ float param_max PL_DEPRECATED;
+};
+
+struct pl_tone_map_constants {
+ // Configures the knee point, as a ratio between the source average and
+ // target average (in PQ space). An adaptation of 1.0 always adapts the
+ // source scene average brightness to the (scaled) target average,
+ // while a value of 0.0 never modifies scene brightness. [0,1]
+ //
+ // Affects all methods that use the ST2094 knee point determination
+ // (currently ST2094-40, ST2094-10 and spline)
+ float knee_adaptation;
+
+ // Configures the knee point minimum and maximum, respectively, as
+ // a percentage of the PQ luminance range. Provides a hard limit on the
+ // knee point chosen by `knee_adaptation`.
+ float knee_minimum; // (0, 0.5)
+ float knee_maximum; // (0.5, 1.0)
+
+ // Default knee point to use in the absence of source scene average
+ // metadata. Normally, this is ignored in favor of picking the knee
+ // point as the (relative) source scene average brightness level.
+ float knee_default; // [knee_minimum, knee_maximum]
+
+ // Knee point offset (for BT.2390 only). Note that a value of 0.5 is
+ // the spec-defined default behavior, which differs from the libplacebo
+ // default of 1.0. [0.5, 2]
+ float knee_offset;
+
+ // For the single-pivot polynomial (spline) function, this controls the
+ // coefficients used to tune the slope of the curve. This tuning is designed
+ // to make the slope closer to 1.0 when the difference in peaks is low,
+ // and closer to linear when the difference between peaks is high.
+ float slope_tuning; // [0,10]
+ float slope_offset; // [0,1]
+
+ // Contrast setting for the spline function. Higher values make the curve
+ // steeper (closer to `clip`), preserving midtones at the cost of losing
+ // shadow/highlight details, while lower values make the curve shallowed
+ // (closer to `linear`), preserving highlights at the cost of losing midtone
+ // contrast. Values above 1.0 are possible, resulting in an output with more
+ // contrast than the input.
+ float spline_contrast; // [0,1.5]
+
+ // For the reinhard function, this specifies the local contrast coefficient
+ // at the display peak. Essentially, a value of 0.5 implies that the
+ // reference white will be about half as bright as when clipping. (0,1)
+ float reinhard_contrast;
+
+ // For legacy functions (mobius, gamma) which operate on linear light, this
+ // directly sets the corresponding knee point. (0,1)
+ float linear_knee;
+
+ // For linear methods (linear, linearlight), this controls the linear
+ // exposure/gain applied to the image. (0,10]
+ float exposure;
+};
+
+#define PL_TONE_MAP_CONSTANTS \
+ .knee_adaptation = 0.4f, \
+ .knee_minimum = 0.1f, \
+ .knee_maximum = 0.8f, \
+ .knee_default = 0.4f, \
+ .knee_offset = 1.0f, \
+ .slope_tuning = 1.5f, \
+ .slope_offset = 0.2f, \
+ .spline_contrast = 0.5f, \
+ .reinhard_contrast = 0.5f, \
+ .linear_knee = 0.3f, \
+ .exposure = 1.0f,
+
+struct pl_tone_map_params {
+ // If `function` is NULL, defaults to `pl_tone_map_clip`.
+ const struct pl_tone_map_function *function;
+
+ // Common constants, should be initialized to PL_TONE_MAP_CONSTANTS if
+ // not intending to override them further.
+ struct pl_tone_map_constants constants;
+
+ // The desired input/output scaling of the tone map. If this differs from
+ // `function->scaling`, any required conversion will be performed.
+ //
+ // Note that to maximize LUT efficiency, it's *highly* recommended to use
+ // either PL_HDR_PQ or PL_HDR_SQRT as the input scaling, except when
+ // using `pl_tone_map_sample`.
+ enum pl_hdr_scaling input_scaling;
+ enum pl_hdr_scaling output_scaling;
+
+ // The size of the resulting LUT. (For `pl_tone_map_generate` only)
+ size_t lut_size;
+
+ // The characteristics of the input, in `input_scaling` units.
+ float input_min;
+ float input_max;
+ float input_avg; // or 0 if unknown
+
+ // The desired characteristics of the output, in `output_scaling` units.
+ float output_min;
+ float output_max;
+
+ // The input HDR metadata. Only used by a select few tone-mapping
+ // functions, currently only SMPTE ST2094. (Optional)
+ struct pl_hdr_metadata hdr;
+
+ // --- Deprecated fields
+ float param PL_DEPRECATED; // see `constants`
+};
+
+#define pl_tone_map_params(...) (&(struct pl_tone_map_params) { __VA_ARGS__ });
+
+// Note: Only does pointer equality testing on `function`
+PL_API bool pl_tone_map_params_equal(const struct pl_tone_map_params *a,
+ const struct pl_tone_map_params *b);
+
+// Clamps/defaults the parameters, including input/output maximum.
+PL_API void pl_tone_map_params_infer(struct pl_tone_map_params *params);
+
+// Returns true if the given tone mapping configuration effectively represents
+// a no-op configuration. Tone mapping can be skipped in this case (although
+// strictly speaking, the LUT would still clip illegal input values)
+PL_API bool pl_tone_map_params_noop(const struct pl_tone_map_params *params);
+
+// Generate a tone-mapping LUT for a given configuration. This will always
+// span the entire input range, as given by `input_min` and `input_max`.
+PL_API void pl_tone_map_generate(float *out, const struct pl_tone_map_params *params);
+
+// Samples a tone mapping function at a single position. Note that this is less
+// efficient than `pl_tone_map_generate` for generating multiple values.
+//
+// Ignores `params->lut_size`.
+PL_API float pl_tone_map_sample(float x, const struct pl_tone_map_params *params);
+
+// Performs no tone-mapping, just clips out-of-range colors. Retains perfect
+// color accuracy for in-range colors but completely destroys out-of-range
+// information. Does not perform any black point adaptation.
+PL_API extern const struct pl_tone_map_function pl_tone_map_clip;
+
+// EETF from SMPTE ST 2094-40 Annex B, which uses the provided OOTF based on
+// Bezier curves to perform tone-mapping. The OOTF used is adjusted based on
+// the ratio between the targeted and actual display peak luminances. In the
+// absence of HDR10+ metadata, falls back to a simple constant bezier curve.
+PL_API extern const struct pl_tone_map_function pl_tone_map_st2094_40;
+
+// EETF from SMPTE ST 2094-10 Annex B.2, which takes into account the input
+// signal average luminance in addition to the maximum/minimum.
+//
+// Note: This does *not* currently include the subjective gain/offset/gamma
+// controls defined in Annex B.3. (Open an issue with a valid sample file if
+// you want such parameters to be respected.)
+PL_API extern const struct pl_tone_map_function pl_tone_map_st2094_10;
+
+// EETF from the ITU-R Report BT.2390, a hermite spline roll-off with linear
+// segment.
+PL_API extern const struct pl_tone_map_function pl_tone_map_bt2390;
+
+// EETF from ITU-R Report BT.2446, method A. Can be used for both forward
+// and inverse tone mapping.
+PL_API extern const struct pl_tone_map_function pl_tone_map_bt2446a;
+
+// Simple spline consisting of two polynomials, joined by a single pivot point,
+// which is tuned based on the source scene average brightness (taking into
+// account dynamic metadata if available). This function can be used
+// for both forward and inverse tone mapping.
+PL_API extern const struct pl_tone_map_function pl_tone_map_spline;
+
+// Very simple non-linear curve. Named after Erik Reinhard.
+PL_API extern const struct pl_tone_map_function pl_tone_map_reinhard;
+
+// Generalization of the reinhard tone mapping algorithm to support an
+// additional linear slope near black. The name is derived from its function
+// shape (ax+b)/(cx+d), which is known as a Möbius transformation.
+PL_API extern const struct pl_tone_map_function pl_tone_map_mobius;
+
+// Piece-wise, filmic tone-mapping algorithm developed by John Hable for use in
+// Uncharted 2, inspired by a similar tone-mapping algorithm used by Kodak.
+// Popularized by its use in video games with HDR rendering. Preserves both
+// dark and bright details very well, but comes with the drawback of changing
+// the average brightness quite significantly. This is sort of similar to
+// pl_tone_map_reinhard with `reinhard_contrast=0.24`.
+PL_API extern const struct pl_tone_map_function pl_tone_map_hable;
+
+// Fits a gamma (power) function to transfer between the source and target
+// color spaces, effectively resulting in a perceptual hard-knee joining two
+// roughly linear sections. This preserves details at all scales, but can result
+// in an image with a muted or dull appearance.
+PL_API extern const struct pl_tone_map_function pl_tone_map_gamma;
+
+// Linearly stretches the input range to the output range, in PQ space. This
+// will preserve all details accurately, but results in a significantly
+// different average brightness. Can be used for inverse tone-mapping in
+// addition to regular tone-mapping.
+PL_API extern const struct pl_tone_map_function pl_tone_map_linear;
+
+// Like `pl_tone_map_linear`, but in linear light (instead of PQ). Works well
+// for small range adjustments but may cause severe darkening when
+// downconverting from e.g. 10k nits to SDR.
+PL_API extern const struct pl_tone_map_function pl_tone_map_linear_light;
+
+// A list of built-in tone mapping functions, terminated by NULL
+PL_API extern const struct pl_tone_map_function * const pl_tone_map_functions[];
+PL_API extern const int pl_num_tone_map_functions; // excluding trailing NULL
+
+// Find the tone mapping function with the given name, or NULL on failure.
+PL_API const struct pl_tone_map_function *pl_find_tone_map_function(const char *name);
+
+// Deprecated alias, do not use
+#define pl_tone_map_auto pl_tone_map_spline
+
+PL_API_END
+
+#endif // LIBPLACEBO_TONE_MAPPING_H_
diff --git a/src/include/libplacebo/utils/dav1d.h b/src/include/libplacebo/utils/dav1d.h
new file mode 100644
index 0000000..ece97c5
--- /dev/null
+++ b/src/include/libplacebo/utils/dav1d.h
@@ -0,0 +1,129 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DAV1D_H_
+#define LIBPLACEBO_DAV1D_H_
+
+#include <libplacebo/gpu.h>
+#include <libplacebo/utils/upload.h>
+#include <dav1d/dav1d.h>
+
+#if defined(__cplusplus) && !defined(PL_DAV1D_IMPLEMENTATION)
+# define PL_DAV1D_API
+# define PL_DAV1D_IMPLEMENTATION 0
+# warning Remember to include this file with a PL_DAV1D_IMPLEMENTATION set to 1 in \
+ C translation unit to provide implementation. Suppress this warning by \
+ defining PL_DAV1D_IMPLEMENTATION to 0 in C++ files.
+#elif !defined(PL_DAV1D_IMPLEMENTATION)
+# define PL_DAV1D_API static inline
+# define PL_DAV1D_IMPLEMENTATION 1
+#else
+# define PL_DAV1D_API
+#endif
+
+PL_API_BEGIN
+
+// Fill in the details of a `pl_frame` from a Dav1dPicture. This function will
+// explicitly clear `out_frame`, setting all extra fields to 0. After this
+// function returns, the only missing data is information related to the plane
+// texture itself (`planes[N].texture`).
+//
+// Note: This will include all possible metadata, including HDR metadata and
+// AV1 film grain data. Users should explicitly clear this out if undesired.
+PL_DAV1D_API void pl_frame_from_dav1dpicture(struct pl_frame *out_frame,
+ const Dav1dPicture *picture);
+
+// Helper function to generate a `pl_color_space` struct from a Dav1dPicture.
+// Useful to update the swapchain colorspace mode dynamically (e.g. for HDR).
+PL_DAV1D_API void pl_swapchain_colors_from_dav1dpicture(struct pl_color_space *out_colors,
+ const Dav1dPicture *picture);
+
+struct pl_dav1d_upload_params {
+ // The picture to upload. Not modified unless `asynchronous` is true.
+ Dav1dPicture *picture;
+
+ // If true, film grain present in `picture` will be exported to the
+ // `pl_frame` as well. This should be set to false unless the user has
+ // disabled `Dav1dSettings.apply_grain`.
+ bool film_grain;
+
+ // If true, libplacebo will probe for the allocation metadata set by
+ // `pl_allocate_dav1dpicture`, and directly import the attached buffers
+ // (saving a memcpy in some cases). Has no effect if the Dav1dPicture was
+ // not allocated using `pl_allocate_dav1dpicture`.
+ //
+ // Note: When this is the case, `asynchronous` has no further effect -
+ // uploads from attached buffers are already asynchronous.
+ bool gpu_allocated;
+
+ // If true, `picture` will be asynchronously uploaded and unref'd
+ // internally by libplacebo, and the struct passed by the user cleared to
+ // {0}. This is needed to avoid `memcpy` in some cases, so setting it to
+ // true is highly recommended wherever possible.
+ //
+ // Note: If `pl_upload_dav1dpicture` returns false, `picture` does not get
+ // unref'd.
+ bool asynchronous;
+};
+
+#define pl_dav1d_upload_params(...) (&(struct pl_dav1d_upload_params) { __VA_ARGS__ })
+
+// Very high level helper function to take a `Dav1dPicture` and upload it to
+// the GPU. Similar in spirit to `pl_upload_plane`, and the same notes apply.
+// `tex` must be an array of 3 pointers of type `pl_tex`, each
+// either pointing to a valid texture, or NULL. Returns whether successful.
+PL_DAV1D_API bool pl_upload_dav1dpicture(pl_gpu gpu,
+ struct pl_frame *out_frame, pl_tex tex[3],
+ const struct pl_dav1d_upload_params *params);
+
+// Allocate a Dav1dPicture from persistently mapped buffers. This can be more
+// efficient than regular Dav1dPictures, especially when using the synchronous
+// `pl_upload_dav1dpicture`, or on platforms that don't support importing
+// PL_HANDLE_HOST_PTR as buffers. Returns 0 or a negative DAV1D_ERR value.
+//
+// Note: These may only be used directly as a Dav1dPicAllocator if the `gpu`
+// passed as the value of `cookie` is `pl_gpu.limits.thread_safe`. Otherwise,
+// the user must manually synchronize this to ensure it runs on the correct
+// thread.
+PL_DAV1D_API int pl_allocate_dav1dpicture(Dav1dPicture *picture, void *gpu);
+PL_DAV1D_API void pl_release_dav1dpicture(Dav1dPicture *picture, void *gpu);
+
+// Mapping functions for the various Dav1dColor* enums. Note that these are not
+// quite 1:1, and even for values that exist in both, the semantics sometimes
+// differ. Some special cases (e.g. ICtCp, or XYZ) are handled differently in
+// libplacebo and libdav1d, respectively.
+PL_DAV1D_API enum pl_color_system pl_system_from_dav1d(enum Dav1dMatrixCoefficients mc);
+PL_DAV1D_API enum Dav1dMatrixCoefficients pl_system_to_dav1d(enum pl_color_system sys);
+PL_DAV1D_API enum pl_color_levels pl_levels_from_dav1d(int color_range);
+PL_DAV1D_API int pl_levels_to_dav1d(enum pl_color_levels levels);
+PL_DAV1D_API enum pl_color_primaries pl_primaries_from_dav1d(enum Dav1dColorPrimaries prim);
+PL_DAV1D_API enum Dav1dColorPrimaries pl_primaries_to_dav1d(enum pl_color_primaries prim);
+PL_DAV1D_API enum pl_color_transfer pl_transfer_from_dav1d(enum Dav1dTransferCharacteristics trc);
+PL_DAV1D_API enum Dav1dTransferCharacteristics pl_transfer_to_dav1d(enum pl_color_transfer trc);
+PL_DAV1D_API enum pl_chroma_location pl_chroma_from_dav1d(enum Dav1dChromaSamplePosition loc);
+PL_DAV1D_API enum Dav1dChromaSamplePosition pl_chroma_to_dav1d(enum pl_chroma_location loc);
+
+
+// Actual implementation, included as part of this header to avoid having
+// a compile-time dependency on libdav1d.
+#if PL_DAV1D_IMPLEMENTATION
+# include <libplacebo/utils/dav1d_internal.h>
+#endif
+
+PL_API_END
+
+#endif // LIBPLACEBO_DAV1D_H_
diff --git a/src/include/libplacebo/utils/dav1d_internal.h b/src/include/libplacebo/utils/dav1d_internal.h
new file mode 100644
index 0000000..2e0512a
--- /dev/null
+++ b/src/include/libplacebo/utils/dav1d_internal.h
@@ -0,0 +1,613 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DAV1D_H_
+#error This header should be included as part of <libplacebo/utils/dav1d.h>
+#elif defined(__cplusplus)
+#error This header cannot be included from C++ define PL_DAV1D_IMPLEMENTATION appropriately
+#else
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+PL_DAV1D_API enum pl_color_system pl_system_from_dav1d(enum Dav1dMatrixCoefficients mc)
+{
+ switch (mc) {
+ case DAV1D_MC_IDENTITY: return PL_COLOR_SYSTEM_RGB; // or XYZ (unlikely)
+ case DAV1D_MC_BT709: return PL_COLOR_SYSTEM_BT_709;
+ case DAV1D_MC_UNKNOWN: return PL_COLOR_SYSTEM_UNKNOWN;
+ case DAV1D_MC_FCC: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case DAV1D_MC_BT470BG: return PL_COLOR_SYSTEM_BT_601;
+ case DAV1D_MC_BT601: return PL_COLOR_SYSTEM_BT_601;
+ case DAV1D_MC_SMPTE240: return PL_COLOR_SYSTEM_SMPTE_240M;
+ case DAV1D_MC_SMPTE_YCGCO: return PL_COLOR_SYSTEM_YCGCO;
+ case DAV1D_MC_BT2020_NCL: return PL_COLOR_SYSTEM_BT_2020_NC;
+ case DAV1D_MC_BT2020_CL: return PL_COLOR_SYSTEM_BT_2020_C;
+ case DAV1D_MC_SMPTE2085: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case DAV1D_MC_CHROMAT_NCL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case DAV1D_MC_CHROMAT_CL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ // Note: this colorspace is confused between PQ and HLG, which dav1d
+ // requires inferring from other sources, but libplacebo makes
+ // explicit. Default to PQ as it's the more common scenario.
+ case DAV1D_MC_ICTCP: return PL_COLOR_SYSTEM_BT_2100_PQ;
+ case DAV1D_MC_RESERVED: abort();
+ }
+
+ return PL_COLOR_SYSTEM_UNKNOWN;
+}
+
+PL_DAV1D_API enum Dav1dMatrixCoefficients pl_system_to_dav1d(enum pl_color_system sys)
+{
+ switch (sys) {
+ case PL_COLOR_SYSTEM_UNKNOWN: return DAV1D_MC_UNKNOWN;
+ case PL_COLOR_SYSTEM_BT_601: return DAV1D_MC_BT601;
+ case PL_COLOR_SYSTEM_BT_709: return DAV1D_MC_BT709;
+ case PL_COLOR_SYSTEM_SMPTE_240M: return DAV1D_MC_SMPTE240;
+ case PL_COLOR_SYSTEM_BT_2020_NC: return DAV1D_MC_BT2020_NCL;
+ case PL_COLOR_SYSTEM_BT_2020_C: return DAV1D_MC_BT2020_CL;
+ case PL_COLOR_SYSTEM_BT_2100_PQ: return DAV1D_MC_ICTCP;
+ case PL_COLOR_SYSTEM_BT_2100_HLG: return DAV1D_MC_ICTCP;
+ case PL_COLOR_SYSTEM_DOLBYVISION: return DAV1D_MC_UNKNOWN; // missing
+ case PL_COLOR_SYSTEM_YCGCO: return DAV1D_MC_SMPTE_YCGCO;
+ case PL_COLOR_SYSTEM_RGB: return DAV1D_MC_IDENTITY;
+ case PL_COLOR_SYSTEM_XYZ: return DAV1D_MC_IDENTITY;
+ case PL_COLOR_SYSTEM_COUNT: abort();
+ }
+
+ return DAV1D_MC_UNKNOWN;
+}
+
+PL_DAV1D_API enum pl_color_levels pl_levels_from_dav1d(int color_range)
+{
+ return color_range ? PL_COLOR_LEVELS_FULL : PL_COLOR_LEVELS_LIMITED;
+}
+
+PL_DAV1D_API int pl_levels_to_dav1d(enum pl_color_levels levels)
+{
+ return levels == PL_COLOR_LEVELS_FULL;
+}
+
+PL_DAV1D_API enum pl_color_primaries pl_primaries_from_dav1d(enum Dav1dColorPrimaries prim)
+{
+ switch (prim) {
+ case DAV1D_COLOR_PRI_BT709: return PL_COLOR_PRIM_BT_709;
+ case DAV1D_COLOR_PRI_UNKNOWN: return PL_COLOR_PRIM_UNKNOWN;
+ case DAV1D_COLOR_PRI_RESERVED: return PL_COLOR_PRIM_UNKNOWN;
+ case DAV1D_COLOR_PRI_BT470M: return PL_COLOR_PRIM_BT_470M;
+ case DAV1D_COLOR_PRI_BT470BG: return PL_COLOR_PRIM_BT_601_625;
+ case DAV1D_COLOR_PRI_BT601: return PL_COLOR_PRIM_BT_601_525;
+ case DAV1D_COLOR_PRI_SMPTE240: return PL_COLOR_PRIM_BT_601_525;
+ case DAV1D_COLOR_PRI_FILM: return PL_COLOR_PRIM_FILM_C;
+ case DAV1D_COLOR_PRI_BT2020: return PL_COLOR_PRIM_BT_2020;
+ case DAV1D_COLOR_PRI_XYZ: return PL_COLOR_PRIM_UNKNOWN;
+ case DAV1D_COLOR_PRI_SMPTE431: return PL_COLOR_PRIM_DCI_P3;
+ case DAV1D_COLOR_PRI_SMPTE432: return PL_COLOR_PRIM_DISPLAY_P3;
+ case DAV1D_COLOR_PRI_EBU3213: return PL_COLOR_PRIM_EBU_3213;
+ }
+
+ return PL_COLOR_PRIM_UNKNOWN;
+}
+
+PL_DAV1D_API enum Dav1dColorPrimaries pl_primaries_to_dav1d(enum pl_color_primaries prim)
+{
+ switch (prim) {
+ case PL_COLOR_PRIM_UNKNOWN: return DAV1D_COLOR_PRI_UNKNOWN;
+ case PL_COLOR_PRIM_BT_601_525: return DAV1D_COLOR_PRI_BT601;
+ case PL_COLOR_PRIM_BT_601_625: return DAV1D_COLOR_PRI_BT470BG;
+ case PL_COLOR_PRIM_BT_709: return DAV1D_COLOR_PRI_BT709;
+ case PL_COLOR_PRIM_BT_470M: return DAV1D_COLOR_PRI_BT470M;
+ case PL_COLOR_PRIM_EBU_3213: return DAV1D_COLOR_PRI_EBU3213;
+ case PL_COLOR_PRIM_BT_2020: return DAV1D_COLOR_PRI_BT2020;
+ case PL_COLOR_PRIM_APPLE: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_ADOBE: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_PRO_PHOTO: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_CIE_1931: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_DCI_P3: return DAV1D_COLOR_PRI_SMPTE431;
+ case PL_COLOR_PRIM_DISPLAY_P3: return DAV1D_COLOR_PRI_SMPTE432;
+ case PL_COLOR_PRIM_V_GAMUT: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_S_GAMUT: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_FILM_C: return DAV1D_COLOR_PRI_FILM;
+ case PL_COLOR_PRIM_ACES_AP0: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_ACES_AP1: return DAV1D_COLOR_PRI_UNKNOWN; // missing
+ case PL_COLOR_PRIM_COUNT: abort();
+ }
+
+ return DAV1D_COLOR_PRI_UNKNOWN;
+}
+
+PL_DAV1D_API enum pl_color_transfer pl_transfer_from_dav1d(enum Dav1dTransferCharacteristics trc)
+{
+ switch (trc) {
+ case DAV1D_TRC_BT709: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_UNKNOWN: return PL_COLOR_TRC_UNKNOWN;
+ case DAV1D_TRC_BT470M: return PL_COLOR_TRC_GAMMA22;
+ case DAV1D_TRC_BT470BG: return PL_COLOR_TRC_GAMMA28;
+ case DAV1D_TRC_BT601: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_SMPTE240: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_LINEAR: return PL_COLOR_TRC_LINEAR;
+ case DAV1D_TRC_LOG100: return PL_COLOR_TRC_UNKNOWN; // missing
+ case DAV1D_TRC_LOG100_SQRT10: return PL_COLOR_TRC_UNKNOWN; // missing
+ case DAV1D_TRC_IEC61966: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_BT1361: return PL_COLOR_TRC_BT_1886; // ETOF != OETF
+ case DAV1D_TRC_SRGB: return PL_COLOR_TRC_SRGB;
+ case DAV1D_TRC_BT2020_10BIT: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_BT2020_12BIT: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case DAV1D_TRC_SMPTE2084: return PL_COLOR_TRC_PQ;
+ case DAV1D_TRC_SMPTE428: return PL_COLOR_TRC_ST428;
+ case DAV1D_TRC_HLG: return PL_COLOR_TRC_HLG;
+ case DAV1D_TRC_RESERVED: abort();
+ }
+
+ return PL_COLOR_TRC_UNKNOWN;
+}
+
+PL_DAV1D_API enum Dav1dTransferCharacteristics pl_transfer_to_dav1d(enum pl_color_transfer trc)
+{
+ switch (trc) {
+ case PL_COLOR_TRC_UNKNOWN: return DAV1D_TRC_UNKNOWN;
+ case PL_COLOR_TRC_BT_1886: return DAV1D_TRC_BT709; // EOTF != OETF
+ case PL_COLOR_TRC_SRGB: return DAV1D_TRC_SRGB;
+ case PL_COLOR_TRC_LINEAR: return DAV1D_TRC_LINEAR;
+ case PL_COLOR_TRC_GAMMA18: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_GAMMA20: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_GAMMA22: return DAV1D_TRC_BT470M;
+ case PL_COLOR_TRC_GAMMA24: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_GAMMA26: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_GAMMA28: return DAV1D_TRC_BT470BG;
+ case PL_COLOR_TRC_ST428: return DAV1D_TRC_SMPTE428;
+ case PL_COLOR_TRC_PRO_PHOTO: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_PQ: return DAV1D_TRC_SMPTE2084;
+ case PL_COLOR_TRC_HLG: return DAV1D_TRC_HLG;
+ case PL_COLOR_TRC_V_LOG: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_S_LOG1: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_S_LOG2: return DAV1D_TRC_UNKNOWN; // missing
+ case PL_COLOR_TRC_COUNT: abort();
+ }
+
+ return DAV1D_TRC_UNKNOWN;
+}
+
+PL_DAV1D_API enum pl_chroma_location pl_chroma_from_dav1d(enum Dav1dChromaSamplePosition loc)
+{
+ switch (loc) {
+ case DAV1D_CHR_UNKNOWN: return PL_CHROMA_UNKNOWN;
+ case DAV1D_CHR_VERTICAL: return PL_CHROMA_LEFT;
+ case DAV1D_CHR_COLOCATED: return PL_CHROMA_TOP_LEFT;
+ }
+
+ return PL_CHROMA_UNKNOWN;
+}
+
+PL_DAV1D_API enum Dav1dChromaSamplePosition pl_chroma_to_dav1d(enum pl_chroma_location loc)
+{
+ switch (loc) {
+ case PL_CHROMA_UNKNOWN: return DAV1D_CHR_UNKNOWN;
+ case PL_CHROMA_LEFT: return DAV1D_CHR_VERTICAL;
+ case PL_CHROMA_CENTER: return DAV1D_CHR_UNKNOWN; // missing
+ case PL_CHROMA_TOP_LEFT: return DAV1D_CHR_COLOCATED;
+ case PL_CHROMA_TOP_CENTER: return DAV1D_CHR_UNKNOWN; // missing
+ case PL_CHROMA_BOTTOM_LEFT: return DAV1D_CHR_UNKNOWN; // missing
+ case PL_CHROMA_BOTTOM_CENTER: return DAV1D_CHR_UNKNOWN; // missing
+ case PL_CHROMA_COUNT: abort();
+ }
+
+ return DAV1D_CHR_UNKNOWN;
+}
+
+static inline float pl_fixed24_8(uint32_t n)
+{
+ return (float) n / (1 << 8);
+}
+
+static inline float pl_fixed18_14(uint32_t n)
+{
+ return (float) n / (1 << 14);
+}
+
+static inline float pl_fixed0_16(uint16_t n)
+{
+ return (float) n / (1 << 16);
+}
+
+// Align to a power of 2
+#define PL_ALIGN2(x, align) (((x) + (align) - 1) & ~((align) - 1))
+
+PL_DAV1D_API void pl_frame_from_dav1dpicture(struct pl_frame *out,
+ const Dav1dPicture *picture)
+{
+ const Dav1dSequenceHeader *seq_hdr = picture->seq_hdr;
+ int num_planes;
+ switch (picture->p.layout) {
+ case DAV1D_PIXEL_LAYOUT_I400:
+ num_planes = 1;
+ break;
+ case DAV1D_PIXEL_LAYOUT_I420:
+ case DAV1D_PIXEL_LAYOUT_I422:
+ case DAV1D_PIXEL_LAYOUT_I444:
+ num_planes = 3;
+ break;
+ default: abort();
+ }
+
+ *out = (struct pl_frame) {
+ .num_planes = num_planes,
+ .planes = {
+ // Components are always in order, which makes things easy
+ {
+ .components = 1,
+ .component_mapping = {0},
+ }, {
+ .components = 1,
+ .component_mapping = {1},
+ }, {
+ .components = 1,
+ .component_mapping = {2},
+ },
+ },
+ .crop = {
+ 0, 0, picture->p.w, picture->p.h,
+ },
+ .color = {
+ .primaries = pl_primaries_from_dav1d(seq_hdr->pri),
+ .transfer = pl_transfer_from_dav1d(seq_hdr->trc),
+ },
+ .repr = {
+ .sys = pl_system_from_dav1d(seq_hdr->mtrx),
+ .levels = pl_levels_from_dav1d(seq_hdr->color_range),
+ .bits = {
+ .sample_depth = PL_ALIGN2(picture->p.bpc, 8),
+ .color_depth = picture->p.bpc,
+ },
+ },
+ };
+
+ if (seq_hdr->mtrx == DAV1D_MC_ICTCP && seq_hdr->trc == DAV1D_TRC_HLG) {
+
+ // dav1d makes no distinction between PQ and HLG ICtCp, so we need
+ // to manually fix it in the case that we have HLG ICtCp data.
+ out->repr.sys = PL_COLOR_SYSTEM_BT_2100_HLG;
+
+ } else if (seq_hdr->mtrx == DAV1D_MC_IDENTITY &&
+ seq_hdr->pri == DAV1D_COLOR_PRI_XYZ)
+ {
+
+ // dav1d handles this as a special case, but doesn't provide an
+ // explicit flag for it either, so we have to resort to this ugly hack,
+ // even though CIE 1931 RGB *is* a valid thing in principle!
+ out->repr.sys= PL_COLOR_SYSTEM_XYZ;
+
+ } else if (!out->repr.sys) {
+
+ // PL_COLOR_SYSTEM_UNKNOWN maps to RGB, so hard-code this one
+ out->repr.sys = pl_color_system_guess_ycbcr(picture->p.w, picture->p.h);
+ }
+
+ const Dav1dContentLightLevel *cll = picture->content_light;
+ if (cll) {
+ out->color.hdr.max_cll = cll->max_content_light_level;
+ out->color.hdr.max_fall = cll->max_frame_average_light_level;
+ }
+
+ // This overrides the CLL values above, if both are present
+ const Dav1dMasteringDisplay *md = picture->mastering_display;
+ if (md) {
+ out->color.hdr.max_luma = pl_fixed24_8(md->max_luminance);
+ out->color.hdr.min_luma = pl_fixed18_14(md->min_luminance);
+ out->color.hdr.prim = (struct pl_raw_primaries) {
+ .red.x = pl_fixed0_16(md->primaries[0][0]),
+ .red.y = pl_fixed0_16(md->primaries[0][1]),
+ .green.x = pl_fixed0_16(md->primaries[1][0]),
+ .green.y = pl_fixed0_16(md->primaries[1][1]),
+ .blue.x = pl_fixed0_16(md->primaries[2][0]),
+ .blue.y = pl_fixed0_16(md->primaries[2][1]),
+ .white.x = pl_fixed0_16(md->white_point[0]),
+ .white.y = pl_fixed0_16(md->white_point[1]),
+ };
+ }
+
+ if (picture->frame_hdr->film_grain.present) {
+ const Dav1dFilmGrainData *fg = &picture->frame_hdr->film_grain.data;
+ out->film_grain = (struct pl_film_grain_data) {
+ .type = PL_FILM_GRAIN_AV1,
+ .seed = fg->seed,
+ .params.av1 = {
+ .num_points_y = fg->num_y_points,
+ .chroma_scaling_from_luma = fg->chroma_scaling_from_luma,
+ .num_points_uv = { fg->num_uv_points[0], fg->num_uv_points[1] },
+ .scaling_shift = fg->scaling_shift,
+ .ar_coeff_lag = fg->ar_coeff_lag,
+ .ar_coeff_shift = (int) fg->ar_coeff_shift,
+ .grain_scale_shift = fg->grain_scale_shift,
+ .uv_mult = { fg->uv_mult[0], fg->uv_mult[1] },
+ .uv_mult_luma = { fg->uv_luma_mult[0], fg->uv_luma_mult[1] },
+ .uv_offset = { fg->uv_offset[0], fg->uv_offset[1] },
+ .overlap = fg->overlap_flag,
+ },
+ };
+
+ struct pl_av1_grain_data *av1 = &out->film_grain.params.av1;
+ memcpy(av1->points_y, fg->y_points, sizeof(av1->points_y));
+ memcpy(av1->points_uv, fg->uv_points, sizeof(av1->points_uv));
+ memcpy(av1->ar_coeffs_y, fg->ar_coeffs_y, sizeof(av1->ar_coeffs_y));
+ memcpy(av1->ar_coeffs_uv[0], fg->ar_coeffs_uv[0], sizeof(av1->ar_coeffs_uv[0]));
+ memcpy(av1->ar_coeffs_uv[1], fg->ar_coeffs_uv[1], sizeof(av1->ar_coeffs_uv[1]));
+ }
+
+ switch (picture->p.layout) {
+ case DAV1D_PIXEL_LAYOUT_I400:
+ case DAV1D_PIXEL_LAYOUT_I444:
+ break;
+ case DAV1D_PIXEL_LAYOUT_I420:
+ case DAV1D_PIXEL_LAYOUT_I422:
+ // Only set the chroma location for definitely subsampled images
+ pl_frame_set_chroma_location(out, pl_chroma_from_dav1d(seq_hdr->chr));
+ break;
+ }
+}
+
+PL_DAV1D_API void pl_swapchain_colors_from_dav1dpicture(struct pl_swapchain_colors *out_colors,
+ const Dav1dPicture *picture)
+{
+ struct pl_frame frame;
+ pl_frame_from_dav1dpicture(&frame, picture);
+
+ *out_colors = (struct pl_swapchain_colors) {
+ .primaries = frame.color.primaries,
+ .transfer = frame.color.transfer,
+ };
+
+ const Dav1dContentLightLevel *cll = picture->content_light;
+ if (cll) {
+ out_colors->hdr.max_cll = cll->max_content_light_level;
+ out_colors->hdr.max_fall = cll->max_frame_average_light_level;
+ }
+
+ const Dav1dMasteringDisplay *md = picture->mastering_display;
+ if (md) {
+ out_colors->hdr.min_luma = pl_fixed18_14(md->min_luminance);
+ out_colors->hdr.max_luma = pl_fixed24_8(md->max_luminance);
+ out_colors->hdr.prim.red.x = pl_fixed0_16(md->primaries[0][0]);
+ out_colors->hdr.prim.red.y = pl_fixed0_16(md->primaries[0][1]);
+ out_colors->hdr.prim.green.x = pl_fixed0_16(md->primaries[1][0]);
+ out_colors->hdr.prim.green.y = pl_fixed0_16(md->primaries[1][1]);
+ out_colors->hdr.prim.blue.x = pl_fixed0_16(md->primaries[2][0]);
+ out_colors->hdr.prim.blue.y = pl_fixed0_16(md->primaries[2][1]);
+ out_colors->hdr.prim.white.x = pl_fixed0_16(md->white_point[0]);
+ out_colors->hdr.prim.white.y = pl_fixed0_16(md->white_point[1]);
+ }
+}
+
+#define PL_MAGIC0 0x2c2a1269
+#define PL_MAGIC1 0xc6d02577
+
+struct pl_dav1dalloc {
+ uint32_t magic[2];
+ pl_gpu gpu;
+ pl_buf buf;
+};
+
+struct pl_dav1dref {
+ Dav1dPicture pic;
+ uint8_t count;
+};
+
+static void pl_dav1dpicture_unref(void *priv)
+{
+ struct pl_dav1dref *ref = priv;
+ if (--ref->count == 0) {
+ dav1d_picture_unref(&ref->pic);
+ free(ref);
+ }
+}
+
+PL_DAV1D_API bool pl_upload_dav1dpicture(pl_gpu gpu,
+ struct pl_frame *out,
+ pl_tex tex[3],
+ const struct pl_dav1d_upload_params *params)
+{
+ Dav1dPicture *pic = params->picture;
+ pl_frame_from_dav1dpicture(out, pic);
+ if (!params->film_grain)
+ out->film_grain.type = PL_FILM_GRAIN_NONE;
+
+ const int bytes = (pic->p.bpc + 7) / 8; // rounded up
+ int sub_x = 0, sub_y = 0;
+ switch (pic->p.layout) {
+ case DAV1D_PIXEL_LAYOUT_I400:
+ case DAV1D_PIXEL_LAYOUT_I444:
+ break;
+ case DAV1D_PIXEL_LAYOUT_I420:
+ sub_x = sub_y = 1;
+ break;
+ case DAV1D_PIXEL_LAYOUT_I422:
+ sub_x = 1;
+ break;
+ }
+
+ struct pl_plane_data data[3] = {
+ {
+ // Y plane
+ .type = PL_FMT_UNORM,
+ .width = pic->p.w,
+ .height = pic->p.h,
+ .pixel_stride = bytes,
+ .component_size = {bytes * 8},
+ .component_map = {0},
+ }, {
+ // U plane
+ .type = PL_FMT_UNORM,
+ .width = pic->p.w >> sub_x,
+ .height = pic->p.h >> sub_y,
+ .pixel_stride = bytes,
+ .component_size = {bytes * 8},
+ .component_map = {1},
+ }, {
+ // V plane
+ .type = PL_FMT_UNORM,
+ .width = pic->p.w >> sub_x,
+ .height = pic->p.h >> sub_y,
+ .pixel_stride = bytes,
+ .component_size = {bytes * 8},
+ .component_map = {2},
+ },
+ };
+
+ pl_buf buf = NULL;
+ struct pl_dav1dalloc *alloc = params->gpu_allocated ? pic->allocator_data : NULL;
+ struct pl_dav1dref *ref = NULL;
+
+ if (alloc && alloc->magic[0] == PL_MAGIC0 && alloc->magic[1] == PL_MAGIC1) {
+ // Re-use pre-allocated buffers directly
+ assert(alloc->gpu == gpu);
+ buf = alloc->buf;
+ } else if (params->asynchronous && gpu->limits.callbacks) {
+ ref = malloc(sizeof(*ref));
+ if (!ref)
+ return false;
+ memcpy(&ref->pic, pic, sizeof(Dav1dPicture));
+ ref->count = out->num_planes;
+ }
+
+ for (int p = 0; p < out->num_planes; p++) {
+ ptrdiff_t stride = p > 0 ? pic->stride[1] : pic->stride[0];
+ if (stride < 0) {
+ data[p].pixels = (uint8_t *) pic->data[p] + stride * (data[p].height - 1);
+ data[p].row_stride = -stride;
+ out->planes[p].flipped = true;
+ } else {
+ data[p].pixels = pic->data[p];
+ data[p].row_stride = stride;
+ }
+
+ if (buf) {
+ data[p].buf = buf;
+ data[p].buf_offset = (uintptr_t) data[p].pixels - (uintptr_t) buf->data;
+ data[p].pixels = NULL;
+ } else if (ref) {
+ data[p].priv = ref;
+ data[p].callback = pl_dav1dpicture_unref;
+ }
+
+ if (!pl_upload_plane(gpu, &out->planes[p], &tex[p], &data[p])) {
+ free(ref);
+ return false;
+ }
+ }
+
+ if (params->asynchronous) {
+ if (ref) {
+ *pic = (Dav1dPicture) {0};
+ } else {
+ dav1d_picture_unref(pic);
+ }
+ }
+
+ return true;
+}
+
+PL_DAV1D_API int pl_allocate_dav1dpicture(Dav1dPicture *p, void *cookie)
+{
+ pl_gpu gpu = cookie;
+ if (!gpu->limits.max_mapped_size || !gpu->limits.host_cached ||
+ !gpu->limits.buf_transfer)
+ {
+ return DAV1D_ERR(ENOTSUP);
+ }
+
+ // Copied from dav1d_default_picture_alloc
+ const int hbd = p->p.bpc > 8;
+ const int aligned_w = PL_ALIGN2(p->p.w, 128);
+ const int aligned_h = PL_ALIGN2(p->p.h, 128);
+ const int has_chroma = p->p.layout != DAV1D_PIXEL_LAYOUT_I400;
+ const int ss_ver = p->p.layout == DAV1D_PIXEL_LAYOUT_I420;
+ const int ss_hor = p->p.layout != DAV1D_PIXEL_LAYOUT_I444;
+ p->stride[0] = aligned_w << hbd;
+ p->stride[1] = has_chroma ? (aligned_w >> ss_hor) << hbd : 0;
+
+ // Align strides up to multiples of the GPU performance hints
+ p->stride[0] = PL_ALIGN2(p->stride[0], gpu->limits.align_tex_xfer_pitch);
+ p->stride[1] = PL_ALIGN2(p->stride[1], gpu->limits.align_tex_xfer_pitch);
+
+ // Aligning offsets to 4 also implicitly aligns to the texel alignment (1 or 2)
+ size_t off_align = PL_ALIGN2(gpu->limits.align_tex_xfer_offset, 4);
+ const size_t y_sz = PL_ALIGN2(p->stride[0] * aligned_h, off_align);
+ const size_t uv_sz = PL_ALIGN2(p->stride[1] * (aligned_h >> ss_ver), off_align);
+
+ // The extra DAV1D_PICTURE_ALIGNMENTs are to brute force plane alignment,
+ // even in the case that the driver gives us insane alignments
+ const size_t pic_size = y_sz + 2 * uv_sz;
+ const size_t total_size = pic_size + DAV1D_PICTURE_ALIGNMENT * 4;
+
+ // Validate size limitations
+ if (total_size > gpu->limits.max_mapped_size)
+ return DAV1D_ERR(ENOMEM);
+
+ pl_buf buf = pl_buf_create(gpu, pl_buf_params(
+ .size = total_size,
+ .host_mapped = true,
+ .memory_type = PL_BUF_MEM_HOST,
+ ));
+
+ if (!buf)
+ return DAV1D_ERR(ENOMEM);
+
+ struct pl_dav1dalloc *alloc = malloc(sizeof(struct pl_dav1dalloc));
+ if (!alloc) {
+ pl_buf_destroy(gpu, &buf);
+ return DAV1D_ERR(ENOMEM);
+ }
+
+ *alloc = (struct pl_dav1dalloc) {
+ .magic = { PL_MAGIC0, PL_MAGIC1 },
+ .gpu = gpu,
+ .buf = buf,
+ };
+
+ assert(buf->data);
+ uintptr_t base = (uintptr_t) buf->data, data[3];
+ data[0] = PL_ALIGN2(base, DAV1D_PICTURE_ALIGNMENT);
+ data[1] = PL_ALIGN2(data[0] + y_sz, DAV1D_PICTURE_ALIGNMENT);
+ data[2] = PL_ALIGN2(data[1] + uv_sz, DAV1D_PICTURE_ALIGNMENT);
+
+ p->allocator_data = alloc;
+ p->data[0] = (void *) data[0];
+ p->data[1] = (void *) data[1];
+ p->data[2] = (void *) data[2];
+ return 0;
+}
+
+PL_DAV1D_API void pl_release_dav1dpicture(Dav1dPicture *p, void *cookie)
+{
+ struct pl_dav1dalloc *alloc = p->allocator_data;
+ if (!alloc)
+ return;
+
+ assert(alloc->magic[0] == PL_MAGIC0);
+ assert(alloc->magic[1] == PL_MAGIC1);
+ assert(alloc->gpu == cookie);
+ pl_buf_destroy(alloc->gpu, &alloc->buf);
+ free(alloc);
+
+ p->data[0] = p->data[1] = p->data[2] = p->allocator_data = NULL;
+}
+
+#undef PL_ALIGN2
+#undef PL_MAGIC0
+#undef PL_MAGIC1
+
+#endif // LIBPLACEBO_DAV1D_H_
diff --git a/src/include/libplacebo/utils/dolbyvision.h b/src/include/libplacebo/utils/dolbyvision.h
new file mode 100644
index 0000000..6d4d72e
--- /dev/null
+++ b/src/include/libplacebo/utils/dolbyvision.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_DOLBYVISION_H_
+#define LIBPLACEBO_DOLBYVISION_H_
+
+#include <libplacebo/colorspace.h>
+
+PL_API_BEGIN
+
+// Parses the Dolby Vision RPU, and sets the `pl_hdr_metadata` dynamic
+// brightness metadata fields accordingly.
+//
+// Note: requires `PL_HAVE_LIBDOVI` to be defined, no-op otherwise.
+PL_API void pl_hdr_metadata_from_dovi_rpu(struct pl_hdr_metadata *out,
+ const uint8_t *buf, size_t size);
+
+PL_API_END
+
+#endif // LIBPLACEBO_DOLBYVISION_H_
diff --git a/src/include/libplacebo/utils/frame_queue.h b/src/include/libplacebo/utils/frame_queue.h
new file mode 100644
index 0000000..2a9c90c
--- /dev/null
+++ b/src/include/libplacebo/utils/frame_queue.h
@@ -0,0 +1,230 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_FRAME_QUEUE_H
+#define LIBPLACEBO_FRAME_QUEUE_H
+
+#include <libplacebo/renderer.h>
+#include <libplacebo/shaders/deinterlacing.h>
+
+PL_API_BEGIN
+
+// An abstraction layer for automatically turning a conceptual stream of
+// (frame, pts) pairs, as emitted by a decoder or filter graph, into a
+// `pl_frame_mix` suitable for `pl_render_image_mix`.
+//
+// This API ensures that minimal work is performed (e.g. only mapping frames
+// that are actually required), while also satisfying the requirements
+// of any configured frame mixer.
+//
+// Thread-safety: Safe
+typedef struct pl_queue_t *pl_queue;
+
+enum pl_queue_status {
+ PL_QUEUE_OK, // success
+ PL_QUEUE_EOF, // no more frames are available
+ PL_QUEUE_MORE, // more frames needed, but not (yet) available
+ PL_QUEUE_ERR = -1, // some unknown error occurred while retrieving frames
+};
+
+struct pl_source_frame {
+ // The frame's presentation timestamp, in seconds relative to the first
+ // frame. These must be monotonically increasing for subsequent frames.
+ // To implement a discontinuous jump, users must explicitly reset the
+ // frame queue with `pl_queue_reset` and restart from PTS 0.0.
+ double pts;
+
+ // The frame's duration. This is not needed in normal scenarios, as the
+ // FPS can be inferred from the `pts` values themselves. Providing it
+ // only helps initialize the value for initial frames, which can smooth
+ // out the interpolation weights. Its use is also highly recommended
+ // when displaying interlaced frames. (Optional)
+ float duration;
+
+ // If set to something other than PL_FIELD_NONE, this source frame is
+ // marked as interlaced. It will be split up into two separate frames
+ // internally, and exported to the resulting `pl_frame_mix` as a pair of
+ // fields, referencing the corresponding previous and next frames. The
+ // first field will have the same PTS as `pts`, and the second field will
+ // be inserted at the timestamp `pts + duration/2`.
+ //
+ // Note: As a result of FPS estimates being unreliable around streams with
+ // mixed FPS (or when mixing interlaced and progressive frames), it's
+ // highly recommended to always specify a valid `duration` for interlaced
+ // frames.
+ enum pl_field first_field;
+
+ // Abstract frame data itself. To allow mapping frames only when they're
+ // actually needed, frames use a lazy representation. The provided
+ // callbacks will be invoked to interface with it.
+ void *frame_data;
+
+ // This will be called to map the frame to the GPU, only if needed.
+ //
+ // `tex` is a pointer to an array of 4 texture objects (or NULL), which
+ // *may* serve as backing storage for the texture being mapped. These are
+ // intended to be recreated by `map`, e.g. using `pl_tex_recreate` or
+ // `pl_upload_plane` as appropriate. They will be managed internally by
+ // `pl_queue` and destroyed at some unspecified future point in time.
+ //
+ // Note: If `map` fails, it will not be retried, nor will `discard` be run.
+ // The user should clean up state in this case.
+ bool (*map)(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
+ struct pl_frame *out_frame);
+
+ // If present, this will be called on frames that are done being used by
+ // `pl_queue`. This may be useful to e.g. unmap textures backed by external
+ // APIs such as hardware decoders. (Optional)
+ void (*unmap)(pl_gpu gpu, struct pl_frame *frame, const struct pl_source_frame *src);
+
+ // This function will be called for frames that are deemed unnecessary
+ // (e.g. never became visible) and should instead be cleanly freed.
+ // (Optional)
+ void (*discard)(const struct pl_source_frame *src);
+};
+
+// Create a new, empty frame queue.
+//
+// It's highly recommended to fully render a single frame with `pts == 0.0`,
+// and flush the GPU pipeline with `pl_gpu_finish`, prior to starting the timed
+// playback loop.
+PL_API pl_queue pl_queue_create(pl_gpu gpu);
+PL_API void pl_queue_destroy(pl_queue *queue);
+
+// Explicitly clear the queue. This is essentially equivalent to destroying
+// and recreating the queue, but preserves any internal memory allocations.
+//
+// Note: Calling `pl_queue_reset` may block, if another thread is currently
+// blocked on a different `pl_queue_*` call.
+PL_API void pl_queue_reset(pl_queue queue);
+
+// Explicitly push a frame. This is an alternative way to feed the frame queue
+// with incoming frames, the other method being the asynchronous callback
+// specified as `pl_queue_params.get_frame`. Both methods may be used
+// simultaneously, although providing `get_frame` is recommended since it
+// avoids the risk of the queue underrunning.
+//
+// When no more frames are available, call this function with `frame == NULL`
+// to indicate EOF and begin draining the frame queue.
+PL_API void pl_queue_push(pl_queue queue, const struct pl_source_frame *frame);
+
+// Variant of `pl_queue_push` that blocks while the queue is judged
+// (internally) to be "too full". This is useful for asynchronous decoder loops
+// in order to prevent the queue from exhausting available RAM if frames are
+// decoded significantly faster than they're displayed.
+//
+// The given `timeout` parameter specifies how long to wait before giving up,
+// in nanoseconds. Returns false if this timeout was reached.
+PL_API bool pl_queue_push_block(pl_queue queue, uint64_t timeout,
+ const struct pl_source_frame *frame);
+
+struct pl_queue_params {
+ // The PTS of the frame that will be rendered. This should be set to the
+ // timestamp (in seconds) of the next vsync, relative to the initial frame.
+ //
+ // These must be monotonically increasing. To implement a discontinuous
+ // jump, users must explicitly reset the frame queue with `pl_queue_reset`
+ // and restart from PTS 0.0.
+ double pts;
+
+ // The radius of the configured mixer. This should be set to the value
+ // as returned by `pl_frame_mix_radius`.
+ float radius;
+
+ // The estimated duration of a vsync, in seconds. This will only be used as
+ // a hint, the true value will be estimated by comparing `pts` timestamps
+ // between calls to `pl_queue_update`. (Optional)
+ float vsync_duration;
+
+ // If the difference between the (estimated) vsync duration and the
+ // (measured) frame duration is smaller than this threshold, silently
+ // disable interpolation and switch to ZOH semantics instead.
+ //
+ // For example, a value of 0.01 allows the FPS to differ by up to 1%
+ // without being interpolated. Note that this will result in a continuous
+ // phase drift unless also compensated for by the user, which will
+ // eventually resulted in a dropped or duplicated frame. (Though this can
+ // be preferable to seeing that same phase drift result in a temporally
+ // smeared image)
+ float interpolation_threshold;
+
+ // Specifies how long `pl_queue_update` will wait for frames to become
+ // available, in nanoseconds, before giving up and returning with
+ // QUEUE_MORE.
+ //
+ // If `get_frame` is provided, this value is ignored by `pl_queue` and
+ // should instead be interpreted by the provided callback.
+ uint64_t timeout;
+
+ // This callback will be used to pull new frames from the decoder. It may
+ // block if needed. The user is responsible for setting appropriate time
+ // limits and/or returning and interpreting QUEUE_MORE as sensible.
+ //
+ // Providing this callback is entirely optional. Users can instead choose
+ // to manually feed the frame queue with new frames using `pl_queue_push`.
+ enum pl_queue_status (*get_frame)(struct pl_source_frame *out_frame,
+ const struct pl_queue_params *params);
+ void *priv;
+};
+
+#define pl_queue_params(...) (&(struct pl_queue_params) { __VA_ARGS__ })
+
+// Advance the frame queue's internal state to the target timestamp. Any frames
+// which are no longer needed (i.e. too far in the past) are automatically
+// unmapped and evicted. Any future frames which are needed to fill the queue
+// must either have been pushed in advance, or will be requested using the
+// provided `get_frame` callback. If you call this on `out_mix == NULL`, the
+// queue state will advance, but no frames will be mapped.
+//
+// This function may return with PL_QUEUE_MORE, in which case the user may wish
+// to ensure more frames are available and then re-run this function with the
+// same parameters. In this case, `out_mix` is still written to, but it may be
+// incomplete (or even contain no frames at all). Additionally, when the source
+// contains interlaced frames (see `pl_source_frame.first_field`), this
+// function may return with PL_QUEUE_MORE if a frame is missing references to
+// a future frame.
+//
+// The resulting mix of frames in `out_mix` will represent the neighbourhood of
+// the target timestamp, and can be passed to `pl_render_image_mix` as-is.
+//
+// Note: `out_mix` will only remain valid until the next call to
+// `pl_queue_update` or `pl_queue_reset`.
+PL_API enum pl_queue_status pl_queue_update(pl_queue queue, struct pl_frame_mix *out_mix,
+ const struct pl_queue_params *params);
+
+// Returns a pl_queue's internal estimates for FPS and VPS (vsyncs per second).
+// Returns 0.0 if no estimate is available.
+PL_API float pl_queue_estimate_fps(pl_queue queue);
+PL_API float pl_queue_estimate_vps(pl_queue queue);
+
+// Returns the number of frames currently contained in a pl_queue.
+PL_API int pl_queue_num_frames(pl_queue queue);
+
+// Inspect the contents of the Nth queued frame. Returns false if `idx` is
+// out of range.
+//
+// Warning: No guarantee is made to ensure validity of `out->frame_data`
+// after this call. In particular, pl_queue_* calls made from another thread
+// may call `discard()` on the frame in question. The user bears responsibility
+// to avoid accessing `out->frame_data` in a multi-threaded scenario unless
+// an external guarantee can be made that the frame won't be dequeued until
+// it is done being used by the user.
+PL_API bool pl_queue_peek(pl_queue queue, int idx, struct pl_source_frame *out);
+
+PL_API_END
+
+#endif // LIBPLACEBO_FRAME_QUEUE_H
diff --git a/src/include/libplacebo/utils/libav.h b/src/include/libplacebo/utils/libav.h
new file mode 100644
index 0000000..91f3dd8
--- /dev/null
+++ b/src/include/libplacebo/utils/libav.h
@@ -0,0 +1,284 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_LIBAV_H_
+#define LIBPLACEBO_LIBAV_H_
+
+#include <libplacebo/config.h>
+#include <libplacebo/gpu.h>
+#include <libplacebo/shaders/deinterlacing.h>
+#include <libplacebo/utils/upload.h>
+
+#if defined(__cplusplus) && !defined(PL_LIBAV_IMPLEMENTATION)
+# define PL_LIBAV_API
+# define PL_LIBAV_IMPLEMENTATION 0
+# warning Remember to include this file with a PL_LIBAV_IMPLEMENTATION set to 1 in \
+ C translation unit to provide implementation. Suppress this warning by \
+ defining PL_LIBAV_IMPLEMENTATION to 0 in C++ files.
+#elif !defined(PL_LIBAV_IMPLEMENTATION)
+# define PL_LIBAV_API static inline
+# define PL_LIBAV_IMPLEMENTATION 1
+#else
+# define PL_LIBAV_API
+#endif
+
+PL_API_BEGIN
+
+#include <libavformat/avformat.h>
+#include <libavutil/frame.h>
+#include <libavutil/version.h>
+#include <libavcodec/avcodec.h>
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 16, 100) && defined(PL_HAVE_DOVI)
+# define PL_HAVE_LAV_DOLBY_VISION
+# include <libavutil/dovi_meta.h>
+#endif
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 61, 100)
+# define PL_HAVE_LAV_FILM_GRAIN
+# include <libavutil/film_grain_params.h>
+#endif
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 25, 100)
+# define PL_HAVE_LAV_HDR
+# include <libavutil/hdr_dynamic_metadata.h>
+# include <libavutil/mastering_display_metadata.h>
+#endif
+
+//------------------------------------------------------------------------
+// Important note: For support for AVVkFrame, which depends on <vulkan.h>,
+// users *SHOULD* include <vulkan/vulkan.h> manually before this header.
+//------------------------------------------------------------------------
+
+
+// Fill in the details of a `pl_frame` from an AVFrame. This function will
+// explicitly clear `out_frame`, setting all extra fields to 0. After this
+// function returns, the only missing data is information related to the plane
+// texture itself (`planes[N].texture`), as well as any overlays (e.g.
+// subtitles).
+//
+// Note: If the AVFrame contains an embedded ICC profile or H.274 film grain
+// metadata, the resulting `out_image->profile` will reference this pointer,
+// meaning that in general, the `pl_frame` is only guaranteed to be valid as
+// long as the AVFrame is not freed.
+//
+// Note: This will ignore Dolby Vision metadata by default (to avoid leaking
+// memory), either switch to pl_map_avframe_ex or do it manually using
+// pl_map_dovi_metadata.
+PL_LIBAV_API void pl_frame_from_avframe(struct pl_frame *out_frame, const AVFrame *frame);
+
+// Deprecated aliases for backwards compatibility
+#define pl_image_from_avframe pl_frame_from_avframe
+#define pl_target_from_avframe pl_frame_from_avframe
+
+// Copy extra metadata from an AVStream to a pl_frame. This should be called
+// after `pl_frame_from_avframe` or `pl_map_avframe` (respectively), and sets
+// metadata associated with stream-level side data. This is needed because
+// FFmpeg rather annoyingly does not propagate stream-level metadata to frames.
+PL_LIBAV_API void pl_frame_copy_stream_props(struct pl_frame *out_frame,
+ const AVStream *stream);
+
+#ifdef PL_HAVE_LAV_HDR
+struct pl_av_hdr_metadata {
+ // All fields are optional and may be passed as `NULL`.
+ const AVMasteringDisplayMetadata *mdm;
+ const AVContentLightMetadata *clm;
+ const AVDynamicHDRPlus *dhp;
+};
+
+// Helper function to update a `pl_hdr_metadata` struct from HDR10/HDR10+
+// metadata in the FFmpeg format. Unspecified/invalid elements will be left
+// uninitialized in `out`.
+PL_LIBAV_API void pl_map_hdr_metadata(struct pl_hdr_metadata *out,
+ const struct pl_av_hdr_metadata *metadata);
+#endif
+
+#ifdef PL_HAVE_LAV_DOLBY_VISION
+// Helper function to map Dolby Vision metadata from the FFmpeg format.
+PL_LIBAV_API void pl_map_dovi_metadata(struct pl_dovi_metadata *out,
+ const AVDOVIMetadata *metadata);
+
+// Helper function to map Dolby Vision metadata from the FFmpeg format
+// to `pl_dovi_metadata`, and adds it to the `pl_frame`.
+// The `pl_frame` colorspace fields and HDR struct are also updated with
+// values from the `AVDOVIMetadata`.
+//
+// Note: The `pl_dovi_metadata` must be allocated externally.
+// Also, currently the metadata is only used if the `AVDOVIRpuDataHeader`
+// `disable_residual_flag` field is not zero and can be checked before allocating.
+PL_LIBAV_API void pl_frame_map_avdovi_metadata(struct pl_frame *out_frame,
+ struct pl_dovi_metadata *dovi,
+ const AVDOVIMetadata *metadata);
+#endif
+
+// Helper function to test if a pixfmt would be supported by the GPU.
+// Essentially, this can be used to check if `pl_map_avframe` would work for a
+// given AVPixelFormat, without actually uploading or allocating anything.
+PL_LIBAV_API bool pl_test_pixfmt(pl_gpu gpu, enum AVPixelFormat pixfmt);
+
+// Variant of `pl_test_pixfmt` that also tests for the given capabilities
+// being present. Note that in the presence of hardware accelerated frames,
+// this cannot be tested without frame-specific information (i.e. swformat),
+// but in practice this should be a non-issue as GPU-native hwformats will
+// probably be fully supported.
+PL_LIBAV_API bool pl_test_pixfmt_caps(pl_gpu gpu, enum AVPixelFormat pixfmt,
+ enum pl_fmt_caps caps);
+
+// Like `pl_frame_from_avframe`, but the texture pointers are also initialized
+// to ensure they have the correct size and format to match the AVframe.
+// Similar in spirit to `pl_recreate_plane`, and the same notes apply. `tex`
+// must be an array of 4 pointers of type `pl_tex`, each either
+// pointing to a valid texture, or NULL. Returns whether successful.
+PL_LIBAV_API bool pl_frame_recreate_from_avframe(pl_gpu gpu, struct pl_frame *out_frame,
+ pl_tex tex[4], const AVFrame *frame);
+
+struct pl_avframe_params {
+ // The AVFrame to map. Required.
+ const AVFrame *frame;
+
+ // Backing textures for frame data. Required for all non-hwdec formats.
+ // This must point to an array of four valid textures (or NULL entries).
+ //
+ // Note: Not cleaned up by `pl_unmap_avframe`. The intent is for users to
+ // re-use this texture array for subsequent frames, to avoid texture
+ // creation/destruction overhead.
+ pl_tex *tex;
+
+ // Also map Dolby Vision metadata (if supported). Note that this also
+ // overrides the colorimetry metadata (forces BT.2020+PQ).
+ bool map_dovi;
+};
+
+#define PL_AVFRAME_DEFAULTS \
+ .map_dovi = true,
+
+#define pl_avframe_params(...) (&(struct pl_avframe_params) { PL_AVFRAME_DEFAULTS __VA_ARGS__ })
+
+// Very high level helper function to take an `AVFrame` and map it to the GPU.
+// The resulting `pl_frame` remains valid until `pl_unmap_avframe` is called,
+// which must be called at some point to clean up state. The `AVFrame` is
+// automatically ref'd and unref'd if needed. Returns whether successful.
+//
+// Note: `out_frame->user_data` points to a privately managed opaque struct
+// and must not be touched by the user.
+PL_LIBAV_API bool pl_map_avframe_ex(pl_gpu gpu, struct pl_frame *out_frame,
+ const struct pl_avframe_params *params);
+PL_LIBAV_API void pl_unmap_avframe(pl_gpu gpu, struct pl_frame *frame);
+
+// Backwards compatibility with previous versions of this API.
+PL_LIBAV_API bool pl_map_avframe(pl_gpu gpu, struct pl_frame *out_frame,
+ pl_tex tex[4], const AVFrame *avframe);
+
+// Return the AVFrame* that a pl_frame was mapped from (via pl_map_avframe_ex)
+// Note: This reference is attached to the `pl_frame` and will get freed by
+// pl_unmap_avframe.
+PL_LIBAV_API AVFrame *pl_get_mapped_avframe(const struct pl_frame *frame);
+
+// Download the texture contents of a `pl_frame` back to a corresponding
+// AVFrame. Blocks until completion.
+//
+// Note: This function performs minimal verification, so incorrect usage will
+// likely result in broken frames. Use `pl_frame_recreate_from_avframe` to
+// ensure matching formats.
+PL_LIBAV_API bool pl_download_avframe(pl_gpu gpu,
+ const struct pl_frame *frame,
+ AVFrame *out_frame);
+
+// Helper functions to update the colorimetry data in an AVFrame based on
+// the values specified in the given color space / color repr / profile.
+//
+// Note: These functions can and will allocate AVFrame side data if needed,
+// in particular to encode HDR metadata in `space.hdr`.
+PL_LIBAV_API void pl_avframe_set_color(AVFrame *frame, struct pl_color_space space);
+PL_LIBAV_API void pl_avframe_set_repr(AVFrame *frame, struct pl_color_repr repr);
+PL_LIBAV_API void pl_avframe_set_profile(AVFrame *frame, struct pl_icc_profile profile);
+
+// Map an AVPixelFormat to an array of pl_plane_data structs. The array must
+// have at least `av_pix_fmt_count_planes(fmt)` elements, but never more than
+// 4. This function leaves `width`, `height` and `row_stride`, as well as the
+// data pointers, uninitialized.
+//
+// If `bits` is non-NULL, this function will attempt aligning the resulting
+// `pl_plane_data` struct for optimal compatibility, placing the resulting
+// `pl_bit_depth` metadata into `bits`.
+//
+// Returns the number of plane structs written to, or 0 on error.
+//
+// Note: This function is usually clumsier to use than the higher-level
+// functions above, but it might have some fringe use cases, for example if
+// the user wants to replace the data buffers by `pl_buf` references in the
+// `pl_plane_data` before uploading it to the GPU.
+PL_LIBAV_API int pl_plane_data_from_pixfmt(struct pl_plane_data data[4],
+ struct pl_bit_encoding *bits,
+ enum AVPixelFormat pix_fmt);
+
+// Callback for AVCodecContext.get_buffer2 that allocates memory from
+// persistently mapped buffers. This can be more efficient than regular
+// system memory, especially on platforms that don't support importing
+// PL_HANDLE_HOST_PTR as buffers.
+//
+// Note: `avctx->opaque` must be a pointer that *points* to the GPU instance.
+// That is, it should have type `pl_gpu *`.
+PL_LIBAV_API int pl_get_buffer2(AVCodecContext *avctx, AVFrame *pic, int flags);
+
+// Mapping functions for the various libavutil enums. Note that these are not
+// quite 1:1, and even for values that exist in both, the semantics sometimes
+// differ. Some special cases (e.g. ICtCp, or XYZ) are handled differently in
+// libplacebo and libavutil, respectively.
+//
+// Because of this, it's generally recommended to avoid these and instead use
+// helpers like `pl_frame_from_avframe`, which contain extra logic to patch
+// through all of the special cases.
+PL_LIBAV_API enum pl_color_system pl_system_from_av(enum AVColorSpace spc);
+PL_LIBAV_API enum AVColorSpace pl_system_to_av(enum pl_color_system sys);
+PL_LIBAV_API enum pl_color_levels pl_levels_from_av(enum AVColorRange range);
+PL_LIBAV_API enum AVColorRange pl_levels_to_av(enum pl_color_levels levels);
+PL_LIBAV_API enum pl_color_primaries pl_primaries_from_av(enum AVColorPrimaries prim);
+PL_LIBAV_API enum AVColorPrimaries pl_primaries_to_av(enum pl_color_primaries prim);
+PL_LIBAV_API enum pl_color_transfer pl_transfer_from_av(enum AVColorTransferCharacteristic trc);
+PL_LIBAV_API enum AVColorTransferCharacteristic pl_transfer_to_av(enum pl_color_transfer trc);
+PL_LIBAV_API enum pl_chroma_location pl_chroma_from_av(enum AVChromaLocation loc);
+PL_LIBAV_API enum AVChromaLocation pl_chroma_to_av(enum pl_chroma_location loc);
+
+// Helper function to generate a `pl_color_space` struct from an AVFrame.
+PL_LIBAV_API void pl_color_space_from_avframe(struct pl_color_space *out_csp,
+ const AVFrame *frame);
+
+// Helper function to pick the right `pl_field` value for an AVFrame.
+PL_LIBAV_API enum pl_field pl_field_from_avframe(const AVFrame *frame);
+
+#ifdef PL_HAVE_LAV_FILM_GRAIN
+// Fill in film grain parameters from an AVFilmGrainParams.
+//
+// Note: The resulting struct will only remain valid as long as the
+// `AVFilmGrainParams` remains valid.
+PL_LIBAV_API void pl_film_grain_from_av(struct pl_film_grain_data *out_data,
+ const AVFilmGrainParams *fgp);
+#endif
+
+// Deprecated alias for backwards compatibility
+#define pl_swapchain_colors_from_avframe pl_color_space_from_avframe
+
+// Actual implementation, included as part of this header to avoid having
+// a compile-time dependency on libavutil.
+#if PL_LIBAV_IMPLEMENTATION
+# include <libplacebo/utils/libav_internal.h>
+#endif
+
+PL_API_END
+
+#endif // LIBPLACEBO_LIBAV_H_
diff --git a/src/include/libplacebo/utils/libav_internal.h b/src/include/libplacebo/utils/libav_internal.h
new file mode 100644
index 0000000..4c269e5
--- /dev/null
+++ b/src/include/libplacebo/utils/libav_internal.h
@@ -0,0 +1,1482 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_LIBAV_H_
+#error This header should be included as part of <libplacebo/utils/libav.h>
+#elif defined(__cplusplus)
+#error This header cannot be included from C++ define PL_LIBAV_IMPLEMENTATION appropriately
+#else
+
+#include <assert.h>
+
+#include <libplacebo/utils/dolbyvision.h>
+
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_drm.h>
+#include <libavutil/imgutils.h>
+#include <libavutil/pixdesc.h>
+#include <libavutil/display.h>
+#include <libavcodec/version.h>
+
+// Try importing <vulkan.h> dynamically if it wasn't already
+#if !defined(VK_API_VERSION_1_2) && defined(__has_include)
+# if __has_include(<vulkan/vulkan.h>)
+# include <vulkan/vulkan.h>
+# endif
+#endif
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 8, 100) && \
+ defined(PL_HAVE_VULKAN) && defined(VK_API_VERSION_1_2)
+# define PL_HAVE_LAV_VULKAN
+# include <libavutil/hwcontext_vulkan.h>
+# include <libplacebo/vulkan.h>
+# if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 11, 100)
+# define PL_HAVE_LAV_VULKAN_V2
+# endif
+#endif
+
+PL_LIBAV_API enum pl_color_system pl_system_from_av(enum AVColorSpace spc)
+{
+ switch (spc) {
+ case AVCOL_SPC_RGB: return PL_COLOR_SYSTEM_RGB;
+ case AVCOL_SPC_BT709: return PL_COLOR_SYSTEM_BT_709;
+ case AVCOL_SPC_UNSPECIFIED: return PL_COLOR_SYSTEM_UNKNOWN;
+ case AVCOL_SPC_RESERVED: return PL_COLOR_SYSTEM_UNKNOWN;
+ case AVCOL_SPC_FCC: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case AVCOL_SPC_BT470BG: return PL_COLOR_SYSTEM_BT_601;
+ case AVCOL_SPC_SMPTE170M: return PL_COLOR_SYSTEM_BT_601;
+ case AVCOL_SPC_SMPTE240M: return PL_COLOR_SYSTEM_SMPTE_240M;
+ case AVCOL_SPC_YCGCO: return PL_COLOR_SYSTEM_YCGCO;
+ case AVCOL_SPC_BT2020_NCL: return PL_COLOR_SYSTEM_BT_2020_NC;
+ case AVCOL_SPC_BT2020_CL: return PL_COLOR_SYSTEM_BT_2020_C;
+ case AVCOL_SPC_SMPTE2085: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case AVCOL_SPC_CHROMA_DERIVED_NCL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ case AVCOL_SPC_CHROMA_DERIVED_CL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
+ // Note: this colorspace is confused between PQ and HLG, which libav*
+ // requires inferring from other sources, but libplacebo makes explicit.
+ // Default to PQ as it's the more common scenario.
+ case AVCOL_SPC_ICTCP: return PL_COLOR_SYSTEM_BT_2100_PQ;
+ case AVCOL_SPC_NB: return PL_COLOR_SYSTEM_COUNT;
+ }
+
+ return PL_COLOR_SYSTEM_UNKNOWN;
+}
+
+PL_LIBAV_API enum AVColorSpace pl_system_to_av(enum pl_color_system sys)
+{
+ switch (sys) {
+ case PL_COLOR_SYSTEM_UNKNOWN: return AVCOL_SPC_UNSPECIFIED;
+ case PL_COLOR_SYSTEM_BT_601: return AVCOL_SPC_SMPTE170M;
+ case PL_COLOR_SYSTEM_BT_709: return AVCOL_SPC_BT709;
+ case PL_COLOR_SYSTEM_SMPTE_240M: return AVCOL_SPC_SMPTE240M;
+ case PL_COLOR_SYSTEM_BT_2020_NC: return AVCOL_SPC_BT2020_NCL;
+ case PL_COLOR_SYSTEM_BT_2020_C: return AVCOL_SPC_BT2020_CL;
+ case PL_COLOR_SYSTEM_BT_2100_PQ: return AVCOL_SPC_ICTCP;
+ case PL_COLOR_SYSTEM_BT_2100_HLG: return AVCOL_SPC_ICTCP;
+ case PL_COLOR_SYSTEM_DOLBYVISION: return AVCOL_SPC_UNSPECIFIED; // missing
+ case PL_COLOR_SYSTEM_YCGCO: return AVCOL_SPC_YCGCO;
+ case PL_COLOR_SYSTEM_RGB: return AVCOL_SPC_RGB;
+ case PL_COLOR_SYSTEM_XYZ: return AVCOL_SPC_UNSPECIFIED; // handled differently
+ case PL_COLOR_SYSTEM_COUNT: return AVCOL_SPC_NB;
+ }
+
+ return AVCOL_SPC_UNSPECIFIED;
+}
+
+PL_LIBAV_API enum pl_color_levels pl_levels_from_av(enum AVColorRange range)
+{
+ switch (range) {
+ case AVCOL_RANGE_UNSPECIFIED: return PL_COLOR_LEVELS_UNKNOWN;
+ case AVCOL_RANGE_MPEG: return PL_COLOR_LEVELS_LIMITED;
+ case AVCOL_RANGE_JPEG: return PL_COLOR_LEVELS_FULL;
+ case AVCOL_RANGE_NB: return PL_COLOR_LEVELS_COUNT;
+ }
+
+ return PL_COLOR_LEVELS_UNKNOWN;
+}
+
+PL_LIBAV_API enum AVColorRange pl_levels_to_av(enum pl_color_levels levels)
+{
+ switch (levels) {
+ case PL_COLOR_LEVELS_UNKNOWN: return AVCOL_RANGE_UNSPECIFIED;
+ case PL_COLOR_LEVELS_LIMITED: return AVCOL_RANGE_MPEG;
+ case PL_COLOR_LEVELS_FULL: return AVCOL_RANGE_JPEG;
+ case PL_COLOR_LEVELS_COUNT: return AVCOL_RANGE_NB;
+ }
+
+ return AVCOL_RANGE_UNSPECIFIED;
+}
+
+PL_LIBAV_API enum pl_color_primaries pl_primaries_from_av(enum AVColorPrimaries prim)
+{
+ switch (prim) {
+ case AVCOL_PRI_RESERVED0: return PL_COLOR_PRIM_UNKNOWN;
+ case AVCOL_PRI_BT709: return PL_COLOR_PRIM_BT_709;
+ case AVCOL_PRI_UNSPECIFIED: return PL_COLOR_PRIM_UNKNOWN;
+ case AVCOL_PRI_RESERVED: return PL_COLOR_PRIM_UNKNOWN;
+ case AVCOL_PRI_BT470M: return PL_COLOR_PRIM_BT_470M;
+ case AVCOL_PRI_BT470BG: return PL_COLOR_PRIM_BT_601_625;
+ case AVCOL_PRI_SMPTE170M: return PL_COLOR_PRIM_BT_601_525;
+ case AVCOL_PRI_SMPTE240M: return PL_COLOR_PRIM_BT_601_525;
+ case AVCOL_PRI_FILM: return PL_COLOR_PRIM_FILM_C;
+ case AVCOL_PRI_BT2020: return PL_COLOR_PRIM_BT_2020;
+ case AVCOL_PRI_SMPTE428: return PL_COLOR_PRIM_CIE_1931;
+ case AVCOL_PRI_SMPTE431: return PL_COLOR_PRIM_DCI_P3;
+ case AVCOL_PRI_SMPTE432: return PL_COLOR_PRIM_DISPLAY_P3;
+ case AVCOL_PRI_JEDEC_P22: return PL_COLOR_PRIM_EBU_3213;
+ case AVCOL_PRI_NB: return PL_COLOR_PRIM_COUNT;
+ }
+
+ return PL_COLOR_PRIM_UNKNOWN;
+}
+
+PL_LIBAV_API enum AVColorPrimaries pl_primaries_to_av(enum pl_color_primaries prim)
+{
+ switch (prim) {
+ case PL_COLOR_PRIM_UNKNOWN: return AVCOL_PRI_UNSPECIFIED;
+ case PL_COLOR_PRIM_BT_601_525: return AVCOL_PRI_SMPTE170M;
+ case PL_COLOR_PRIM_BT_601_625: return AVCOL_PRI_BT470BG;
+ case PL_COLOR_PRIM_BT_709: return AVCOL_PRI_BT709;
+ case PL_COLOR_PRIM_BT_470M: return AVCOL_PRI_BT470M;
+ case PL_COLOR_PRIM_EBU_3213: return AVCOL_PRI_JEDEC_P22;
+ case PL_COLOR_PRIM_BT_2020: return AVCOL_PRI_BT2020;
+ case PL_COLOR_PRIM_APPLE: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_ADOBE: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_PRO_PHOTO: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_CIE_1931: return AVCOL_PRI_SMPTE428;
+ case PL_COLOR_PRIM_DCI_P3: return AVCOL_PRI_SMPTE431;
+ case PL_COLOR_PRIM_DISPLAY_P3: return AVCOL_PRI_SMPTE432;
+ case PL_COLOR_PRIM_V_GAMUT: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_S_GAMUT: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_FILM_C: return AVCOL_PRI_FILM;
+ case PL_COLOR_PRIM_ACES_AP0: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_ACES_AP1: return AVCOL_PRI_UNSPECIFIED; // missing
+ case PL_COLOR_PRIM_COUNT: return AVCOL_PRI_NB;
+ }
+
+ return AVCOL_PRI_UNSPECIFIED;
+}
+
+PL_LIBAV_API enum pl_color_transfer pl_transfer_from_av(enum AVColorTransferCharacteristic trc)
+{
+ switch (trc) {
+ case AVCOL_TRC_RESERVED0: return PL_COLOR_TRC_UNKNOWN;
+ case AVCOL_TRC_BT709: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_UNSPECIFIED: return PL_COLOR_TRC_UNKNOWN;
+ case AVCOL_TRC_RESERVED: return PL_COLOR_TRC_UNKNOWN;
+ case AVCOL_TRC_GAMMA22: return PL_COLOR_TRC_GAMMA22;
+ case AVCOL_TRC_GAMMA28: return PL_COLOR_TRC_GAMMA28;
+ case AVCOL_TRC_SMPTE170M: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_SMPTE240M: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_LINEAR: return PL_COLOR_TRC_LINEAR;
+ case AVCOL_TRC_LOG: return PL_COLOR_TRC_UNKNOWN; // missing
+ case AVCOL_TRC_LOG_SQRT: return PL_COLOR_TRC_UNKNOWN; // missing
+ case AVCOL_TRC_IEC61966_2_4: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_BT1361_ECG: return PL_COLOR_TRC_BT_1886; // ETOF != OETF
+ case AVCOL_TRC_IEC61966_2_1: return PL_COLOR_TRC_SRGB;
+ case AVCOL_TRC_BT2020_10: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_BT2020_12: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
+ case AVCOL_TRC_SMPTE2084: return PL_COLOR_TRC_PQ;
+ case AVCOL_TRC_SMPTE428: return PL_COLOR_TRC_ST428;
+ case AVCOL_TRC_ARIB_STD_B67: return PL_COLOR_TRC_HLG;
+ case AVCOL_TRC_NB: return PL_COLOR_TRC_COUNT;
+ }
+
+ return PL_COLOR_TRC_UNKNOWN;
+}
+
+PL_LIBAV_API enum AVColorTransferCharacteristic pl_transfer_to_av(enum pl_color_transfer trc)
+{
+ switch (trc) {
+ case PL_COLOR_TRC_UNKNOWN: return AVCOL_TRC_UNSPECIFIED;
+ case PL_COLOR_TRC_BT_1886: return AVCOL_TRC_BT709; // EOTF != OETF
+ case PL_COLOR_TRC_SRGB: return AVCOL_TRC_IEC61966_2_1;
+ case PL_COLOR_TRC_LINEAR: return AVCOL_TRC_LINEAR;
+ case PL_COLOR_TRC_GAMMA18: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_GAMMA20: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_GAMMA22: return AVCOL_TRC_GAMMA22;
+ case PL_COLOR_TRC_GAMMA24: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_GAMMA26: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_GAMMA28: return AVCOL_TRC_GAMMA28;
+ case PL_COLOR_TRC_ST428: return AVCOL_TRC_SMPTE428;
+ case PL_COLOR_TRC_PRO_PHOTO: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_PQ: return AVCOL_TRC_SMPTE2084;
+ case PL_COLOR_TRC_HLG: return AVCOL_TRC_ARIB_STD_B67;
+ case PL_COLOR_TRC_V_LOG: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_S_LOG1: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_S_LOG2: return AVCOL_TRC_UNSPECIFIED; // missing
+ case PL_COLOR_TRC_COUNT: return AVCOL_TRC_NB;
+ }
+
+ return AVCOL_TRC_UNSPECIFIED;
+}
+
+PL_LIBAV_API enum pl_chroma_location pl_chroma_from_av(enum AVChromaLocation loc)
+{
+ switch (loc) {
+ case AVCHROMA_LOC_UNSPECIFIED: return PL_CHROMA_UNKNOWN;
+ case AVCHROMA_LOC_LEFT: return PL_CHROMA_LEFT;
+ case AVCHROMA_LOC_CENTER: return PL_CHROMA_CENTER;
+ case AVCHROMA_LOC_TOPLEFT: return PL_CHROMA_TOP_LEFT;
+ case AVCHROMA_LOC_TOP: return PL_CHROMA_TOP_CENTER;
+ case AVCHROMA_LOC_BOTTOMLEFT: return PL_CHROMA_BOTTOM_LEFT;
+ case AVCHROMA_LOC_BOTTOM: return PL_CHROMA_BOTTOM_CENTER;
+ case AVCHROMA_LOC_NB: return PL_CHROMA_COUNT;
+ }
+
+ return PL_CHROMA_UNKNOWN;
+}
+
+PL_LIBAV_API enum AVChromaLocation pl_chroma_to_av(enum pl_chroma_location loc)
+{
+ switch (loc) {
+ case PL_CHROMA_UNKNOWN: return AVCHROMA_LOC_UNSPECIFIED;
+ case PL_CHROMA_LEFT: return AVCHROMA_LOC_LEFT;
+ case PL_CHROMA_CENTER: return AVCHROMA_LOC_CENTER;
+ case PL_CHROMA_TOP_LEFT: return AVCHROMA_LOC_TOPLEFT;
+ case PL_CHROMA_TOP_CENTER: return AVCHROMA_LOC_TOP;
+ case PL_CHROMA_BOTTOM_LEFT: return AVCHROMA_LOC_BOTTOMLEFT;
+ case PL_CHROMA_BOTTOM_CENTER: return AVCHROMA_LOC_BOTTOM;
+ case PL_CHROMA_COUNT: return AVCHROMA_LOC_NB;
+ }
+
+ return AVCHROMA_LOC_UNSPECIFIED;
+}
+
+#ifdef PL_HAVE_LAV_HDR
+PL_LIBAV_API void pl_map_hdr_metadata(struct pl_hdr_metadata *out,
+ const struct pl_av_hdr_metadata *data)
+{
+ if (data->mdm) {
+ if (data->mdm->has_luminance) {
+ out->max_luma = av_q2d(data->mdm->max_luminance);
+ out->min_luma = av_q2d(data->mdm->min_luminance);
+ if (out->max_luma < 10.0 || out->min_luma >= out->max_luma)
+ out->max_luma = out->min_luma = 0; /* sanity */
+ }
+ if (data->mdm->has_primaries) {
+ out->prim = (struct pl_raw_primaries) {
+ .red.x = av_q2d(data->mdm->display_primaries[0][0]),
+ .red.y = av_q2d(data->mdm->display_primaries[0][1]),
+ .green.x = av_q2d(data->mdm->display_primaries[1][0]),
+ .green.y = av_q2d(data->mdm->display_primaries[1][1]),
+ .blue.x = av_q2d(data->mdm->display_primaries[2][0]),
+ .blue.y = av_q2d(data->mdm->display_primaries[2][1]),
+ .white.x = av_q2d(data->mdm->white_point[0]),
+ .white.y = av_q2d(data->mdm->white_point[1]),
+ };
+ }
+ }
+
+ if (data->clm) {
+ out->max_cll = data->clm->MaxCLL;
+ out->max_fall = data->clm->MaxFALL;
+ }
+
+ if (data->dhp && data->dhp->application_version < 2) {
+ float hist_max = 0;
+ const AVHDRPlusColorTransformParams *pars = &data->dhp->params[0];
+ assert(data->dhp->num_windows > 0);
+ out->scene_max[0] = 10000 * av_q2d(pars->maxscl[0]);
+ out->scene_max[1] = 10000 * av_q2d(pars->maxscl[1]);
+ out->scene_max[2] = 10000 * av_q2d(pars->maxscl[2]);
+ out->scene_avg = 10000 * av_q2d(pars->average_maxrgb);
+
+ // Calculate largest value from histogram to use as fallback for clips
+ // with missing MaxSCL information. Note that this may end up picking
+ // the "reserved" value at the 5% percentile, which in practice appears
+ // to track the brightest pixel in the scene.
+ for (int i = 0; i < pars->num_distribution_maxrgb_percentiles; i++) {
+ float hist_val = av_q2d(pars->distribution_maxrgb[i].percentile);
+ if (hist_val > hist_max)
+ hist_max = hist_val;
+ }
+ hist_max *= 10000;
+ if (!out->scene_max[0])
+ out->scene_max[0] = hist_max;
+ if (!out->scene_max[1])
+ out->scene_max[1] = hist_max;
+ if (!out->scene_max[2])
+ out->scene_max[2] = hist_max;
+
+ if (pars->tone_mapping_flag == 1) {
+ out->ootf.target_luma = av_q2d(data->dhp->targeted_system_display_maximum_luminance);
+ out->ootf.knee_x = av_q2d(pars->knee_point_x);
+ out->ootf.knee_y = av_q2d(pars->knee_point_y);
+ assert(pars->num_bezier_curve_anchors < 16);
+ for (int i = 0; i < pars->num_bezier_curve_anchors; i++)
+ out->ootf.anchors[i] = av_q2d(pars->bezier_curve_anchors[i]);
+ out->ootf.num_anchors = pars->num_bezier_curve_anchors;
+ }
+ }
+}
+#endif // PL_HAVE_LAV_HDR
+
+static inline void *pl_get_side_data_raw(const AVFrame *frame,
+ enum AVFrameSideDataType type)
+{
+ const AVFrameSideData *sd = av_frame_get_side_data(frame, type);
+ return sd ? (void *) sd->data : NULL;
+}
+
+PL_LIBAV_API void pl_color_space_from_avframe(struct pl_color_space *out_csp,
+ const AVFrame *frame)
+{
+ *out_csp = (struct pl_color_space) {
+ .primaries = pl_primaries_from_av(frame->color_primaries),
+ .transfer = pl_transfer_from_av(frame->color_trc),
+ };
+
+#ifdef PL_HAVE_LAV_HDR
+ pl_map_hdr_metadata(&out_csp->hdr, &(struct pl_av_hdr_metadata) {
+ .mdm = pl_get_side_data_raw(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA),
+ .clm = pl_get_side_data_raw(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL),
+ .dhp = pl_get_side_data_raw(frame, AV_FRAME_DATA_DYNAMIC_HDR_PLUS),
+ });
+#endif
+}
+
+PL_LIBAV_API enum pl_field pl_field_from_avframe(const AVFrame *frame)
+{
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
+ if (!frame || !(frame->flags & AV_FRAME_FLAG_INTERLACED))
+ return PL_FIELD_NONE;
+ return (frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST)
+ ? PL_FIELD_TOP : PL_FIELD_BOTTOM;
+#else
+ if (!frame || !frame->interlaced_frame)
+ return PL_FIELD_NONE;
+ return frame->top_field_first ? PL_FIELD_TOP : PL_FIELD_BOTTOM;
+#endif
+}
+
+#ifdef PL_HAVE_LAV_FILM_GRAIN
+PL_LIBAV_API void pl_film_grain_from_av(struct pl_film_grain_data *out_data,
+ const AVFilmGrainParams *fgp)
+{
+ out_data->seed = fgp->seed;
+
+ switch (fgp->type) {
+ case AV_FILM_GRAIN_PARAMS_NONE: break;
+ case AV_FILM_GRAIN_PARAMS_AV1: {
+ const AVFilmGrainAOMParams *src = &fgp->codec.aom;
+ struct pl_av1_grain_data *dst = &out_data->params.av1;
+ out_data->type = PL_FILM_GRAIN_AV1;
+ *dst = (struct pl_av1_grain_data) {
+ .num_points_y = src->num_y_points,
+ .chroma_scaling_from_luma = src->chroma_scaling_from_luma,
+ .num_points_uv = { src->num_uv_points[0], src->num_uv_points[1] },
+ .scaling_shift = src->scaling_shift,
+ .ar_coeff_lag = src->ar_coeff_lag,
+ .ar_coeff_shift = src->ar_coeff_shift,
+ .grain_scale_shift = src->grain_scale_shift,
+ .uv_mult = { src->uv_mult[0], src->uv_mult[1] },
+ .uv_mult_luma = { src->uv_mult_luma[0], src->uv_mult_luma[1] },
+ .uv_offset = { src->uv_offset[0], src->uv_offset[1] },
+ .overlap = src->overlap_flag,
+ };
+
+ assert(sizeof(dst->ar_coeffs_uv) == sizeof(src->ar_coeffs_uv));
+ memcpy(dst->points_y, src->y_points, sizeof(dst->points_y));
+ memcpy(dst->points_uv, src->uv_points, sizeof(dst->points_uv));
+ memcpy(dst->ar_coeffs_y, src->ar_coeffs_y, sizeof(dst->ar_coeffs_y));
+ memcpy(dst->ar_coeffs_uv, src->ar_coeffs_uv, sizeof(dst->ar_coeffs_uv));
+ break;
+ }
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 2, 100)
+ case AV_FILM_GRAIN_PARAMS_H274: {
+ const AVFilmGrainH274Params *src = &fgp->codec.h274;
+ struct pl_h274_grain_data *dst = &out_data->params.h274;
+ out_data->type = PL_FILM_GRAIN_H274;
+ *dst = (struct pl_h274_grain_data) {
+ .model_id = src->model_id,
+ .blending_mode_id = src->blending_mode_id,
+ .log2_scale_factor = src->log2_scale_factor,
+ .component_model_present = {
+ src->component_model_present[0],
+ src->component_model_present[1],
+ src->component_model_present[2],
+ },
+ .intensity_interval_lower_bound = {
+ src->intensity_interval_lower_bound[0],
+ src->intensity_interval_lower_bound[1],
+ src->intensity_interval_lower_bound[2],
+ },
+ .intensity_interval_upper_bound = {
+ src->intensity_interval_upper_bound[0],
+ src->intensity_interval_upper_bound[1],
+ src->intensity_interval_upper_bound[2],
+ },
+ .comp_model_value = {
+ src->comp_model_value[0],
+ src->comp_model_value[1],
+ src->comp_model_value[2],
+ },
+ };
+ memcpy(dst->num_intensity_intervals, src->num_intensity_intervals,
+ sizeof(dst->num_intensity_intervals));
+ memcpy(dst->num_model_values, src->num_model_values,
+ sizeof(dst->num_model_values));
+ break;
+ }
+#endif
+ }
+}
+#endif // PL_HAVE_LAV_FILM_GRAIN
+
+static inline int pl_plane_data_num_comps(const struct pl_plane_data *data)
+{
+ for (int i = 0; i < 4; i++) {
+ if (data->component_size[i] == 0)
+ return i;
+ }
+
+ return 4;
+}
+
+PL_LIBAV_API int pl_plane_data_from_pixfmt(struct pl_plane_data out_data[4],
+ struct pl_bit_encoding *out_bits,
+ enum AVPixelFormat pix_fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
+ int planes = av_pix_fmt_count_planes(pix_fmt);
+ struct pl_plane_data aligned_data[4];
+ struct pl_bit_encoding bits;
+ bool first;
+ if (!desc || planes < 0) // e.g. AV_PIX_FMT_NONE
+ return 0;
+
+ if (desc->flags & AV_PIX_FMT_FLAG_BITSTREAM) {
+ // Bitstream formats will most likely never be supported
+ return 0;
+ }
+
+ if (desc->flags & AV_PIX_FMT_FLAG_PAL) {
+ // Palette formats are (currently) not supported
+ return 0;
+ }
+
+ if (desc->flags & AV_PIX_FMT_FLAG_BAYER) {
+ // Bayer format don't have valid `desc->offset` values, so we can't
+ // use `pl_plane_data_from_mask` on them.
+ return 0;
+ }
+
+ if (desc->nb_components == 0 || desc->nb_components > 4) {
+ // Bogus components, possibly fake/virtual/hwaccel format?
+ return 0;
+ }
+
+ if (planes > 4)
+ return 0; // This shouldn't ever happen
+
+ // Fill in the details for each plane
+ for (int p = 0; p < planes; p++) {
+ struct pl_plane_data *data = &out_data[p];
+ int size[4] = {0};
+ int shift[4] = {0};
+ data->swapped = desc->flags & AV_PIX_FMT_FLAG_BE;
+ data->type = (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
+ ? PL_FMT_FLOAT
+ : PL_FMT_UNORM;
+
+ data->pixel_stride = 0;
+
+ for (int c = 0; c < desc->nb_components; c++) {
+ const AVComponentDescriptor *comp = &desc->comp[c];
+ if (comp->plane != p)
+ continue;
+ if (data->swapped && comp->shift) {
+ // We cannot naively handle packed big endian formats because
+ // swapping the words also swaps the component order, so just
+ // exit out as a stupid safety measure
+ return 0;
+ }
+
+ size[c] = comp->depth;
+ shift[c] = comp->shift + comp->offset * 8;
+
+ if (data->pixel_stride && (int) data->pixel_stride != comp->step) {
+ // Pixel format contains components with different pixel stride
+ // (e.g. packed YUYV), this is currently not supported
+ return 0;
+ }
+ data->pixel_stride = comp->step;
+ }
+
+ pl_plane_data_from_comps(data, size, shift);
+ }
+
+ if (!out_bits)
+ return planes;
+
+ // Attempt aligning all of the planes for optimum compatibility
+ first = true;
+ for (int p = 0; p < planes; p++) {
+ aligned_data[p] = out_data[p];
+
+ // Planes with only an alpha component should be ignored
+ if (pl_plane_data_num_comps(&aligned_data[p]) == 1 &&
+ aligned_data[p].component_map[0] == PL_CHANNEL_A)
+ {
+ continue;
+ }
+
+ if (!pl_plane_data_align(&aligned_data[p], &bits))
+ goto misaligned;
+
+ if (first) {
+ *out_bits = bits;
+ first = false;
+ } else {
+ if (!pl_bit_encoding_equal(&bits, out_bits))
+ goto misaligned;
+ }
+ }
+
+ // Overwrite the planes by their aligned versions
+ for (int p = 0; p < planes; p++)
+ out_data[p] = aligned_data[p];
+
+ return planes;
+
+misaligned:
+ *out_bits = (struct pl_bit_encoding) {0};
+ return planes;
+}
+
+PL_LIBAV_API bool pl_test_pixfmt_caps(pl_gpu gpu, enum AVPixelFormat pixfmt,
+ enum pl_fmt_caps caps)
+{
+ struct pl_bit_encoding bits;
+ struct pl_plane_data data[4];
+ pl_fmt fmt;
+ int planes;
+
+ switch (pixfmt) {
+ case AV_PIX_FMT_DRM_PRIME:
+ case AV_PIX_FMT_VAAPI:
+ return gpu->import_caps.tex & PL_HANDLE_DMA_BUF;
+
+#ifdef PL_HAVE_LAV_VULKAN
+ case AV_PIX_FMT_VULKAN:
+ return pl_vulkan_get(gpu);
+#endif
+
+ default: break;
+ }
+
+ planes = pl_plane_data_from_pixfmt(data, &bits, pixfmt);
+ if (!planes)
+ return false;
+
+ for (int i = 0; i < planes; i++) {
+ data[i].row_stride = 0;
+ fmt = pl_plane_find_fmt(gpu, NULL, &data[i]);
+ if (!fmt || (fmt->caps & caps) != caps)
+ return false;
+
+ }
+
+ return true;
+}
+
+PL_LIBAV_API bool pl_test_pixfmt(pl_gpu gpu, enum AVPixelFormat pixfmt)
+{
+ return pl_test_pixfmt_caps(gpu, pixfmt, 0);
+}
+
+PL_LIBAV_API void pl_avframe_set_color(AVFrame *frame, struct pl_color_space csp)
+{
+ const AVFrameSideData *sd;
+ (void) sd;
+
+ frame->color_primaries = pl_primaries_to_av(csp.primaries);
+ frame->color_trc = pl_transfer_to_av(csp.transfer);
+
+#ifdef PL_HAVE_LAV_HDR
+ if (csp.hdr.max_cll) {
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+ if (!sd) {
+ sd = av_frame_new_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+ sizeof(AVContentLightMetadata));
+ }
+
+ if (sd) {
+ AVContentLightMetadata *clm = (AVContentLightMetadata *) sd->data;
+ *clm = (AVContentLightMetadata) {
+ .MaxCLL = csp.hdr.max_cll,
+ .MaxFALL = csp.hdr.max_fall,
+ };
+ }
+ }
+
+ if (csp.hdr.max_luma || csp.hdr.prim.red.x) {
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+ if (!sd) {
+ sd = av_frame_new_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+ sizeof(AVMasteringDisplayMetadata));
+ }
+
+ if (sd) {
+ AVMasteringDisplayMetadata *mdm = (AVMasteringDisplayMetadata *) sd->data;
+ *mdm = (AVMasteringDisplayMetadata) {
+ .max_luminance = av_d2q(csp.hdr.max_luma, 1000000),
+ .min_luminance = av_d2q(csp.hdr.min_luma, 1000000),
+ .has_luminance = !!csp.hdr.max_luma,
+ .display_primaries = {
+ {
+ av_d2q(csp.hdr.prim.red.x, 1000000),
+ av_d2q(csp.hdr.prim.red.y, 1000000),
+ }, {
+ av_d2q(csp.hdr.prim.green.x, 1000000),
+ av_d2q(csp.hdr.prim.green.y, 1000000),
+ }, {
+ av_d2q(csp.hdr.prim.blue.x, 1000000),
+ av_d2q(csp.hdr.prim.blue.y, 1000000),
+ }
+ },
+ .white_point = {
+ av_d2q(csp.hdr.prim.white.x, 1000000),
+ av_d2q(csp.hdr.prim.white.y, 1000000),
+ },
+ .has_primaries = !!csp.hdr.prim.red.x,
+ };
+ }
+ }
+#endif // PL_HAVE_LAV_HDR
+}
+
+PL_LIBAV_API void pl_avframe_set_repr(AVFrame *frame, struct pl_color_repr repr)
+{
+ frame->colorspace = pl_system_to_av(repr.sys);
+ frame->color_range = pl_levels_to_av(repr.levels);
+
+ // No real way to map repr.bits, the image format already has to match
+}
+
+PL_LIBAV_API void pl_avframe_set_profile(AVFrame *frame, struct pl_icc_profile profile)
+{
+ const AVFrameSideData *sd;
+ av_frame_remove_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
+
+ if (!profile.len)
+ return;
+
+ sd = av_frame_new_side_data(frame, AV_FRAME_DATA_ICC_PROFILE, profile.len);
+ memcpy(sd->data, profile.data, profile.len);
+}
+
+PL_LIBAV_API void pl_frame_from_avframe(struct pl_frame *out,
+ const AVFrame *frame)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
+ int planes = av_pix_fmt_count_planes(frame->format);
+ const AVFrameSideData *sd;
+ assert(desc);
+
+ if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ desc = av_pix_fmt_desc_get(hwfc->sw_format);
+ planes = av_pix_fmt_count_planes(hwfc->sw_format);
+ }
+
+ // This should never fail, and there's nothing really useful we can do in
+ // this failure case anyway, since this is a `void` function.
+ assert(planes <= 4);
+
+ *out = (struct pl_frame) {
+ .num_planes = planes,
+ .crop = {
+ .x0 = frame->crop_left,
+ .y0 = frame->crop_top,
+ .x1 = frame->width - frame->crop_right,
+ .y1 = frame->height - frame->crop_bottom,
+ },
+ .repr = {
+ .sys = pl_system_from_av(frame->colorspace),
+ .levels = pl_levels_from_av(frame->color_range),
+ .alpha = (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
+ ? PL_ALPHA_INDEPENDENT
+ : PL_ALPHA_UNKNOWN,
+
+ // For sake of simplicity, just use the first component's depth as
+ // the authoritative color depth for the whole image. Usually, this
+ // will be overwritten by more specific information when using e.g.
+ // `pl_map_avframe`, but for the sake of e.g. users wishing to map
+ // hwaccel frames manually, this is a good default.
+ .bits.color_depth = desc->comp[0].depth,
+ },
+ };
+
+ pl_color_space_from_avframe(&out->color, frame);
+
+ if (frame->colorspace == AVCOL_SPC_ICTCP &&
+ frame->color_trc == AVCOL_TRC_ARIB_STD_B67)
+ {
+ // libav* makes no distinction between PQ and HLG ICtCp, so we need
+ // to manually fix it in the case that we have HLG ICtCp data.
+ out->repr.sys = PL_COLOR_SYSTEM_BT_2100_HLG;
+
+ } else if (strncmp(desc->name, "xyz", 3) == 0) {
+
+ // libav* handles this as a special case, but doesn't provide an
+ // explicit flag for it either, so we have to resort to this ugly
+ // hack...
+ out->repr.sys = PL_COLOR_SYSTEM_XYZ;
+
+ } else if (desc->flags & AV_PIX_FMT_FLAG_RGB) {
+
+ out->repr.sys = PL_COLOR_SYSTEM_RGB;
+ out->repr.levels = PL_COLOR_LEVELS_FULL; // libav* ignores levels for RGB
+
+ } else if (!pl_color_system_is_ycbcr_like(out->repr.sys)) {
+ // libav* likes leaving this as UNKNOWN (or even RGB) for YCbCr frames,
+ // which confuses libplacebo since we infer UNKNOWN as RGB. To get
+ // around this, explicitly infer a suitable colorspace.
+ out->repr.sys = pl_color_system_guess_ycbcr(frame->width, frame->height);
+ }
+
+ if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE))) {
+ out->profile = (struct pl_icc_profile) {
+ .data = sd->data,
+ .len = sd->size,
+ };
+
+ // Needed to ensure profile uniqueness
+ pl_icc_profile_compute_signature(&out->profile);
+ }
+
+ if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX))) {
+ double rot = av_display_rotation_get((const int32_t *) sd->data);
+ out->rotation = pl_rotation_normalize(4.5 - rot / 90.0);
+ }
+
+#ifdef PL_HAVE_LAV_FILM_GRAIN
+ if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_FILM_GRAIN_PARAMS)))
+ pl_film_grain_from_av(&out->film_grain, (AVFilmGrainParams *) sd->data);
+#endif // HAVE_LAV_FILM_GRAIN
+
+ for (int p = 0; p < out->num_planes; p++) {
+ struct pl_plane *plane = &out->planes[p];
+
+ // Fill in the component mapping array
+ for (int c = 0; c < desc->nb_components; c++) {
+ if (desc->comp[c].plane == p)
+ plane->component_mapping[plane->components++] = c;
+ }
+
+ // Clear the superfluous components
+ for (int c = plane->components; c < 4; c++)
+ plane->component_mapping[c] = PL_CHANNEL_NONE;
+ }
+
+ // Only set the chroma location for definitely subsampled images, makes no
+ // sense otherwise
+ if (desc->log2_chroma_w || desc->log2_chroma_h) {
+ enum pl_chroma_location loc = pl_chroma_from_av(frame->chroma_location);
+ pl_frame_set_chroma_location(out, loc);
+ }
+}
+
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
+PL_LIBAV_API const uint8_t *pl_av_stream_get_side_data(const AVStream *st,
+ enum AVPacketSideDataType type)
+{
+ const AVPacketSideData *sd;
+ sd = av_packet_side_data_get(st->codecpar->coded_side_data,
+ st->codecpar->nb_coded_side_data,
+ type);
+ return sd ? sd->data : NULL;
+}
+#else
+# define pl_av_stream_get_side_data(st, type) av_stream_get_side_data(st, type, NULL)
+#endif
+
+PL_LIBAV_API void pl_frame_copy_stream_props(struct pl_frame *out,
+ const AVStream *stream)
+{
+ const uint8_t *sd;
+ if ((sd = pl_av_stream_get_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX))) {
+ double rot = av_display_rotation_get((const int32_t *) sd);
+ out->rotation = pl_rotation_normalize(4.5 - rot / 90.0);
+ }
+
+#ifdef PL_HAVE_LAV_HDR
+ pl_map_hdr_metadata(&out->color.hdr, &(struct pl_av_hdr_metadata) {
+ .mdm = (void *) pl_av_stream_get_side_data(stream,
+ AV_PKT_DATA_MASTERING_DISPLAY_METADATA),
+ .clm = (void *) pl_av_stream_get_side_data(stream,
+ AV_PKT_DATA_CONTENT_LIGHT_LEVEL),
+# if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 2, 100)
+ .dhp = (void *) pl_av_stream_get_side_data(stream,
+ AV_PKT_DATA_DYNAMIC_HDR10_PLUS),
+# endif
+ });
+#endif
+}
+
+#undef pl_av_stream_get_side_data
+
+#ifdef PL_HAVE_LAV_DOLBY_VISION
+PL_LIBAV_API void pl_map_dovi_metadata(struct pl_dovi_metadata *out,
+ const AVDOVIMetadata *data)
+{
+ const AVDOVIRpuDataHeader *header;
+ const AVDOVIDataMapping *mapping;
+ const AVDOVIColorMetadata *color;
+ if (!data)
+ return;
+
+ header = av_dovi_get_header(data);
+ mapping = av_dovi_get_mapping(data);
+ color = av_dovi_get_color(data);
+
+ for (int i = 0; i < 3; i++)
+ out->nonlinear_offset[i] = av_q2d(color->ycc_to_rgb_offset[i]);
+ for (int i = 0; i < 9; i++) {
+ float *nonlinear = &out->nonlinear.m[0][0];
+ float *linear = &out->linear.m[0][0];
+ nonlinear[i] = av_q2d(color->ycc_to_rgb_matrix[i]);
+ linear[i] = av_q2d(color->rgb_to_lms_matrix[i]);
+ }
+ for (int c = 0; c < 3; c++) {
+ const AVDOVIReshapingCurve *csrc = &mapping->curves[c];
+ struct pl_reshape_data *cdst = &out->comp[c];
+ cdst->num_pivots = csrc->num_pivots;
+ for (int i = 0; i < csrc->num_pivots; i++) {
+ const float scale = 1.0f / ((1 << header->bl_bit_depth) - 1);
+ cdst->pivots[i] = scale * csrc->pivots[i];
+ }
+ for (int i = 0; i < csrc->num_pivots - 1; i++) {
+ const float scale = 1.0f / (1 << header->coef_log2_denom);
+ cdst->method[i] = csrc->mapping_idc[i];
+ switch (csrc->mapping_idc[i]) {
+ case AV_DOVI_MAPPING_POLYNOMIAL:
+ for (int k = 0; k < 3; k++) {
+ cdst->poly_coeffs[i][k] = (k <= csrc->poly_order[i])
+ ? scale * csrc->poly_coef[i][k]
+ : 0.0f;
+ }
+ break;
+ case AV_DOVI_MAPPING_MMR:
+ cdst->mmr_order[i] = csrc->mmr_order[i];
+ cdst->mmr_constant[i] = scale * csrc->mmr_constant[i];
+ for (int j = 0; j < csrc->mmr_order[i]; j++) {
+ for (int k = 0; k < 7; k++)
+ cdst->mmr_coeffs[i][j][k] = scale * csrc->mmr_coef[i][j][k];
+ }
+ break;
+ }
+ }
+ }
+}
+
+PL_LIBAV_API void pl_frame_map_avdovi_metadata(struct pl_frame *out_frame,
+ struct pl_dovi_metadata *dovi,
+ const AVDOVIMetadata *metadata)
+{
+ const AVDOVIRpuDataHeader *header;
+ const AVDOVIColorMetadata *color;
+ if (!dovi || !metadata)
+ return;
+
+ header = av_dovi_get_header(metadata);
+ color = av_dovi_get_color(metadata);
+ if (header->disable_residual_flag) {
+ pl_map_dovi_metadata(dovi, metadata);
+
+ out_frame->repr.dovi = dovi;
+ out_frame->repr.sys = PL_COLOR_SYSTEM_DOLBYVISION;
+ out_frame->color.primaries = PL_COLOR_PRIM_BT_2020;
+ out_frame->color.transfer = PL_COLOR_TRC_PQ;
+ out_frame->color.hdr.min_luma =
+ pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, color->source_min_pq / 4095.0f);
+ out_frame->color.hdr.max_luma =
+ pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, color->source_max_pq / 4095.0f);
+ }
+}
+#endif // PL_HAVE_LAV_DOLBY_VISION
+
+PL_LIBAV_API bool pl_frame_recreate_from_avframe(pl_gpu gpu,
+ struct pl_frame *out,
+ pl_tex tex[4],
+ const AVFrame *frame)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
+ struct pl_plane_data data[4] = {0};
+ int planes;
+
+ pl_frame_from_avframe(out, frame);
+ planes = pl_plane_data_from_pixfmt(data, &out->repr.bits, frame->format);
+ if (!planes)
+ return false;
+
+ for (int p = 0; p < planes; p++) {
+ bool is_chroma = p == 1 || p == 2; // matches lavu logic
+ data[p].width = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0);
+ data[p].height = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0);
+
+ if (!pl_recreate_plane(gpu, &out->planes[p], &tex[p], &data[p]))
+ return false;
+ }
+
+ return true;
+}
+
+static void pl_avframe_free_cb(void *priv)
+{
+ AVFrame *frame = priv;
+ av_frame_free(&frame);
+}
+
+#define PL_MAGIC0 0xfb5b3b8b
+#define PL_MAGIC1 0xee659f6d
+
+struct pl_avalloc {
+ uint32_t magic[2];
+ pl_gpu gpu;
+ pl_buf buf;
+};
+
+// Attached to `pl_frame.user_data` for mapped AVFrames
+struct pl_avframe_priv {
+ AVFrame *avframe;
+ struct pl_dovi_metadata dovi; // backing storage for per-frame dovi metadata
+ pl_tex planar; // for planar vulkan textures
+};
+
+static void pl_fix_hwframe_sample_depth(struct pl_frame *out, const AVFrame *frame)
+{
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ pl_fmt fmt = out->planes[0].texture->params.format;
+ struct pl_bit_encoding *bits = &out->repr.bits;
+
+ bits->sample_depth = fmt->component_depth[0];
+
+ switch (hwfc->sw_format) {
+ case AV_PIX_FMT_P010: bits->bit_shift = 6; break;
+ default: break;
+ }
+}
+
+static bool pl_map_avframe_drm(pl_gpu gpu, struct pl_frame *out,
+ const AVFrame *frame)
+{
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
+ const AVDRMFrameDescriptor *drm = (AVDRMFrameDescriptor *) frame->data[0];
+ assert(frame->format == AV_PIX_FMT_DRM_PRIME);
+ if (!(gpu->import_caps.tex & PL_HANDLE_DMA_BUF))
+ return false;
+
+ assert(drm->nb_layers >= out->num_planes);
+ for (int n = 0; n < out->num_planes; n++) {
+ const AVDRMLayerDescriptor *layer = &drm->layers[n];
+ const AVDRMPlaneDescriptor *plane = &layer->planes[0];
+ const AVDRMObjectDescriptor *object = &drm->objects[plane->object_index];
+ pl_fmt fmt = pl_find_fourcc(gpu, layer->format);
+ bool is_chroma = n == 1 || n == 2;
+ if (!fmt || !pl_fmt_has_modifier(fmt, object->format_modifier))
+ return false;
+
+ assert(layer->nb_planes == 1); // we only support planar formats
+ assert(plane->pitch >= 0); // definitely requires special handling
+ out->planes[n].texture = pl_tex_create(gpu, pl_tex_params(
+ .w = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0),
+ .h = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0),
+ .format = fmt,
+ .sampleable = true,
+ .blit_src = fmt->caps & PL_FMT_CAP_BLITTABLE,
+ .import_handle = PL_HANDLE_DMA_BUF,
+ .shared_mem = {
+ .handle.fd = object->fd,
+ .size = object->size,
+ .offset = plane->offset,
+ .drm_format_mod = object->format_modifier,
+ .stride_w = plane->pitch,
+ },
+ ));
+ if (!out->planes[n].texture)
+ return false;
+ }
+
+ pl_fix_hwframe_sample_depth(out, frame);
+ return true;
+}
+
+// Derive a DMABUF from any other hwaccel format, and map that instead
+static bool pl_map_avframe_derived(pl_gpu gpu, struct pl_frame *out,
+ const AVFrame *frame)
+{
+ const int flags = AV_HWFRAME_MAP_READ | AV_HWFRAME_MAP_DIRECT;
+ struct pl_avframe_priv *priv = out->user_data;
+ AVFrame *derived = av_frame_alloc();
+ derived->width = frame->width;
+ derived->height = frame->height;
+ derived->format = AV_PIX_FMT_DRM_PRIME;
+ derived->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx);
+ if (av_hwframe_map(derived, frame, flags) < 0)
+ goto error;
+ if (av_frame_copy_props(derived, frame) < 0)
+ goto error;
+ if (!pl_map_avframe_drm(gpu, out, derived))
+ goto error;
+
+ av_frame_free(&priv->avframe);
+ priv->avframe = derived;
+ return true;
+
+error:
+ av_frame_free(&derived);
+ return false;
+}
+
+#ifdef PL_HAVE_LAV_VULKAN
+static bool pl_acquire_avframe(pl_gpu gpu, struct pl_frame *frame)
+{
+ const struct pl_avframe_priv *priv = frame->user_data;
+ AVHWFramesContext *hwfc = (void *) priv->avframe->hw_frames_ctx->data;
+ AVVulkanFramesContext *vkfc = hwfc->hwctx;
+ AVVkFrame *vkf = (AVVkFrame *) priv->avframe->data[0];
+
+#ifdef PL_HAVE_LAV_VULKAN_V2
+ vkfc->lock_frame(hwfc, vkf);
+#else
+ (void) vkfc;
+#endif
+
+ for (int n = 0; n < frame->num_planes; n++) {
+ pl_vulkan_release_ex(gpu, pl_vulkan_release_params(
+ .tex = priv->planar ? priv->planar : frame->planes[n].texture,
+ .layout = vkf->layout[n],
+ .qf = VK_QUEUE_FAMILY_IGNORED,
+ .semaphore = {
+ .sem = vkf->sem[n],
+ .value = vkf->sem_value[n],
+ },
+ ));
+ if (priv->planar)
+ break;
+ }
+
+ return true;
+}
+
+static void pl_release_avframe(pl_gpu gpu, struct pl_frame *frame)
+{
+ const struct pl_avframe_priv *priv = frame->user_data;
+ AVHWFramesContext *hwfc = (void *) priv->avframe->hw_frames_ctx->data;
+ AVVulkanFramesContext *vkfc = hwfc->hwctx;
+ AVVkFrame *vkf = (AVVkFrame *) priv->avframe->data[0];
+
+ for (int n = 0; n < frame->num_planes; n++) {
+ int ok = pl_vulkan_hold_ex(gpu, pl_vulkan_hold_params(
+ .tex = priv->planar ? priv->planar : frame->planes[n].texture,
+ .out_layout = &vkf->layout[n],
+ .qf = VK_QUEUE_FAMILY_IGNORED,
+ .semaphore = {
+ .sem = vkf->sem[n],
+ .value = vkf->sem_value[n] + 1,
+ },
+ ));
+
+ vkf->access[n] = 0;
+ vkf->sem_value[n] += !!ok;
+ if (priv->planar)
+ break;
+ }
+
+#ifdef PL_HAVE_LAV_VULKAN_V2
+ vkfc->unlock_frame(hwfc, vkf);
+#else
+ (void) vkfc;
+#endif
+}
+
+static bool pl_map_avframe_vulkan(pl_gpu gpu, struct pl_frame *out,
+ const AVFrame *frame)
+{
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
+ const AVVulkanFramesContext *vkfc = hwfc->hwctx;
+ AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
+ struct pl_avframe_priv *priv = out->user_data;
+ pl_vulkan vk = pl_vulkan_get(gpu);
+
+#ifdef PL_HAVE_LAV_VULKAN_V2
+ const VkFormat *vk_fmt = vkfc->format;
+#else
+ const VkFormat *vk_fmt = av_vkfmt_from_pixfmt(hwfc->sw_format);
+#endif
+
+ assert(frame->format == AV_PIX_FMT_VULKAN);
+ priv->planar = NULL;
+ if (!vk)
+ return false;
+
+ for (int n = 0; n < out->num_planes; n++) {
+ struct pl_plane *plane = &out->planes[n];
+ bool chroma = n == 1 || n == 2;
+ int num_subplanes;
+ assert(vk_fmt[n]);
+
+ plane->texture = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
+ .image = vkf->img[n],
+ .width = AV_CEIL_RSHIFT(hwfc->width, chroma ? desc->log2_chroma_w : 0),
+ .height = AV_CEIL_RSHIFT(hwfc->height, chroma ? desc->log2_chroma_h : 0),
+ .format = vk_fmt[n],
+ .usage = vkfc->usage,
+ ));
+ if (!plane->texture)
+ return false;
+
+ num_subplanes = plane->texture->params.format->num_planes;
+ if (num_subplanes) {
+ assert(num_subplanes == out->num_planes);
+ priv->planar = plane->texture;
+ for (int i = 0; i < num_subplanes; i++)
+ out->planes[i].texture = priv->planar->planes[i];
+ break;
+ }
+ }
+
+ out->acquire = pl_acquire_avframe;
+ out->release = pl_release_avframe;
+ pl_fix_hwframe_sample_depth(out, frame);
+ return true;
+}
+
+static void pl_unmap_avframe_vulkan(pl_gpu gpu, struct pl_frame *frame)
+{
+ struct pl_avframe_priv *priv = frame->user_data;
+ if (priv->planar) {
+ pl_tex_destroy(gpu, &priv->planar);
+ for (int n = 0; n < frame->num_planes; n++)
+ frame->planes[n].texture = NULL;
+ }
+}
+#endif
+
+PL_LIBAV_API bool pl_map_avframe_ex(pl_gpu gpu, struct pl_frame *out,
+ const struct pl_avframe_params *params)
+{
+ const AVFrame *frame = params->frame;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
+ struct pl_plane_data data[4] = {0};
+ pl_tex *tex = params->tex;
+ int planes;
+
+ struct pl_avframe_priv *priv = malloc(sizeof(*priv));
+ if (!priv)
+ goto error;
+
+ pl_frame_from_avframe(out, frame);
+ priv->avframe = av_frame_clone(frame);
+ out->user_data = priv;
+
+#ifdef PL_HAVE_LAV_DOLBY_VISION
+ if (params->map_dovi) {
+ AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DOVI_METADATA);
+ if (sd) {
+ const AVDOVIMetadata *metadata = (AVDOVIMetadata *) sd->data;
+ const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata);
+ // Only automatically map DoVi RPUs that don't require an EL
+ if (header->disable_residual_flag)
+ pl_frame_map_avdovi_metadata(out, &priv->dovi, metadata);
+ }
+
+#ifdef PL_HAVE_LIBDOVI
+ sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DOVI_RPU_BUFFER);
+ if (sd)
+ pl_hdr_metadata_from_dovi_rpu(&out->color.hdr, sd->buf->data, sd->buf->size);
+#endif // PL_HAVE_LIBDOVI
+ }
+
+#endif // PL_HAVE_LAV_DOLBY_VISION
+
+ switch (frame->format) {
+ case AV_PIX_FMT_DRM_PRIME:
+ if (!pl_map_avframe_drm(gpu, out, frame))
+ goto error;
+ return true;
+
+ case AV_PIX_FMT_VAAPI:
+ if (!pl_map_avframe_derived(gpu, out, frame))
+ goto error;
+ return true;
+
+#ifdef PL_HAVE_LAV_VULKAN
+ case AV_PIX_FMT_VULKAN:
+ if (!pl_map_avframe_vulkan(gpu, out, frame))
+ goto error;
+ return true;
+#endif
+
+ default: break;
+ }
+
+ // Backing textures are required from this point onwards
+ if (!tex)
+ goto error;
+
+ planes = pl_plane_data_from_pixfmt(data, &out->repr.bits, frame->format);
+ if (!planes)
+ goto error;
+
+ for (int p = 0; p < planes; p++) {
+ AVBufferRef *buf = av_frame_get_plane_buffer((AVFrame *) frame, p);
+ struct pl_avalloc *alloc = buf ? av_buffer_get_opaque(buf) : NULL;
+ bool is_chroma = p == 1 || p == 2; // matches lavu logic
+
+ data[p].width = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0);
+ data[p].height = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0);
+ if (frame->linesize[p] < 0) {
+ data[p].pixels = frame->data[p] + frame->linesize[p] * (data[p].height - 1);
+ data[p].row_stride = -frame->linesize[p];
+ out->planes[p].flipped = true;
+ } else {
+ data[p].pixels = frame->data[p];
+ data[p].row_stride = frame->linesize[p];
+ }
+
+ // Probe for frames allocated by pl_get_buffer2
+ if (alloc && alloc->magic[0] == PL_MAGIC0 && alloc->magic[1] == PL_MAGIC1) {
+ data[p].buf = alloc->buf;
+ data[p].buf_offset = (uintptr_t) data[p].pixels - (uintptr_t) alloc->buf->data;
+ data[p].pixels = NULL;
+ } else if (gpu->limits.callbacks) {
+ // Use asynchronous upload if possible
+ data[p].callback = pl_avframe_free_cb;
+ data[p].priv = av_frame_clone(frame);
+ }
+
+ if (!pl_upload_plane(gpu, &out->planes[p], &tex[p], &data[p])) {
+ av_frame_free((AVFrame **) &data[p].priv);
+ goto error;
+ }
+
+ out->planes[p].texture = tex[p];
+ }
+
+ return true;
+
+error:
+ pl_unmap_avframe(gpu, out);
+ return false;
+}
+
+// Backwards compatibility with previous versions of this API.
+PL_LIBAV_API bool pl_map_avframe(pl_gpu gpu, struct pl_frame *out_frame,
+ pl_tex tex[4], const AVFrame *avframe)
+{
+ return pl_map_avframe_ex(gpu, out_frame, &(struct pl_avframe_params) {
+ .frame = avframe,
+ .tex = tex,
+ });
+}
+
+PL_LIBAV_API void pl_unmap_avframe(pl_gpu gpu, struct pl_frame *frame)
+{
+ struct pl_avframe_priv *priv = frame->user_data;
+ const AVPixFmtDescriptor *desc;
+ if (!priv)
+ goto done;
+
+#ifdef PL_HAVE_LAV_VULKAN
+ if (priv->avframe->format == AV_PIX_FMT_VULKAN)
+ pl_unmap_avframe_vulkan(gpu, frame);
+#endif
+
+ desc = av_pix_fmt_desc_get(priv->avframe->format);
+ if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
+ for (int i = 0; i < 4; i++)
+ pl_tex_destroy(gpu, &frame->planes[i].texture);
+ }
+
+ av_frame_free(&priv->avframe);
+ free(priv);
+
+done:
+ memset(frame, 0, sizeof(*frame)); // sanity
+}
+
+PL_LIBAV_API AVFrame *pl_get_mapped_avframe(const struct pl_frame *frame)
+{
+ struct pl_avframe_priv *priv = frame->user_data;
+ return priv->avframe;
+}
+
+static void pl_done_cb(void *priv)
+{
+ bool *status = priv;
+ *status = true;
+}
+
+PL_LIBAV_API bool pl_download_avframe(pl_gpu gpu,
+ const struct pl_frame *frame,
+ AVFrame *out_frame)
+{
+ bool done[4] = {0};
+ if (frame->num_planes != av_pix_fmt_count_planes(out_frame->format))
+ return false;
+
+ for (int p = 0; p < frame->num_planes; p++) {
+ bool ok = pl_tex_download(gpu, pl_tex_transfer_params(
+ .tex = frame->planes[p].texture,
+ .row_pitch = out_frame->linesize[p],
+ .ptr = out_frame->data[p],
+ // Use synchronous transfer for the last plane
+ .callback = (p+1) < frame->num_planes ? pl_done_cb : NULL,
+ .priv = &done[p],
+ ));
+
+ if (!ok)
+ return false;
+ }
+
+ for (int p = 0; p < frame->num_planes - 1; p++) {
+ while (!done[p])
+ pl_tex_poll(gpu, frame->planes[p].texture, UINT64_MAX);
+ }
+
+ return true;
+}
+
+#define PL_DIV_UP(x, y) (((x) + (y) - 1) / (y))
+#define PL_ALIGN(x, align) ((align) ? PL_DIV_UP(x, align) * (align) : (x))
+#define PL_MAX(x, y) ((x) > (y) ? (x) : (y))
+#define PL_LCM(x, y) ((x) * ((y) / av_gcd(x, y)))
+
+static inline void pl_avalloc_free(void *opaque, uint8_t *data)
+{
+ struct pl_avalloc *alloc = opaque;
+ assert(alloc->magic[0] == PL_MAGIC0);
+ assert(alloc->magic[1] == PL_MAGIC1);
+ assert(alloc->buf->data == data);
+ pl_buf_destroy(alloc->gpu, &alloc->buf);
+ free(alloc);
+}
+
+PL_LIBAV_API int pl_get_buffer2(AVCodecContext *avctx, AVFrame *pic, int flags)
+{
+ int alignment[AV_NUM_DATA_POINTERS];
+ int width = pic->width;
+ int height = pic->height;
+ size_t planesize[4];
+ int ret = 0;
+
+ pl_gpu *pgpu = avctx->opaque;
+ pl_gpu gpu = pgpu ? *pgpu : NULL;
+ struct pl_plane_data data[4];
+ struct pl_avalloc *alloc;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pic->format);
+ int planes = pl_plane_data_from_pixfmt(data, NULL, pic->format);
+
+ // Sanitize frame structs
+ memset(pic->data, 0, sizeof(pic->data));
+ memset(pic->linesize, 0, sizeof(pic->linesize));
+ memset(pic->buf, 0, sizeof(pic->buf));
+ pic->extended_data = pic->data;
+ pic->extended_buf = NULL;
+
+ if (!(avctx->codec->capabilities & AV_CODEC_CAP_DR1) || !planes)
+ goto fallback;
+ if (!gpu || !gpu->limits.thread_safe || !gpu->limits.max_mapped_size ||
+ !gpu->limits.host_cached)
+ {
+ goto fallback;
+ }
+
+ avcodec_align_dimensions2(avctx, &width, &height, alignment);
+ if ((ret = av_image_fill_linesizes(pic->linesize, pic->format, width)))
+ return ret;
+
+ for (int p = 0; p < planes; p++) {
+ alignment[p] = PL_LCM(alignment[p], gpu->limits.align_tex_xfer_pitch);
+ alignment[p] = PL_LCM(alignment[p], gpu->limits.align_tex_xfer_offset);
+ alignment[p] = PL_LCM(alignment[p], data[p].pixel_stride);
+ pic->linesize[p] = PL_ALIGN(pic->linesize[p], alignment[p]);
+ }
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 56, 100)
+ ret = av_image_fill_plane_sizes(planesize, pic->format, height, (ptrdiff_t[4]) {
+ pic->linesize[0], pic->linesize[1], pic->linesize[2], pic->linesize[3],
+ });
+ if (ret < 0)
+ return ret;
+#else
+ uint8_t *ptrs[4], * const base = (uint8_t *) 0x10000;
+ ret = av_image_fill_pointers(ptrs, pic->format, height, base, pic->linesize);
+ if (ret < 0)
+ return ret;
+ for (int p = 0; p < 4; p++)
+ planesize[p] = (uintptr_t) ptrs[p] - (uintptr_t) base;
+#endif
+
+ for (int p = 0; p < planes; p++) {
+ const size_t buf_size = planesize[p] + alignment[p];
+ if (buf_size > gpu->limits.max_mapped_size) {
+ av_frame_unref(pic);
+ goto fallback;
+ }
+
+ alloc = malloc(sizeof(*alloc));
+ if (!alloc) {
+ av_frame_unref(pic);
+ return AVERROR(ENOMEM);
+ }
+
+ *alloc = (struct pl_avalloc) {
+ .magic = { PL_MAGIC0, PL_MAGIC1 },
+ .gpu = gpu,
+ .buf = pl_buf_create(gpu, pl_buf_params(
+ .size = buf_size,
+ .memory_type = PL_BUF_MEM_HOST,
+ .host_mapped = true,
+ .storable = desc->flags & AV_PIX_FMT_FLAG_BE,
+ )),
+ };
+
+ if (!alloc->buf) {
+ free(alloc);
+ av_frame_unref(pic);
+ return AVERROR(ENOMEM);
+ }
+
+ pic->data[p] = (uint8_t *) PL_ALIGN((uintptr_t) alloc->buf->data, alignment[p]);
+ pic->buf[p] = av_buffer_create(alloc->buf->data, buf_size, pl_avalloc_free, alloc, 0);
+ if (!pic->buf[p]) {
+ pl_buf_destroy(gpu, &alloc->buf);
+ free(alloc);
+ av_frame_unref(pic);
+ return AVERROR(ENOMEM);
+ }
+ }
+
+ return 0;
+
+fallback:
+ return avcodec_default_get_buffer2(avctx, pic, flags);
+}
+
+#undef PL_MAGIC0
+#undef PL_MAGIC1
+#undef PL_ALIGN
+#undef PL_MAX
+
+#endif // LIBPLACEBO_LIBAV_H_
diff --git a/src/include/libplacebo/utils/upload.h b/src/include/libplacebo/utils/upload.h
new file mode 100644
index 0000000..9e8d436
--- /dev/null
+++ b/src/include/libplacebo/utils/upload.h
@@ -0,0 +1,153 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_UPLOAD_H_
+#define LIBPLACEBO_UPLOAD_H_
+
+#include <stdint.h>
+
+#include <libplacebo/gpu.h>
+#include <libplacebo/renderer.h>
+
+PL_API_BEGIN
+
+// This file contains a utility function to assist in uploading data from host
+// memory to a texture. In particular, the texture will be suitable for use as
+// a `pl_plane`.
+
+// Description of the host representation of an image plane
+struct pl_plane_data {
+ enum pl_fmt_type type; // meaning of the data (must not be UINT or SINT)
+ int width, height; // dimensions of the plane
+ int component_size[4]; // size in bits of each coordinate
+ int component_pad[4]; // ignored bits preceding each component
+ int component_map[4]; // semantic meaning of each component (pixel order)
+ size_t pixel_stride; // offset in bytes between pixels (required)
+ size_t row_stride; // offset in bytes between rows (optional)
+ bool swapped; // pixel data is endian-swapped (non-native)
+
+ // Similar to `pl_tex_transfer_params`, you can either upload from a raw
+ // pointer address, or a buffer + offset. Again, the use of these two
+ // mechanisms is mutually exclusive.
+ //
+ // 1. Uploading from host memory
+ const void *pixels; // the actual data underlying this plane
+
+ // 2. Uploading from a buffer (requires `pl_gpu_limits.buf_transfer`)
+ pl_buf buf; // the buffer to use
+ size_t buf_offset; // offset of data within buffer, must be a
+ // multiple of `pixel_stride` as well as of 4
+
+ // Similar to `pl_tex_transfer_params.callback`, this allows turning the
+ // upload of a plane into an asynchronous upload. The same notes apply.
+ void (*callback)(void *priv);
+ void *priv;
+
+ // Note: When using this together with `pl_frame`, there is some amount of
+ // overlap between `component_pad` and `pl_color_repr.bits`. Some key
+ // differences between the two:
+ //
+ // - the bits from `component_pad` are ignored; whereas the superfluous bits
+ // in a `pl_color_repr` must be 0.
+ // - the `component_pad` exists to align the component size and placement
+ // with the capabilities of GPUs; the `pl_color_repr` exists to control
+ // the semantics of the color samples on a finer granularity.
+ // - the `pl_color_repr` applies to the color sample as a whole, and
+ // therefore applies to all planes; the `component_pad` can be different
+ // for each plane.
+ // - `component_pad` interacts with float textures by moving the actual
+ // float in memory. `pl_color_repr` interacts with float data as if
+ // the float was converted from an integer under full range semantics.
+ //
+ // To help establish the motivating difference, a typical example of a use
+ // case would be yuv420p10. Since 10-bit GPU texture support is limited,
+ // and working with non-byte-aligned pixels is awkward in general, the
+ // convention is to represent yuv420p10 as 16-bit samples with either the
+ // high or low bits set to 0. In this scenario, the `component_size` of the
+ // `pl_plane_data` and `pl_bit_encoding.sample_depth` would be 16, while
+ // the `pl_bit_encoding.color_depth` would be 10 (and additionally, the
+ // `pl_bit_encoding.bit_shift` would be either 0 or 6, depending on
+ // whether the low or the high bits are used).
+ //
+ // On the contrary, something like a packed, 8-bit XBGR format (where the
+ // X bits are ignored and may contain garbage) would set `component_pad[0]`
+ // to 8, and the component_size[0:2] (respectively) to 8 as well.
+ //
+ // As a general rule of thumb, for maximum compatibility, you should try
+ // and align component_size/component_pad to multiples of 8 and explicitly
+ // clear any remaining superfluous bits (+ use `pl_color_repr.bits` to
+ // ensure they're decoded correctly). You should also try to align the
+ // `pixel_stride` to a power of two.
+};
+
+// Fills in the `component_size`, `component_pad` and `component_map` fields
+// based on the supplied mask for each component (in semantic order, i.e.
+// RGBA). Each element of `mask` must have a contiguous range of set bits.
+PL_API void pl_plane_data_from_mask(struct pl_plane_data *data, uint64_t mask[4]);
+
+// Fills in the `component_size`, `component_pad` and `component_map` fields
+// based on the supplied sizes (in bits) and shift of each component (in
+// semantic order).
+//
+// Similar to `pl_plane_data_from_mask` but not limited to 64-bit pixels.
+PL_API void pl_plane_data_from_comps(struct pl_plane_data *data, int size[4],
+ int shift[4]);
+
+// Helper function to take a `pl_plane_data` struct and try and improve its
+// alignment to make it more likely to correspond to a real `pl_fmt`. It does
+// this by attempting to round each component up to the nearest byte boundary.
+// This relies on the assumption (true in practice) that superfluous bits of
+// byte-misaligned formats are explicitly set to 0.
+//
+// The resulting shift must be consistent across all components, in which case
+// it's returned in `out_bits`. If no alignment was possible, `out_bits` is set
+// to {0}, and this function returns false.
+PL_API bool pl_plane_data_align(struct pl_plane_data *data, struct pl_bit_encoding *out_bits);
+
+// Helper function to find a suitable `pl_fmt` based on a pl_plane_data's
+// requirements. This is called internally by `pl_upload_plane`, but it's
+// exposed to users both as a convenience and so they may pre-emptively check
+// if a format would be supported without actually having to attempt the upload.
+PL_API pl_fmt pl_plane_find_fmt(pl_gpu gpu, int out_map[4], const struct pl_plane_data *data);
+
+// Upload an image plane to a texture, and output the resulting `pl_plane`
+// struct to `out_plane` (optional). `tex` must be a valid pointer to a texture
+// (or NULL), which will be destroyed and reinitialized if it does not already
+// exist or is incompatible. Returns whether successful.
+//
+// The resulting texture is guaranteed to be `sampleable`, and it will also try
+// and maximize compatibility with the other `pl_renderer` requirements
+// (blittable, linear filterable, etc.).
+//
+// Note: `out_plane->shift_x/y` and `out_plane->flipped` are left
+// uninitialized, and should be set explicitly by the user.
+PL_API bool pl_upload_plane(pl_gpu gpu, struct pl_plane *out_plane,
+ pl_tex *tex, const struct pl_plane_data *data);
+
+// Like `pl_upload_plane`, but only creates an uninitialized texture object
+// rather than actually performing an upload. This can be useful to, for
+// example, prepare textures to be used as the target of rendering.
+//
+// The resulting texture is guaranteed to be `renderable`, and it will also try
+// to maximize compatibility with the other `pl_renderer` requirements
+// (blittable, storable, etc.).
+PL_API bool pl_recreate_plane(pl_gpu gpu, struct pl_plane *out_plane,
+ pl_tex *tex, const struct pl_plane_data *data);
+
+PL_API_END
+
+#endif // LIBPLACEBO_UPLOAD_H_
diff --git a/src/include/libplacebo/vulkan.h b/src/include/libplacebo/vulkan.h
new file mode 100644
index 0000000..4e5db95
--- /dev/null
+++ b/src/include/libplacebo/vulkan.h
@@ -0,0 +1,638 @@
+/*
+ * This file is part of libplacebo.
+ *
+ * libplacebo is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libplacebo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBPLACEBO_VULKAN_H_
+#define LIBPLACEBO_VULKAN_H_
+
+#include <vulkan/vulkan.h>
+#include <libplacebo/gpu.h>
+#include <libplacebo/swapchain.h>
+
+PL_API_BEGIN
+
+#define PL_VK_MIN_VERSION VK_API_VERSION_1_2
+
+// Structure representing a VkInstance. Using this is not required.
+typedef const struct pl_vk_inst_t {
+ VkInstance instance;
+
+ // The Vulkan API version supported by this VkInstance.
+ uint32_t api_version;
+
+ // The associated vkGetInstanceProcAddr pointer.
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+
+ // The instance extensions that were successfully enabled, including
+ // extensions enabled by libplacebo internally. May contain duplicates.
+ const char * const *extensions;
+ int num_extensions;
+
+ // The instance layers that were successfully enabled, including
+ // layers enabled by libplacebo internally. May contain duplicates.
+ const char * const *layers;
+ int num_layers;
+} *pl_vk_inst;
+
+struct pl_vk_inst_params {
+ // If set, enable the debugging and validation layers. These should
+ // generally be lightweight and relatively harmless to enable.
+ bool debug;
+
+ // If set, also enable GPU-assisted verification and best practices
+ // layers. (Note: May cause substantial slowdown and/or result in lots of
+ // false positive spam)
+ bool debug_extra;
+
+ // If nonzero, restricts the Vulkan API version to be at most this. This
+ // is only really useful for explicitly testing backwards compatibility.
+ uint32_t max_api_version;
+
+ // Pointer to a user-provided `vkGetInstanceProcAddr`. If this is NULL,
+ // libplacebo will use the directly linked version (if available).
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+
+ // Enables extra instance extensions. Instance creation will fail if these
+ // extensions are not all supported. The user may use this to enable e.g.
+ // windowing system integration.
+ const char * const *extensions;
+ int num_extensions;
+
+ // Enables extra optional instance extensions. These are opportunistically
+ // enabled if supported by the device, but otherwise skipped.
+ const char * const *opt_extensions;
+ int num_opt_extensions;
+
+ // Enables extra layers. Instance creation will fail if these layers are
+ // not all supported.
+ //
+ // NOTE: Layers needed for required/optional extensions are automatically
+ // enabled. The user does not specifically need to enable layers related
+ // to extension support.
+ const char * const *layers;
+ int num_layers;
+
+ // Enables extra optional layers. These are opportunistically enabled if
+ // supported by the platform, but otherwise skipped.
+ const char * const *opt_layers;
+ int num_opt_layers;
+};
+
+#define pl_vk_inst_params(...) (&(struct pl_vk_inst_params) { __VA_ARGS__ })
+PL_API extern const struct pl_vk_inst_params pl_vk_inst_default_params;
+
+// Helper function to simplify instance creation. The user could also bypass
+// these helpers and do it manually, but this function is provided as a
+// convenience. It also sets up a debug callback which forwards all vulkan
+// messages to the `pl_log` callback.
+PL_API pl_vk_inst pl_vk_inst_create(pl_log log, const struct pl_vk_inst_params *params);
+PL_API void pl_vk_inst_destroy(pl_vk_inst *inst);
+
+struct pl_vulkan_queue {
+ uint32_t index; // Queue family index
+ uint32_t count; // Queue family count
+};
+
+// Structure representing the actual vulkan device and associated GPU instance
+typedef const struct pl_vulkan_t *pl_vulkan;
+struct pl_vulkan_t {
+ pl_gpu gpu;
+
+ // The vulkan objects in use. The user may use this for their own purposes,
+ // but please note that the lifetime is tied to the lifetime of the
+ // pl_vulkan object, and must not be destroyed by the user. Note that the
+ // created vulkan device may have any number of queues and queue family
+ // assignments; so using it for queue submission commands is ill-advised.
+ VkInstance instance;
+ VkPhysicalDevice phys_device;
+ VkDevice device;
+
+ // The associated vkGetInstanceProcAddr pointer.
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+
+ // The Vulkan API version supported by this VkPhysicalDevice.
+ uint32_t api_version;
+
+ // The device extensions that were successfully enabled, including
+ // extensions enabled by libplacebo internally. May contain duplicates.
+ const char * const *extensions;
+ int num_extensions;
+
+ // The device features that were enabled at device creation time.
+ //
+ // Note: Whenever a feature flag is ambiguious between several alternative
+ // locations, for completeness' sake, we include both.
+ const VkPhysicalDeviceFeatures2 *features;
+
+ // The explicit queue families we are using to provide a given capability.
+ struct pl_vulkan_queue queue_graphics; // provides VK_QUEUE_GRAPHICS_BIT
+ struct pl_vulkan_queue queue_compute; // provides VK_QUEUE_COMPUTE_BIT
+ struct pl_vulkan_queue queue_transfer; // provides VK_QUEUE_TRANSFER_BIT
+
+ // Functions for locking a queue. These must be used to lock VkQueues for
+ // submission or other related operations when sharing the VkDevice between
+ // multiple threads, Using this on queue families or indices not contained
+ // in `queues` is undefined behavior.
+ void (*lock_queue)(pl_vulkan vk, uint32_t qf, uint32_t qidx);
+ void (*unlock_queue)(pl_vulkan vk, uint32_t qf, uint32_t qidx);
+
+ // --- Deprecated fields
+
+ // These are the same active queue families and their queue counts in list
+ // form. This list does not contain duplicates, nor any extra queues
+ // enabled at device creation time. Deprecated in favor of querying
+ // `vkGetPhysicalDeviceQueueFamilyProperties` directly.
+ const struct pl_vulkan_queue *queues PL_DEPRECATED;
+ int num_queues PL_DEPRECATED;
+};
+
+struct pl_vulkan_params {
+ // The vulkan instance. Optional, if NULL then libplacebo will internally
+ // create a VkInstance with the settings from `instance_params`.
+ //
+ // Note: The VkInstance provided by the user *MUST* be created with a
+ // VkApplicationInfo.apiVersion of PL_VK_MIN_VERSION or higher.
+ VkInstance instance;
+
+ // Pointer to `vkGetInstanceProcAddr`. If this is NULL, libplacebo will
+ // use the directly linked version (if available).
+ //
+ // Note: This overwrites the same value from `instance_params`.
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+
+ // Configures the settings used for creating an internal vulkan instance.
+ // May be NULL. Ignored if `instance` is set.
+ const struct pl_vk_inst_params *instance_params;
+
+ // When choosing the device, rule out all devices that don't support
+ // presenting to this surface. When creating a device, enable all extensions
+ // needed to ensure we can present to this surface. Optional. Only legal
+ // when specifying an existing VkInstance to use.
+ VkSurfaceKHR surface;
+
+ // --- Physical device selection options
+
+ // The vulkan physical device. May be set by the caller to indicate the
+ // physical device to use. Otherwise, libplacebo will pick the "best"
+ // available GPU, based on the advertised device type. (i.e., it will
+ // prefer discrete GPUs over integrated GPUs). Only legal when specifying
+ // an existing VkInstance to use.
+ VkPhysicalDevice device;
+
+ // When choosing the device, only choose a device with this exact name.
+ // This overrides `allow_software`. No effect if `device` is set. Note: A
+ // list of devices and their names are logged at level PL_LOG_INFO.
+ const char *device_name;
+
+ // When choosing the device, only choose a device with this exact UUID.
+ // This overrides `allow_software` and `device_name`. No effect if `device`
+ // is set.
+ uint8_t device_uuid[16];
+
+ // When choosing the device, controls whether or not to also allow software
+ // GPUs. No effect if `device` or `device_name` are set.
+ bool allow_software;
+
+ // --- Logical device creation options
+
+ // Controls whether or not to allow asynchronous transfers, using transfer
+ // queue families, if supported by the device. This can be significantly
+ // faster and more power efficient, and also allows streaming uploads in
+ // parallel with rendering commands. Enabled by default.
+ bool async_transfer;
+
+ // Controls whether or not to allow asynchronous compute, using dedicated
+ // compute queue families, if supported by the device. On some devices,
+ // these can allow the GPU to schedule compute shaders in parallel with
+ // fragment shaders. Enabled by default.
+ bool async_compute;
+
+ // Limits the number of queues to use. If left as 0, libplacebo will use as
+ // many queues as the device supports. Multiple queues can result in
+ // improved efficiency when submitting multiple commands that can entirely
+ // or partially execute in parallel. Defaults to 1, since using more queues
+ // can actually decrease performance.
+ //
+ // Note: libplacebo will always *create* logical devices with all available
+ // queues for a given QF enabled, regardless of this setting.
+ int queue_count;
+
+ // Bitmask of extra queue families to enable. If set, then *all* queue
+ // families matching *any* of these flags will be enabled at device
+ // creation time. Setting this to VK_QUEUE_FLAG_BITS_MAX_ENUM effectively
+ // enables all queue families supported by the device.
+ VkQueueFlags extra_queues;
+
+ // Enables extra device extensions. Device creation will fail if these
+ // extensions are not all supported. The user may use this to enable e.g.
+ // interop extensions.
+ const char * const *extensions;
+ int num_extensions;
+
+ // Enables extra optional device extensions. These are opportunistically
+ // enabled if supported by the device, but otherwise skipped.
+ const char * const *opt_extensions;
+ int num_opt_extensions;
+
+ // Optional extra features to enable at device creation time. These are
+ // opportunistically enabled if supported by the physical device, but
+ // otherwise kept disabled.
+ const VkPhysicalDeviceFeatures2 *features;
+
+ // --- Misc/debugging options
+
+ // Restrict specific features to e.g. work around driver bugs, or simply
+ // for testing purposes
+ int max_glsl_version; // limit the maximum GLSL version
+ uint32_t max_api_version; // limit the maximum vulkan API version
+};
+
+// Default/recommended parameters. Should generally be safe and efficient.
+#define PL_VULKAN_DEFAULTS \
+ .async_transfer = true, \
+ .async_compute = true, \
+ /* enabling multiple queues often decreases perf */ \
+ .queue_count = 1,
+
+#define pl_vulkan_params(...) (&(struct pl_vulkan_params) { PL_VULKAN_DEFAULTS __VA_ARGS__ })
+PL_API extern const struct pl_vulkan_params pl_vulkan_default_params;
+
+// Creates a new vulkan device based on the given parameters and initializes
+// a new GPU. If `params` is left as NULL, it defaults to
+// &pl_vulkan_default_params.
+//
+// Thread-safety: Safe
+PL_API pl_vulkan pl_vulkan_create(pl_log log, const struct pl_vulkan_params *params);
+
+// Destroys the vulkan device and all associated objects, except for the
+// VkInstance provided by the user.
+//
+// Note that all resources allocated from this vulkan object (e.g. via the
+// `vk->ra` or using `pl_vulkan_create_swapchain`) *must* be explicitly
+// destroyed by the user before calling this.
+//
+// Also note that this function will block until all in-flight GPU commands are
+// finished processing. You can avoid this by manually calling `pl_gpu_finish`
+// before `pl_vulkan_destroy`.
+PL_API void pl_vulkan_destroy(pl_vulkan *vk);
+
+// For a `pl_gpu` backed by `pl_vulkan`, this function can be used to retrieve
+// the underlying `pl_vulkan`. Returns NULL for any other type of `gpu`.
+PL_API pl_vulkan pl_vulkan_get(pl_gpu gpu);
+
+struct pl_vulkan_device_params {
+ // The instance to use. Required!
+ //
+ // Note: The VkInstance provided by the user *must* be created with a
+ // VkApplicationInfo.apiVersion of PL_VK_MIN_VERSION or higher.
+ VkInstance instance;
+
+ // Mirrored from `pl_vulkan_params`. All of these fields are optional.
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+ VkSurfaceKHR surface;
+ const char *device_name;
+ uint8_t device_uuid[16];
+ bool allow_software;
+};
+
+#define pl_vulkan_device_params(...) (&(struct pl_vulkan_device_params) { __VA_ARGS__ })
+
+// Helper function to choose the best VkPhysicalDevice, given a VkInstance.
+// This uses the same logic as `pl_vulkan_create` uses internally. If no
+// matching device was found, this returns VK_NULL_HANDLE.
+PL_API VkPhysicalDevice pl_vulkan_choose_device(pl_log log,
+ const struct pl_vulkan_device_params *params);
+
+struct pl_vulkan_swapchain_params {
+ // The surface to use for rendering. Required, the user is in charge of
+ // creating this. Must belong to the same VkInstance as `vk->instance`.
+ VkSurfaceKHR surface;
+
+ // The preferred presentation mode. See the vulkan documentation for more
+ // information about these. If the device/surface combination does not
+ // support this mode, libplacebo will fall back to VK_PRESENT_MODE_FIFO_KHR.
+ //
+ // Warning: Leaving this zero-initialized is the same as having specified
+ // VK_PRESENT_MODE_IMMEDIATE_KHR, which is probably not what the user
+ // wants!
+ VkPresentModeKHR present_mode;
+
+ // Allow up to N in-flight frames. This essentially controls how many
+ // rendering commands may be queued up at the same time. See the
+ // documentation for `pl_swapchain_get_latency` for more information. For
+ // vulkan specifically, we are only able to wait until the GPU has finished
+ // rendering a frame - we are unable to wait until the display has actually
+ // finished displaying it. So this only provides a rough guideline.
+ // Optional, defaults to 3.
+ int swapchain_depth;
+
+ // This suppresses automatic recreation of the swapchain when any call
+ // returns VK_SUBOPTIMAL_KHR. Normally, libplacebo will recreate the
+ // swapchain internally on the next `pl_swapchain_start_frame`. If enabled,
+ // clients are assumed to take care of swapchain recreations themselves, by
+ // calling `pl_swapchain_resize` as appropriate. libplacebo will tolerate
+ // the "suboptimal" status indefinitely.
+ bool allow_suboptimal;
+
+ // Disable high-bit (10 or more) SDR formats. May help work around buggy
+ // drivers which don't dither properly when outputting high bit depth
+ // SDR backbuffers to 8-bit screens.
+ bool disable_10bit_sdr;
+};
+
+#define pl_vulkan_swapchain_params(...) (&(struct pl_vulkan_swapchain_params) { __VA_ARGS__ })
+
+// Creates a new vulkan swapchain based on an existing VkSurfaceKHR. Using this
+// function requires that the vulkan device was created with the
+// VK_KHR_swapchain extension. The easiest way of accomplishing this is to set
+// the `pl_vulkan_params.surface` explicitly at creation time.
+PL_API pl_swapchain pl_vulkan_create_swapchain(pl_vulkan vk,
+ const struct pl_vulkan_swapchain_params *params);
+
+// This will return true if the vulkan swapchain is internally detected
+// as being suboptimal (VK_SUBOPTIMAL_KHR). This might be of use to clients
+// who have `params->allow_suboptimal` enabled.
+PL_API bool pl_vulkan_swapchain_suboptimal(pl_swapchain sw);
+
+// Vulkan interop API, for sharing a single VkDevice (and associated vulkan
+// resources) directly with the API user. The use of this API is a bit sketchy
+// and requires careful communication of Vulkan API state.
+
+struct pl_vulkan_import_params {
+ // The vulkan instance. Required.
+ //
+ // Note: The VkInstance provided by the user *must* be created with a
+ // VkApplicationInfo.apiVersion of PL_VK_MIN_VERSION or higher.
+ VkInstance instance;
+
+ // Pointer to `vkGetInstanceProcAddr`. If this is NULL, libplacebo will
+ // use the directly linked version (if available).
+ PFN_vkGetInstanceProcAddr get_proc_addr;
+
+ // The physical device selected by the user. Required.
+ VkPhysicalDevice phys_device;
+
+ // The logical device created by the user. Required.
+ VkDevice device;
+
+ // --- Logical device parameters
+
+ // List of all device-level extensions that were enabled. (Instance-level
+ // extensions need not be re-specified here, since it's guaranteed that any
+ // instance-level extensions that device-level extensions depend on were
+ // enabled at the instance level)
+ const char * const *extensions;
+ int num_extensions;
+
+ // Enabled queue families. At least `queue_graphics` is required.
+ //
+ // It's okay for multiple queue families to be specified with the same
+ // index, e.g. in the event that a dedicated compute queue also happens to
+ // be the dedicated transfer queue.
+ //
+ // It's also okay to leave the queue struct as {0} in the event that no
+ // dedicated queue exists for a given operation type. libplacebo will
+ // automatically fall back to using e.g. the graphics queue instead.
+ struct pl_vulkan_queue queue_graphics; // must support VK_QUEUE_GRAPHICS_BIT
+ struct pl_vulkan_queue queue_compute; // must support VK_QUEUE_COMPUTE_BIT
+ struct pl_vulkan_queue queue_transfer; // must support VK_QUEUE_TRANSFER_BIT
+
+ // Enabled VkPhysicalDeviceFeatures. The device *must* be created with
+ // all of the features in `pl_vulkan_required_features` enabled.
+ const VkPhysicalDeviceFeatures2 *features;
+
+ // Functions for locking a queue. If set, these will be used instead of
+ // libplacebo's internal functions for `pl_vulkan.(un)lock_queue`.
+ void (*lock_queue)(void *ctx, uint32_t qf, uint32_t qidx);
+ void (*unlock_queue)(void *ctx, uint32_t qf, uint32_t qidx);
+ void *queue_ctx;
+
+ // --- Misc/debugging options
+
+ // Restrict specific features to e.g. work around driver bugs, or simply
+ // for testing purposes. See `pl_vulkan_params` for a description of these.
+ int max_glsl_version;
+ uint32_t max_api_version;
+};
+
+#define pl_vulkan_import_params(...) (&(struct pl_vulkan_import_params) { __VA_ARGS__ })
+
+// For purely informative reasons, this contains a list of extensions and
+// device features that libplacebo *can* make use of. These are all strictly
+// optional, but provide a hint to the API user as to what might be worth
+// enabling at device creation time.
+//
+// Note: This also includes physical device features provided by extensions.
+// They are all provided using extension-specific features structs, rather
+// than the more general purpose VkPhysicalDeviceVulkan11Features etc.
+PL_API extern const char * const pl_vulkan_recommended_extensions[];
+PL_API extern const int pl_vulkan_num_recommended_extensions;
+PL_API extern const VkPhysicalDeviceFeatures2 pl_vulkan_recommended_features;
+
+// A list of device features that are required by libplacebo. These
+// *must* be provided by imported Vulkan devices.
+//
+// Note: `pl_vulkan_recommended_features` does not include this list.
+PL_API extern const VkPhysicalDeviceFeatures2 pl_vulkan_required_features;
+
+// Import an existing VkDevice instead of creating a new one, and wrap it into
+// a `pl_vulkan` abstraction. It's safe to `pl_vulkan_destroy` this, which will
+// destroy application state related to libplacebo but leave the underlying
+// VkDevice intact.
+PL_API pl_vulkan pl_vulkan_import(pl_log log, const struct pl_vulkan_import_params *params);
+
+struct pl_vulkan_wrap_params {
+ // The image itself. It *must* be usable concurrently by all of the queue
+ // family indices listed in `pl_vulkan->queues`. Note that this requires
+ // the use of VK_SHARING_MODE_CONCURRENT if `pl_vulkan->num_queues` is
+ // greater than 1. If this is difficult to achieve for the user, then
+ // `async_transfer` / `async_compute` should be turned off, which
+ // guarantees the use of only one queue family.
+ VkImage image;
+
+ // Which aspect of `image` to wrap. Only useful for wrapping individual
+ // sub-planes of planar images. If left as 0, it defaults to the entire
+ // image (i.e. the union of VK_IMAGE_ASPECT_PLANE_N_BIT for planar formats,
+ // and VK_IMAGE_ASPECT_COLOR_BIT otherwise).
+ VkImageAspectFlags aspect;
+
+ // The image's dimensions (unused dimensions must be 0)
+ int width;
+ int height;
+ int depth;
+
+ // The image's format. libplacebo will try to map this to an equivalent
+ // pl_fmt. If no compatible pl_fmt is found, wrapping will fail.
+ VkFormat format;
+
+ // The usage flags the image was created with. libplacebo will set the
+ // pl_tex capabilities to include whatever it can, as determined by the set
+ // of enabled usage flags.
+ VkImageUsageFlags usage;
+
+ // See `pl_tex_params`
+ void *user_data;
+ pl_debug_tag debug_tag;
+};
+
+#define pl_vulkan_wrap_params(...) (&(struct pl_vulkan_wrap_params) { \
+ .debug_tag = PL_DEBUG_TAG, \
+ __VA_ARGS__ \
+ })
+
+// Wraps an external VkImage into a pl_tex abstraction. By default, the image
+// is considered "held" by the user and must be released before calling any
+// pl_tex_* API calls on it (see `pl_vulkan_release`).
+//
+// This wrapper can be destroyed by simply calling `pl_tex_destroy` on it,
+// which will not destroy the underlying VkImage. If a pl_tex wrapper is
+// destroyed while an image is not currently being held by the user, that
+// image is left in an undefined state.
+//
+// Wrapping the same VkImage multiple times is undefined behavior, as is trying
+// to wrap an image belonging to a different VkDevice than the one in use by
+// `gpu`.
+//
+// This function may fail, in which case it returns NULL.
+PL_API pl_tex pl_vulkan_wrap(pl_gpu gpu, const struct pl_vulkan_wrap_params *params);
+
+// Analogous to `pl_vulkan_wrap`, this function takes any `pl_tex` (including
+// ones created by `pl_tex_create`) and unwraps it to expose the underlying
+// VkImage to the user. Unlike `pl_vulkan_wrap`, this `pl_tex` is *not*
+// considered held after calling this function - the user must explicitly
+// `pl_vulkan_hold` before accessing the VkImage.
+//
+// `out_format` and `out_flags` will be updated to hold the VkImage's
+// format and usage flags. (Optional)
+PL_API VkImage pl_vulkan_unwrap(pl_gpu gpu, pl_tex tex,
+ VkFormat *out_format, VkImageUsageFlags *out_flags);
+
+// Represents a vulkan semaphore/value pair (for compatibility with timeline
+// semaphores). When using normal, binary semaphores, `value` may be ignored.
+typedef struct pl_vulkan_sem {
+ VkSemaphore sem;
+ uint64_t value;
+} pl_vulkan_sem;
+
+struct pl_vulkan_hold_params {
+ // The Vulkan image to hold. It will be marked as held. Attempting to
+ // perform any pl_tex_* operation (except pl_tex_destroy) on a held image
+ // is undefined behavior.
+ pl_tex tex;
+
+ // The layout to transition the image to when holding. Alternatively, a
+ // pointer to receive the current image layout. If `out_layout` is
+ // provided, `layout` is ignored.
+ VkImageLayout layout;
+ VkImageLayout *out_layout;
+
+ // The queue family index to transition the image to. This can be used with
+ // VK_QUEUE_FAMILY_EXTERNAL to transition the image to an external API. As
+ // a special case, if set to VK_QUEUE_FAMILY_IGNORED, libplacebo will not
+ // transition the image, even if this image was not set up for concurrent
+ // usage. Ignored for concurrent images.
+ uint32_t qf;
+
+ // The semaphore to fire when the image is available for use. (Required)
+ pl_vulkan_sem semaphore;
+};
+
+#define pl_vulkan_hold_params(...) (&(struct pl_vulkan_hold_params) { __VA_ARGS__ })
+
+// "Hold" a shared image, transferring control over the image to the user.
+// Returns whether successful.
+PL_API bool pl_vulkan_hold_ex(pl_gpu gpu, const struct pl_vulkan_hold_params *params);
+
+struct pl_vulkan_release_params {
+ // The image to be released. It must be marked as "held". Performing any
+ // operation on the VkImage underlying this `pl_tex` while it is not being
+ // held by the user is undefined behavior.
+ pl_tex tex;
+
+ // The current layout of the image at the point in time when `semaphore`
+ // fires, or if no semaphore is specified, at the time of call.
+ VkImageLayout layout;
+
+ // The queue family index to transition the image to. This can be used with
+ // VK_QUEUE_FAMILY_EXTERNAL to transition the image rom an external API. As
+ // a special case, if set to VK_QUEUE_FAMILY_IGNORED, libplacebo will not
+ // transition the image, even if this image was not set up for concurrent
+ // usage. Ignored for concurrent images.
+ uint32_t qf;
+
+ // The semaphore to wait on before libplacebo will actually use or modify
+ // the image. (Optional)
+ //
+ // Note: the lifetime of `semaphore` is indeterminate, and destroying it
+ // while the texture is still depending on that semaphore is undefined
+ // behavior.
+ //
+ // Technically, the only way to be sure that it's safe to free is to use
+ // `pl_gpu_finish()` or similar (e.g. `pl_vulkan_destroy` or
+ // `vkDeviceWaitIdle`) after another operation involving `tex` has been
+ // emitted (or the texture has been destroyed).
+ //
+ //
+ // Warning: If `tex` is a planar image (`pl_fmt.num_planes > 0`), and
+ // `semaphore` is specified, it *must* be a timeline semaphore! Failure to
+ // respect this will result in undefined behavior. This warning does not
+ // apply to individual planes (as exposed by `pl_tex.planes`).
+ pl_vulkan_sem semaphore;
+};
+
+#define pl_vulkan_release_params(...) (&(struct pl_vulkan_release_params) { __VA_ARGS__ })
+
+// "Release" a shared image, transferring control to libplacebo.
+PL_API void pl_vulkan_release_ex(pl_gpu gpu, const struct pl_vulkan_release_params *params);
+
+struct pl_vulkan_sem_params {
+ // The type of semaphore to create.
+ VkSemaphoreType type;
+
+ // For VK_SEMAPHORE_TYPE_TIMELINE, sets the initial timeline value.
+ uint64_t initial_value;
+
+ // If set, exports this VkSemaphore to the handle given in `out_handle`.
+ // The user takes over ownership, and should manually close it before
+ // destroying this VkSemaphore (via `pl_vulkan_sem_destroy`).
+ enum pl_handle_type export_handle;
+ union pl_handle *out_handle;
+
+ // Optional debug tag to identify this semaphore.
+ pl_debug_tag debug_tag;
+};
+
+#define pl_vulkan_sem_params(...) (&(struct pl_vulkan_sem_params) { \
+ .debug_tag = PL_DEBUG_TAG, \
+ __VA_ARGS__ \
+ })
+
+// Helper functions to create and destroy vulkan semaphores. Returns
+// VK_NULL_HANDLE on failure.
+PL_API VkSemaphore pl_vulkan_sem_create(pl_gpu gpu, const struct pl_vulkan_sem_params *params);
+PL_API void pl_vulkan_sem_destroy(pl_gpu gpu, VkSemaphore *semaphore);
+
+// Backwards-compatibility wrappers for older versions of the API.
+PL_DEPRECATED PL_API bool pl_vulkan_hold(pl_gpu gpu, pl_tex tex, VkImageLayout layout,
+ pl_vulkan_sem sem_out);
+PL_DEPRECATED PL_API bool pl_vulkan_hold_raw(pl_gpu gpu, pl_tex tex, VkImageLayout *out_layout,
+ pl_vulkan_sem sem_out);
+PL_DEPRECATED PL_API void pl_vulkan_release(pl_gpu gpu, pl_tex tex, VkImageLayout layout,
+ pl_vulkan_sem sem_in);
+
+PL_API_END
+
+#endif // LIBPLACEBO_VULKAN_H_