/*
* 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 .
*/
#include
#include
#include "common.h"
#include "log.h"
#include "shaders.h"
pl_shader_info pl_shader_info_ref(pl_shader_info pinfo)
{
struct sh_info *info = (struct sh_info *) pinfo;
if (!info)
return NULL;
pl_rc_ref(&info->rc);
return &info->info;
}
void pl_shader_info_deref(pl_shader_info *pinfo)
{
struct sh_info *info = (struct sh_info *) *pinfo;
if (!info)
return;
if (pl_rc_deref(&info->rc))
pl_free(info);
*pinfo = NULL;
}
static struct sh_info *sh_info_alloc(void *alloc)
{
struct sh_info *info = pl_zalloc_ptr(alloc, info);
info->tmp = pl_tmp(info);
pl_rc_init(&info->rc);
return info;
}
// Re-use `sh_info` allocation if possible, allocate new otherwise
static struct sh_info *sh_info_recycle(struct sh_info *info)
{
if (!pl_rc_deref(&info->rc))
return sh_info_alloc(NULL);
memset(&info->info, 0, sizeof(info->info)); // reset public fields
pl_free_children(info->tmp);
pl_rc_ref(&info->rc);
info->desc.len = 0;
info->steps.num = 0;
return info;
}
static uint8_t reverse_bits(uint8_t x)
{
static const uint8_t reverse_nibble[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf,
};
return reverse_nibble[x & 0xF] << 4 | reverse_nibble[x >> 4];
}
static void init_shader(pl_shader sh, const struct pl_shader_params *params)
{
if (params) {
sh->info->info.params = *params;
// To avoid collisions for shaders with very high number of
// identifiers, pack the shader ID into the highest bits (MSB -> LSB)
pl_static_assert(sizeof(sh->prefix) > sizeof(params->id));
const int shift = 8 * (sizeof(sh->prefix) - sizeof(params->id));
sh->prefix = reverse_bits(params->id) << shift;
}
sh->name = sh_fresh(sh, "main");
}
pl_shader pl_shader_alloc(pl_log log, const struct pl_shader_params *params)
{
static const int glsl_ver_req = 130;
if (params && params->glsl.version && params->glsl.version < 130) {
pl_err(log, "Requested GLSL version %d too low (required: %d)",
params->glsl.version, glsl_ver_req);
return NULL;
}
pl_shader sh = pl_alloc_ptr(NULL, sh);
*sh = (struct pl_shader_t) {
.log = log,
.tmp = pl_tmp(sh),
.info = sh_info_alloc(NULL),
.mutable = true,
};
for (int i = 0; i < PL_ARRAY_SIZE(sh->buffers); i++)
sh->buffers[i] = pl_str_builder_alloc(sh);
init_shader(sh, params);
return sh;
}
static void sh_obj_deref(pl_shader_obj obj);
void sh_deref(pl_shader sh)
{
pl_free_children(sh->tmp);
for (int i = 0; i < sh->obj.num; i++)
sh_obj_deref(sh->obj.elem[i]);
sh->obj.num = 0;
}
void pl_shader_free(pl_shader *psh)
{
pl_shader sh = *psh;
if (!sh)
return;
sh_deref(sh);
pl_shader_info_deref((pl_shader_info *) &sh->info);
pl_free_ptr(psh);
}
void pl_shader_reset(pl_shader sh, const struct pl_shader_params *params)
{
sh_deref(sh);
struct pl_shader_t new = {
.log = sh->log,
.tmp = sh->tmp,
.info = sh_info_recycle(sh->info),
.data.buf = sh->data.buf,
.mutable = true,
// Preserve array allocations
.obj.elem = sh->obj.elem,
.vas.elem = sh->vas.elem,
.vars.elem = sh->vars.elem,
.descs.elem = sh->descs.elem,
.consts.elem = sh->consts.elem,
};
// Preserve buffer allocations
memcpy(new.buffers, sh->buffers, sizeof(new.buffers));
for (int i = 0; i < PL_ARRAY_SIZE(new.buffers); i++)
pl_str_builder_reset(new.buffers[i]);
*sh = new;
init_shader(sh, params);
}
static void *sh_alloc(pl_shader sh, size_t size, size_t align)
{
const size_t offset = PL_ALIGN2(sh->data.len, align);
const size_t req_size = offset + size;
if (req_size <= pl_get_size(sh->data.buf)) {
sh->data.len = offset + size;
return sh->data.buf + offset;
}
// We can't realloc this buffer because various pointers will be left
// dangling, so just reparent it onto `sh->tmp` (so it will be cleaned
// up when the shader is next reset) and allocate a new, larger buffer
// in its place
const size_t new_size = PL_MAX(req_size << 1, 256);
pl_steal(sh->tmp, sh->data.buf);
sh->data.buf = pl_alloc(sh, new_size);
sh->data.len = size;
return sh->data.buf;
}
static void *sh_memdup(pl_shader sh, const void *data, size_t size, size_t align)
{
if (!size)
return NULL;
void *dst = sh_alloc(sh, size, align);
assert(data);
memcpy(dst, data, size);
return dst;
}
bool pl_shader_is_failed(const pl_shader sh)
{
return sh->failed;
}
struct pl_glsl_version sh_glsl(const pl_shader sh)
{
if (SH_PARAMS(sh).glsl.version)
return SH_PARAMS(sh).glsl;
if (SH_GPU(sh))
return SH_GPU(sh)->glsl;
return (struct pl_glsl_version) { .version = 130 };
}
bool sh_try_compute(pl_shader sh, int bw, int bh, bool flex, size_t mem)
{
pl_assert(bw && bh);
int *sh_bw = &sh->group_size[0];
int *sh_bh = &sh->group_size[1];
struct pl_glsl_version glsl = sh_glsl(sh);
if (!glsl.compute) {
PL_TRACE(sh, "Disabling compute shader due to missing `compute` support");
return false;
}
if (sh->shmem + mem > glsl.max_shmem_size) {
PL_TRACE(sh, "Disabling compute shader due to insufficient shmem");
return false;
}
if (sh->type == SH_FRAGMENT) {
PL_TRACE(sh, "Disabling compute shader because shader is already marked "
"as fragment shader");
return false;
}
if (bw > glsl.max_group_size[0] ||
bh > glsl.max_group_size[1] ||
(bw * bh) > glsl.max_group_threads)
{
if (!flex) {
PL_TRACE(sh, "Disabling compute shader due to exceeded group "
"thread count.");
return false;
} else {
// Pick better group sizes
bw = PL_MIN(bw, glsl.max_group_size[0]);
bh = glsl.max_group_threads / bw;
}
}
sh->shmem += mem;
// If the current shader is either not a compute shader, or we have no
// choice but to override the metadata, always do so
if (sh->type != SH_COMPUTE || (sh->flexible_work_groups && !flex)) {
*sh_bw = bw;
*sh_bh = bh;
sh->type = SH_COMPUTE;
sh->flexible_work_groups = flex;
return true;
}
// If both shaders are flexible, pick the larger of the two
if (sh->flexible_work_groups && flex) {
*sh_bw = PL_MAX(*sh_bw, bw);
*sh_bh = PL_MAX(*sh_bh, bh);
pl_assert(*sh_bw * *sh_bh <= glsl.max_group_threads);
return true;
}
// At this point we're looking only at a non-flexible compute shader
pl_assert(sh->type == SH_COMPUTE && !sh->flexible_work_groups);
if (!flex) {
// Ensure parameters match
if (bw != *sh_bw || bh != *sh_bh) {
PL_TRACE(sh, "Disabling compute shader due to incompatible group "
"sizes %dx%d and %dx%d", *sh_bw, *sh_bh, bw, bh);
sh->shmem -= mem;
return false;
}
}
return true;
}
bool pl_shader_is_compute(const pl_shader sh)
{
return sh->type == SH_COMPUTE;
}
bool pl_shader_output_size(const pl_shader sh, int *w, int *h)
{
if (!sh->output_w || !sh->output_h)
return false;
*w = sh->transpose ? sh->output_h : sh->output_w;
*h = sh->transpose ? sh->output_w : sh->output_h;
return true;
}
ident_t sh_fresh(pl_shader sh, const char *name)
{
unsigned short id = ++sh->fresh;
assert(!(sh->prefix & id));
id |= sh->prefix;
assert(name);
return sh_mkident(id, name);
}
static inline ident_t sh_fresh_name(pl_shader sh, const char **pname)
{
ident_t id = sh_fresh(sh, *pname);
*pname = sh_ident_pack(id);
return id;
}
ident_t sh_var(pl_shader sh, struct pl_shader_var sv)
{
ident_t id = sh_fresh_name(sh, &sv.var.name);
struct pl_var_layout layout = pl_var_host_layout(0, &sv.var);
sv.data = sh_memdup(sh, sv.data, layout.size, layout.stride);
PL_ARRAY_APPEND(sh, sh->vars, sv);
return id;
}
ident_t sh_var_int(pl_shader sh, const char *name, int val, bool dynamic)
{
return sh_var(sh, (struct pl_shader_var) {
.var = pl_var_int(name),
.data = &val,
.dynamic = dynamic,
});
}
ident_t sh_var_uint(pl_shader sh, const char *name, unsigned int val, bool dynamic)
{
return sh_var(sh, (struct pl_shader_var) {
.var = pl_var_uint(name),
.data = &val,
.dynamic = dynamic,
});
}
ident_t sh_var_float(pl_shader sh, const char *name, float val, bool dynamic)
{
return sh_var(sh, (struct pl_shader_var) {
.var = pl_var_float(name),
.data = &val,
.dynamic = dynamic,
});
}
ident_t sh_var_mat3(pl_shader sh, const char *name, pl_matrix3x3 val)
{
return sh_var(sh, (struct pl_shader_var) {
.var = pl_var_mat3(name),
.data = PL_TRANSPOSE_3X3(val.m),
});
}
ident_t sh_desc(pl_shader sh, struct pl_shader_desc sd)
{
switch (sd.desc.type) {
case PL_DESC_BUF_UNIFORM:
case PL_DESC_BUF_STORAGE:
for (int i = 0; i < sh->descs.num; i++) // ensure uniqueness
pl_assert(sh->descs.elem[i].binding.object != sd.binding.object);
size_t bsize = sizeof(sd.buffer_vars[0]) * sd.num_buffer_vars;
sd.buffer_vars = sh_memdup(sh, sd.buffer_vars, bsize,
alignof(struct pl_buffer_var));
for (int i = 0; i < sd.num_buffer_vars; i++) {
struct pl_var *bv = &sd.buffer_vars[i].var;
const char *name = bv->name;
GLSLP("#define %s "$"\n", name, sh_fresh_name(sh, &bv->name));
}
break;
case PL_DESC_BUF_TEXEL_UNIFORM:
case PL_DESC_BUF_TEXEL_STORAGE:
case PL_DESC_SAMPLED_TEX:
case PL_DESC_STORAGE_IMG:
pl_assert(!sd.num_buffer_vars);
break;
case PL_DESC_INVALID:
case PL_DESC_TYPE_COUNT:
pl_unreachable();
}
ident_t id = sh_fresh_name(sh, &sd.desc.name);
PL_ARRAY_APPEND(sh, sh->descs, sd);
return id;
}
ident_t sh_const(pl_shader sh, struct pl_shader_const sc)
{
if (SH_PARAMS(sh).dynamic_constants && !sc.compile_time) {
return sh_var(sh, (struct pl_shader_var) {
.var = {
.name = sc.name,
.type = sc.type,
.dim_v = 1,
.dim_m = 1,
.dim_a = 1,
},
.data = sc.data,
});
}
ident_t id = sh_fresh_name(sh, &sc.name);
pl_gpu gpu = SH_GPU(sh);
if (gpu && gpu->limits.max_constants) {
if (!sc.compile_time || gpu->limits.array_size_constants) {
size_t size = pl_var_type_size(sc.type);
sc.data = sh_memdup(sh, sc.data, size, size);
PL_ARRAY_APPEND(sh, sh->consts, sc);
return id;
}
}
// Fallback for GPUs without specialization constants
switch (sc.type) {
case PL_VAR_SINT:
GLSLH("const int "$" = %d; \n", id, *(int *) sc.data);
return id;
case PL_VAR_UINT:
GLSLH("const uint "$" = uint(%u); \n", id, *(unsigned int *) sc.data);
return id;
case PL_VAR_FLOAT:
GLSLH("const float "$" = float(%f); \n", id, *(float *) sc.data);
return id;
case PL_VAR_INVALID:
case PL_VAR_TYPE_COUNT:
break;
}
pl_unreachable();
}
ident_t sh_const_int(pl_shader sh, const char *name, int val)
{
return sh_const(sh, (struct pl_shader_const) {
.type = PL_VAR_SINT,
.name = name,
.data = &val,
});
}
ident_t sh_const_uint(pl_shader sh, const char *name, unsigned int val)
{
return sh_const(sh, (struct pl_shader_const) {
.type = PL_VAR_UINT,
.name = name,
.data = &val,
});
}
ident_t sh_const_float(pl_shader sh, const char *name, float val)
{
return sh_const(sh, (struct pl_shader_const) {
.type = PL_VAR_FLOAT,
.name = name,
.data = &val,
});
}
ident_t sh_attr(pl_shader sh, struct pl_shader_va sva)
{
const size_t vsize = sva.attr.fmt->texel_size;
uint8_t *data = sh_alloc(sh, vsize * 4, vsize);
for (int i = 0; i < 4; i++) {
memcpy(data, sva.data[i], vsize);
sva.data[i] = data;
data += vsize;
}
ident_t id = sh_fresh_name(sh, &sva.attr.name);
PL_ARRAY_APPEND(sh, sh->vas, sva);
return id;
}
ident_t sh_attr_vec2(pl_shader sh, const char *name, const pl_rect2df *rc)
{
pl_gpu gpu = SH_GPU(sh);
if (!gpu) {
SH_FAIL(sh, "Failed adding vertex attr '%s': No GPU available!", name);
return NULL_IDENT;
}
pl_fmt fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2);
if (!fmt) {
SH_FAIL(sh, "Failed adding vertex attr '%s': no vertex fmt!", name);
return NULL_IDENT;
}
float verts[4][2] = {
{ rc->x0, rc->y0 },
{ rc->x1, rc->y0 },
{ rc->x0, rc->y1 },
{ rc->x1, rc->y1 },
};
return sh_attr(sh, (struct pl_shader_va) {
.attr = {
.name = name,
.fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
},
.data = { verts[0], verts[1], verts[2], verts[3] },
});
}
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)
{
if (pl_tex_params_dimension(tex->params) != 2) {
SH_FAIL(sh, "Failed binding texture '%s': not a 2D texture!", name);
return NULL_IDENT;
}
if (!tex->params.sampleable) {
SH_FAIL(sh, "Failed binding texture '%s': texture not sampleable!", name);
return NULL_IDENT;
}
ident_t itex = sh_desc(sh, (struct pl_shader_desc) {
.desc = {
.name = name,
.type = PL_DESC_SAMPLED_TEX,
},
.binding = {
.object = tex,
.address_mode = address_mode,
.sample_mode = sample_mode,
},
});
float sx, sy;
if (tex->sampler_type == PL_SAMPLER_RECT) {
sx = 1.0;
sy = 1.0;
} else {
sx = 1.0 / tex->params.w;
sy = 1.0 / tex->params.h;
}
if (out_pos) {
pl_rect2df full = {
.x1 = tex->params.w,
.y1 = tex->params.h,
};
rect = PL_DEF(rect, &full);
*out_pos = sh_attr_vec2(sh, "tex_coord", &(pl_rect2df) {
.x0 = sx * rect->x0, .y0 = sy * rect->y0,
.x1 = sx * rect->x1, .y1 = sy * rect->y1,
});
}
if (out_pt) {
*out_pt = sh_var(sh, (struct pl_shader_var) {
.var = pl_var_vec2("tex_pt"),
.data = &(float[2]) {sx, sy},
});
}
return itex;
}
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)
{
struct pl_buffer_var bv = { .var = new_var };
size_t cur_size = sh_buf_desc_size(buf_desc);
switch (buf_desc->desc.type) {
case PL_DESC_BUF_UNIFORM:
bv.layout = pl_std140_layout(cur_size, &new_var);
if (bv.layout.offset + bv.layout.size > gpu->limits.max_ubo_size)
return false;
break;
case PL_DESC_BUF_STORAGE:
bv.layout = pl_std430_layout(cur_size, &new_var);
if (bv.layout.offset + bv.layout.size > gpu->limits.max_ssbo_size)
return false;
break;
case PL_DESC_INVALID:
case PL_DESC_SAMPLED_TEX:
case PL_DESC_STORAGE_IMG:
case PL_DESC_BUF_TEXEL_UNIFORM:
case PL_DESC_BUF_TEXEL_STORAGE:
case PL_DESC_TYPE_COUNT:
pl_unreachable();
}
if (out_layout)
*out_layout = bv.layout;
PL_ARRAY_APPEND_RAW(alloc, buf_desc->buffer_vars, buf_desc->num_buffer_vars, bv);
return true;
}
size_t sh_buf_desc_size(const struct pl_shader_desc *buf_desc)
{
if (!buf_desc->num_buffer_vars)
return 0;
const struct pl_buffer_var *last;
last = &buf_desc->buffer_vars[buf_desc->num_buffer_vars - 1];
return last->layout.offset + last->layout.size;
}
void sh_describef(pl_shader sh, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
sh_describe(sh, pl_vasprintf(sh->info->tmp, fmt, ap));
va_end(ap);
}
static const char *insigs[] = {
[PL_SHADER_SIG_NONE] = "",
[PL_SHADER_SIG_COLOR] = "vec4 color",
};
static const char *outsigs[] = {
[PL_SHADER_SIG_NONE] = "void",
[PL_SHADER_SIG_COLOR] = "vec4",
};
static const char *retvals[] = {
[PL_SHADER_SIG_NONE] = "",
[PL_SHADER_SIG_COLOR] = "return color;",
};
// libplacebo currently only allows 2D samplers for shader signatures
static const char *samplers2D[] = {
[PL_SAMPLER_NORMAL] = "sampler2D",
[PL_SAMPLER_RECT] = "sampler2DRect",
[PL_SAMPLER_EXTERNAL] = "samplerExternalOES",
};
ident_t sh_subpass(pl_shader sh, pl_shader sub)
{
pl_assert(sh->mutable);
if (sh->prefix == sub->prefix) {
PL_TRACE(sh, "Can't merge shaders: conflicting identifiers!");
return NULL_IDENT;
}
// Check for shader compatibility
int res_w = PL_DEF(sh->output_w, sub->output_w),
res_h = PL_DEF(sh->output_h, sub->output_h);
if ((sub->output_w && res_w != sub->output_w) ||
(sub->output_h && res_h != sub->output_h))
{
PL_TRACE(sh, "Can't merge shaders: incompatible sizes: %dx%d and %dx%d",
sh->output_w, sh->output_h, sub->output_w, sub->output_h);
return NULL_IDENT;
}
if (sub->type == SH_COMPUTE) {
int subw = sub->group_size[0],
subh = sub->group_size[1];
bool flex = sub->flexible_work_groups;
if (!sh_try_compute(sh, subw, subh, flex, sub->shmem)) {
PL_TRACE(sh, "Can't merge shaders: incompatible block sizes or "
"exceeded shared memory resource capabilities");
return NULL_IDENT;
}
}
sh->output_w = res_w;
sh->output_h = res_h;
// Append the prelude and header
pl_str_builder_concat(sh->buffers[SH_BUF_PRELUDE], sub->buffers[SH_BUF_PRELUDE]);
pl_str_builder_concat(sh->buffers[SH_BUF_HEADER], sub->buffers[SH_BUF_HEADER]);
// Append the body as a new header function
if (sub->input == PL_SHADER_SIG_SAMPLER) {
pl_assert(sub->sampler_prefix);
GLSLH("%s "$"(%c%s src_tex, vec2 tex_coord) {\n",
outsigs[sub->output], sub->name,
sub->sampler_prefix, samplers2D[sub->sampler_type]);
} else {
GLSLH("%s "$"(%s) {\n",
outsigs[sub->output], sub->name, insigs[sub->input]);
}
pl_str_builder_concat(sh->buffers[SH_BUF_HEADER], sub->buffers[SH_BUF_BODY]);
GLSLH("%s\n}\n\n", retvals[sub->output]);
// Steal all inputs and objects from the subpass
#define ARRAY_STEAL(arr) do \
{ \
PL_ARRAY_CONCAT(sh, sh->arr, sub->arr); \
sub->arr.num = 0; \
} while (0)
ARRAY_STEAL(obj);
ARRAY_STEAL(vas);
ARRAY_STEAL(vars);
ARRAY_STEAL(descs);
ARRAY_STEAL(consts);
#undef ARRAY_STEAL
// Steal the scratch buffer (if it holds data)
if (sub->data.len) {
pl_steal(sh->tmp, sub->data.buf);
sub->data = (pl_str) {0};
}
// Steal all temporary allocations and mark the child as unusable
pl_steal(sh->tmp, sub->tmp);
sub->tmp = pl_tmp(sub);
sub->failed = true;
// Steal the shader steps array (and allocations)
pl_assert(pl_rc_count(&sub->info->rc) == 1);
PL_ARRAY_CONCAT(sh->info, sh->info->steps, sub->info->steps);
pl_steal(sh->info->tmp, sub->info->tmp);
sub->info->tmp = pl_tmp(sub->info);
sub->info->steps.num = 0; // sanity
return sub->name;
}
pl_str_builder sh_finalize_internal(pl_shader sh)
{
pl_assert(sh->mutable); // this function should only ever be called once
if (sh->failed)
return NULL;
// Padding for readability
GLSLP("\n");
// Concatenate everything onto the prelude to form the final output
pl_str_builder_concat(sh->buffers[SH_BUF_PRELUDE], sh->buffers[SH_BUF_HEADER]);
if (sh->input == PL_SHADER_SIG_SAMPLER) {
pl_assert(sh->sampler_prefix);
GLSLP("%s "$"(%c%s src_tex, vec2 tex_coord) {\n",
outsigs[sh->output], sh->name,
sh->sampler_prefix,
samplers2D[sh->sampler_type]);
} else {
GLSLP("%s "$"(%s) {\n", outsigs[sh->output], sh->name, insigs[sh->input]);
}
pl_str_builder_concat(sh->buffers[SH_BUF_PRELUDE], sh->buffers[SH_BUF_BODY]);
pl_str_builder_concat(sh->buffers[SH_BUF_PRELUDE], sh->buffers[SH_BUF_FOOTER]);
GLSLP("%s\n}\n\n", retvals[sh->output]);
// Generate the shader info
struct sh_info *info = sh->info;
info->info.steps = info->steps.elem;
info->info.num_steps = info->steps.num;
info->info.description = "(unknown shader)";
// Generate pretty description
for (int i = 0; i < info->steps.num; i++) {
const char *step = info->steps.elem[i];
// Prevent duplicates. We're okay using a weak equality check here
// because most pass descriptions are static strings.
for (int j = 0; j < i; j++) {
if (info->steps.elem[j] == step)
goto next_step;
}
int count = 1;
for (int j = i+1; j < info->steps.num; j++) {
if (info->steps.elem[j] == step)
count++;
}
const char *prefix = i > 0 ? ", " : "";
if (count > 1) {
pl_str_append_asprintf(info, &info->desc, "%s%s x%d",
prefix, step, count);
} else {
pl_str_append_asprintf(info, &info->desc, "%s%s", prefix, step);
}
next_step: ;
}
if (info->desc.len)
info->info.description = (char *) info->desc.buf;
sh->mutable = false;
return sh->buffers[SH_BUF_PRELUDE];
}
const struct pl_shader_res *pl_shader_finalize(pl_shader sh)
{
if (sh->failed) {
return NULL;
} else if (!sh->mutable) {
return &sh->result;
}
pl_shader_info info = &sh->info->info;
pl_str_builder glsl = sh_finalize_internal(sh);
// Turn ident_t into friendly strings before passing it to users
#define FIX_IDENT(name) \
name = sh_ident_tostr(sh_ident_unpack(name))
for (int i = 0; i < sh->vas.num; i++)
FIX_IDENT(sh->vas.elem[i].attr.name);
for (int i = 0; i < sh->vars.num; i++)
FIX_IDENT(sh->vars.elem[i].var.name);
for (int i = 0; i < sh->consts.num; i++)
FIX_IDENT(sh->consts.elem[i].name);
for (int i = 0; i < sh->descs.num; i++) {
struct pl_shader_desc *sd = &sh->descs.elem[i];
FIX_IDENT(sd->desc.name);
for (int j = 0; j < sd->num_buffer_vars; sd++)
FIX_IDENT(sd->buffer_vars[j].var.name);
}
#undef FIX_IDENT
sh->result = (struct pl_shader_res) {
.info = info,
.glsl = (char *) pl_str_builder_exec(glsl).buf,
.name = sh_ident_tostr(sh->name),
.input = sh->input,
.output = sh->output,
.compute_group_size = { sh->group_size[0], sh->group_size[1] },
.compute_shmem = sh->shmem,
.vertex_attribs = sh->vas.elem,
.num_vertex_attribs = sh->vas.num,
.variables = sh->vars.elem,
.num_variables = sh->vars.num,
.descriptors = sh->descs.elem,
.num_descriptors = sh->descs.num,
.constants = sh->consts.elem,
.num_constants = sh->consts.num,
// deprecated fields
.params = info->params,
.steps = info->steps,
.num_steps = info->num_steps,
.description = info->description,
};
return &sh->result;
}
bool sh_require(pl_shader sh, enum pl_shader_sig insig, int w, int h)
{
if (sh->failed) {
SH_FAIL(sh, "Attempting to modify a failed shader!");
return false;
}
if (!sh->mutable) {
SH_FAIL(sh, "Attempted to modify an immutable shader!");
return false;
}
if ((w && sh->output_w && sh->output_w != w) ||
(h && sh->output_h && sh->output_h != h))
{
SH_FAIL(sh, "Illegal sequence of shader operations: Incompatible "
"output size requirements %dx%d and %dx%d",
sh->output_w, sh->output_h, w, h);
return false;
}
static const char *names[] = {
[PL_SHADER_SIG_NONE] = "PL_SHADER_SIG_NONE",
[PL_SHADER_SIG_COLOR] = "PL_SHADER_SIG_COLOR",
};
// If we require an input, but there is none available - just get it from
// the user by turning it into an explicit input signature.
if (!sh->output && insig) {
pl_assert(!sh->input);
sh->input = insig;
} else if (sh->output != insig) {
SH_FAIL(sh, "Illegal sequence of shader operations! Current output "
"signature is '%s', but called operation expects '%s'!",
names[sh->output], names[insig]);
return false;
}
// All of our shaders end up returning a vec4 color
sh->output = PL_SHADER_SIG_COLOR;
sh->output_w = PL_DEF(sh->output_w, w);
sh->output_h = PL_DEF(sh->output_h, h);
return true;
}
static void sh_obj_deref(pl_shader_obj obj)
{
if (!pl_rc_deref(&obj->rc))
return;
if (obj->uninit)
obj->uninit(obj->gpu, obj->priv);
pl_free(obj);
}
void pl_shader_obj_destroy(pl_shader_obj *ptr)
{
pl_shader_obj obj = *ptr;
if (!obj)
return;
sh_obj_deref(obj);
*ptr = NULL;
}
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))
{
if (!ptr)
return NULL;
pl_shader_obj obj = *ptr;
if (obj && obj->gpu != SH_GPU(sh)) {
SH_FAIL(sh, "Passed pl_shader_obj belongs to different GPU!");
return NULL;
}
if (obj && obj->type != type) {
SH_FAIL(sh, "Passed pl_shader_obj of wrong type! Shader objects must "
"always be used with the same type of shader.");
return NULL;
}
if (!obj) {
obj = pl_zalloc_ptr(NULL, obj);
pl_rc_init(&obj->rc);
obj->gpu = SH_GPU(sh);
obj->type = type;
obj->priv = pl_zalloc(obj, priv_size);
obj->uninit = uninit;
}
PL_ARRAY_APPEND(sh, sh->obj, obj);
pl_rc_ref(&obj->rc);
*ptr = obj;
return obj->priv;
}
ident_t sh_prng(pl_shader sh, bool temporal, ident_t *p_state)
{
ident_t randfun = sh_fresh(sh, "rand"),
state = sh_fresh(sh, "state");
// Based on pcg3d (http://jcgt.org/published/0009/03/02/)
GLSLP("#define prng_t uvec3\n");
GLSLH("vec3 "$"(inout uvec3 s) { \n"
" s = 1664525u * s + uvec3(1013904223u); \n"
" s.x += s.y * s.z; \n"
" s.y += s.z * s.x; \n"
" s.z += s.x * s.y; \n"
" s ^= s >> 16u; \n"
" s.x += s.y * s.z; \n"
" s.y += s.z * s.x; \n"
" s.z += s.x * s.y; \n"
" return vec3(s) * 1.0/float(0xFFFFFFFFu); \n"
"} \n",
randfun);
if (temporal) {
GLSL("uvec3 "$" = uvec3(gl_FragCoord.xy, "$"); \n",
state, SH_UINT_DYN(SH_PARAMS(sh).index));
} else {
GLSL("uvec3 "$" = uvec3(gl_FragCoord.xy, 0.0); \n", state);
}
if (p_state)
*p_state = state;
ident_t res = sh_fresh(sh, "RAND");
GLSLH("#define "$" ("$"("$"))\n", res, randfun, state);
return res;
}