/*
* 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 .
*/
#pragma once
#include
#include
#include "common.h"
#include "cache.h"
#include "log.h"
#include "gpu.h"
#include
// This represents an identifier (e.g. name of function, uniform etc.) for
// a shader resource. Not human-readable.
typedef unsigned short ident_t;
#define $ "_%hx"
#define NULL_IDENT 0u
#define sh_mkident(id, name) ((ident_t) id)
#define sh_ident_tostr(id) pl_asprintf(sh->tmp, $, id)
enum {
IDENT_BITS = 8 * sizeof(ident_t),
IDENT_MASK = (uintptr_t) USHRT_MAX,
IDENT_SENTINEL = (uintptr_t) 0x20230319 << IDENT_BITS,
};
// Functions to pack/unpack an identifier into a `const char *` name field.
// Used to defer string templating of friendly names until actually necessary
static inline const char *sh_ident_pack(ident_t id)
{
return (const char *)(uintptr_t) (IDENT_SENTINEL | id);
}
static inline ident_t sh_ident_unpack(const char *name)
{
uintptr_t uname = (uintptr_t) name;
assert((uname & ~IDENT_MASK) == IDENT_SENTINEL);
return uname & IDENT_MASK;
}
enum pl_shader_buf {
SH_BUF_PRELUDE, // extra #defines etc.
SH_BUF_HEADER, // previous passes, helper function definitions, etc.
SH_BUF_BODY, // partial contents of the "current" function
SH_BUF_FOOTER, // will be appended to the end of the current function
SH_BUF_COUNT,
};
enum pl_shader_type {
SH_AUTO,
SH_COMPUTE,
SH_FRAGMENT
};
struct sh_info {
// public-facing struct
struct pl_shader_info_t info;
// internal fields
void *tmp;
pl_rc_t rc;
pl_str desc;
PL_ARRAY(const char *) steps;
};
struct pl_shader_t {
pl_log log;
void *tmp; // temporary allocations (freed on pl_shader_reset)
struct sh_info *info;
pl_str data; // pooled/recycled scratch buffer for small allocations
PL_ARRAY(pl_shader_obj) obj;
bool failed;
bool mutable;
ident_t name;
enum pl_shader_sig input, output;
int output_w;
int output_h;
bool transpose;
pl_str_builder buffers[SH_BUF_COUNT];
enum pl_shader_type type;
bool flexible_work_groups;
int group_size[2];
size_t shmem;
enum pl_sampler_type sampler_type;
char sampler_prefix;
unsigned short prefix; // pre-processed version of res.params.id
unsigned short fresh;
// Note: internally, these `pl_shader_va` etc. use raw ident_t fields
// instead of `const char *` wherever a name is required! These are
// translated to legal strings either in `pl_shader_finalize`, or inside
// the `pl_dispatch` shader compilation step.
PL_ARRAY(struct pl_shader_va) vas;
PL_ARRAY(struct pl_shader_var) vars;
PL_ARRAY(struct pl_shader_desc) descs;
PL_ARRAY(struct pl_shader_const) consts;
// cached result of `pl_shader_finalize`
struct pl_shader_res result;
};
// Free temporary resources associated with a shader. Normally called by
// pl_shader_reset(), but used internally to reduce memory waste.
void sh_deref(pl_shader sh);
// Same as `pl_shader_finalize` but doesn't generate `sh->res`, instead returns
// the string builder to be used to finalize the shader. Assumes the caller
// will access the shader's internal fields directly.
pl_str_builder sh_finalize_internal(pl_shader sh);
// Helper functions for convenience
#define SH_PARAMS(sh) ((sh)->info->info.params)
#define SH_GPU(sh) (SH_PARAMS(sh).gpu)
#define SH_CACHE(sh) pl_gpu_cache(SH_GPU(sh))
// Returns the GLSL version, defaulting to desktop 130.
struct pl_glsl_version sh_glsl(const pl_shader sh);
#define SH_FAIL(sh, ...) do { \
sh->failed = true; \
PL_ERR(sh, __VA_ARGS__); \
} while (0)
// Attempt enabling compute shaders for this pass, if possible
bool sh_try_compute(pl_shader sh, int bw, int bh, bool flex, size_t mem);
// Attempt merging a secondary shader into the current shader. Returns NULL if
// merging fails (e.g. incompatible signatures); otherwise returns an identifier
// corresponding to the generated subpass function.
//
// If successful, the subpass shader is set to an undefined failure state and
// must be explicitly reset/aborted before being re-used.
ident_t sh_subpass(pl_shader sh, pl_shader sub);
// Helpers for adding new variables/descriptors/etc. with fresh, unique
// identifier names. These will never conflict with other identifiers, even
// if the shaders are merged together.
ident_t sh_fresh(pl_shader sh, const char *name);
// Add a new shader var and return its identifier
ident_t sh_var(pl_shader sh, struct pl_shader_var sv);
// Helper functions for `sh_var`
ident_t sh_var_int(pl_shader sh, const char *name, int val, bool dynamic);
ident_t sh_var_uint(pl_shader sh, const char *name, unsigned int val, bool dynamic);
ident_t sh_var_float(pl_shader sh, const char *name, float val, bool dynamic);
ident_t sh_var_mat3(pl_shader sh, const char *name, pl_matrix3x3 val);
#define SH_INT_DYN(val) sh_var_int(sh, "const", val, true)
#define SH_UINT_DYN(val) sh_var_uint(sh, "const", val, true)
#define SH_FLOAT_DYN(val) sh_var_float(sh, "const", val, true)
#define SH_MAT3(val) sh_var_mat3(sh, "mat", val)
// Add a new shader desc and return its identifier.
ident_t sh_desc(pl_shader sh, struct pl_shader_desc sd);
// Add a new shader constant and return its identifier.
ident_t sh_const(pl_shader sh, struct pl_shader_const sc);
// Helper functions for `sh_const`
ident_t sh_const_int(pl_shader sh, const char *name, int val);
ident_t sh_const_uint(pl_shader sh, const char *name, unsigned int val);
ident_t sh_const_float(pl_shader sh, const char *name, float val);
#define SH_INT(val) sh_const_int(sh, "const", val)
#define SH_UINT(val) sh_const_uint(sh, "const", val)
#define SH_FLOAT(val) sh_const_float(sh, "const", val)
// Add a new shader va and return its identifier
ident_t sh_attr(pl_shader sh, struct pl_shader_va sva);
// Helper to add a a vec2 VA from a pl_rect2df. Returns NULL_IDENT on failure.
ident_t sh_attr_vec2(pl_shader sh, const char *name, const pl_rect2df *rc);
// Bind a texture under a given transformation and make its attributes
// available as well. If an output pointer for one of the attributes is left
// as NULL, that attribute will not be added. Returns NULL on failure. `rect`
// is optional, and defaults to the full texture if left as NULL.
//
// Note that for e.g. compute shaders, the vec2 out_pos might be a macro that
// expands to an expensive computation, and should be cached by the user.
ident_t sh_bind(pl_shader sh, pl_tex tex,
enum pl_tex_address_mode address_mode,
enum pl_tex_sample_mode sample_mode,
const char *name, const pl_rect2df *rect,
ident_t *out_pos, ident_t *out_pt);
// Incrementally build up a buffer by adding new variable elements to the
// buffer, resizing buf.buffer_vars if necessary. Returns whether or not the
// variable could be successfully added (which may fail if you try exceeding
// the size limits of the buffer type). If successful, the layout is stored
// in *out_layout (may be NULL).
bool sh_buf_desc_append(void *alloc, pl_gpu gpu,
struct pl_shader_desc *buf_desc,
struct pl_var_layout *out_layout,
const struct pl_var new_var);
size_t sh_buf_desc_size(const struct pl_shader_desc *buf_desc);
// Underlying function for appending text to a shader
#define sh_append(sh, buf, ...) \
pl_str_builder_addf((sh)->buffers[buf], __VA_ARGS__)
#define sh_append_str(sh, buf, str) \
pl_str_builder_str((sh)->buffers[buf], str)
#define GLSLP(...) sh_append(sh, SH_BUF_PRELUDE, __VA_ARGS__)
#define GLSLH(...) sh_append(sh, SH_BUF_HEADER, __VA_ARGS__)
#define GLSL(...) sh_append(sh, SH_BUF_BODY, __VA_ARGS__)
#define GLSLF(...) sh_append(sh, SH_BUF_FOOTER, __VA_ARGS__)
// Attach a description to a shader
void sh_describef(pl_shader sh, const char *fmt, ...)
PL_PRINTF(2, 3);
static inline void sh_describe(pl_shader sh, const char *desc)
{
PL_ARRAY_APPEND(sh->info, sh->info->steps, desc);
};
// Requires that the share is mutable, has an output signature compatible
// with the given input signature, as well as an output size compatible with
// the given size requirements. Errors and returns false otherwise.
bool sh_require(pl_shader sh, enum pl_shader_sig insig, int w, int h);
// Shader resources
enum pl_shader_obj_type {
PL_SHADER_OBJ_INVALID = 0,
PL_SHADER_OBJ_COLOR_MAP,
PL_SHADER_OBJ_SAMPLER,
PL_SHADER_OBJ_DITHER,
PL_SHADER_OBJ_LUT,
PL_SHADER_OBJ_AV1_GRAIN,
PL_SHADER_OBJ_FILM_GRAIN,
PL_SHADER_OBJ_RESHAPE,
};
struct pl_shader_obj_t {
enum pl_shader_obj_type type;
pl_rc_t rc;
pl_gpu gpu;
void (*uninit)(pl_gpu gpu, void *priv);
void *priv;
};
// Returns (*ptr)->priv, or NULL on failure
void *sh_require_obj(pl_shader sh, pl_shader_obj *ptr,
enum pl_shader_obj_type type, size_t priv_size,
void (*uninit)(pl_gpu gpu, void *priv));
#define SH_OBJ(sh, ptr, type, t, uninit) \
((t*) sh_require_obj(sh, ptr, type, sizeof(t), uninit))
// Initializes a PRNG. The resulting string will directly evaluate to a
// pseudorandom, uniformly distributed vec3 from [0.0,1.0]. Since this
// algorithm works by mutating a state variable, if the user wants to use the
// resulting PRNG inside a subfunction, they must add an extra `inout prng_t %s`
// with the contents of `state` to the signature. (Optional)
//
// If `temporal` is set, the PRNG will vary across frames.
ident_t sh_prng(pl_shader sh, bool temporal, ident_t *state);
// Backing memory type
enum sh_lut_type {
SH_LUT_AUTO = 0, // pick whatever makes the most sense
SH_LUT_TEXTURE, // upload as texture
SH_LUT_UNIFORM, // uniform array
SH_LUT_LITERAL, // constant / literal array in shader source (fallback)
};
// Interpolation method
enum sh_lut_method {
SH_LUT_NONE = 0, // no interpolation, integer indices
SH_LUT_LINEAR, // linear interpolation, vecN indices in range [0,1]
SH_LUT_CUBIC, // (bi/tri)cubic interpolation
SH_LUT_TETRAHEDRAL, // tetrahedral interpolation for vec3, equivalent to
// SH_LUT_LINEAR for lower dimensions
};
struct sh_lut_params {
pl_shader_obj *object;
// Type of the LUT we intend to generate.
//
// Note: If `var_type` is PL_VAR_*INT, `method` must be SH_LUT_NONE.
enum pl_var_type var_type;
enum sh_lut_type lut_type;
enum sh_lut_method method;
// For SH_LUT_TEXTURE, this can be used to override the texture's internal
// format, in which case it takes precedence over the default for `type`.
pl_fmt fmt;
// LUT dimensions. Unused dimensions may be left as 0.
int width;
int height;
int depth;
int comps;
// If true, the LUT will always be regenerated, even if the dimensions have
// not changed.
bool update;
// Alternate way of triggering shader invalidations. If the signature
// does not match the LUT's signature, it will be regenerated.
uint64_t signature;
// If set to true, shader objects will be preserved and updated in-place
// rather than being treated as read-only.
bool dynamic;
// If set , generated shader objects are automatically cached in this
// cache. Requires `signature` to be set (and uniquely identify the LUT).
pl_cache cache;
// Will be called with a zero-initialized buffer whenever the data needs to
// be computed, which happens whenever the size is changed, the shader
// object is invalidated, or `update` is set to true.
//
// Note: Interpretation of `data` is according to `type` and `fmt`.
void (*fill)(void *data, const struct sh_lut_params *params);
void *priv;
// Debug tag to track LUT source
pl_debug_tag debug_tag;
};
#define sh_lut_params(...) (&(struct sh_lut_params) { \
.debug_tag = PL_DEBUG_TAG, \
__VA_ARGS__ \
})
// Makes a table of values available as a shader variable, using an a given
// method (falling back if needed). The resulting identifier can be sampled
// directly as %s(pos), where pos is a vector with the right number of
// dimensions. `pos` must be an integer vector within the bounds of the array,
// unless the method is `SH_LUT_LINEAR`, in which case it's a float vector that
// gets interpolated and clamped as needed. Returns NULL on error.
ident_t sh_lut(pl_shader sh, const struct sh_lut_params *params);
static inline uint8_t sh_num_comps(uint8_t mask)
{
pl_assert((mask & 0xF) == mask);
return __builtin_popcount(mask);
}
static inline const char *sh_float_type(uint8_t mask)
{
switch (sh_num_comps(mask)) {
case 1: return "float";
case 2: return "vec2";
case 3: return "vec3";
case 4: return "vec4";
}
pl_unreachable();
}
static inline const char *sh_swizzle(uint8_t mask)
{
static const char * const swizzles[0x10] = {
NULL, "r", "g", "rg", "b", "rb", "gb", "rgb",
"a", "ra", "ga", "rga", "ba", "rba", "gba", "rgba",
};
pl_assert(mask <= PL_ARRAY_SIZE(swizzles));
return swizzles[mask];
}