quakeforge/tools/qfcc/source/glsl-layout.c
Bill Currie 594ba86aae [qfcc] Start emitting spir-v expression instructions
They're currently wrong since they're meant to be for specialization
constants (and that whole system is currently broken anyway) but are
instead raw code expressions, but progress is progress.
2024-09-23 18:56:18 +09:00

885 lines
20 KiB
C

/*
glsl-layout.c
GLSL layout attribute handling
Copyright (C) 2024 Bill Currie <bill@taniwha.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <strings.h>
#include "QF/alloc.h"
#include "QF/hash.h"
#include "QF/heapsort.h"
#include "QF/va.h"
#include "tools/qfcc/include/attribute.h"
#include "tools/qfcc/include/def.h"
#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/glsl-lang.h"
#include "tools/qfcc/include/shared.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/symtab.h"
#include "tools/qfcc/include/type.h"
#include "tools/qfcc/include/value.h"
static void
glsl_layout_location_invalid (specifier_t spec, const expr_t *qual_name,
const expr_t *val)
{
error (qual_name, "not allowed for vulkan");
}
static void
set_attribute (attribute_t **attributes, const char *name, const expr_t *val)
{
for (auto a = *attributes; a; a = a->next) {
if (strcmp (a->name, name) == 0) {
a->params = val;
return;
}
}
auto attr = new_attribute (name, val);
attr->next = *attributes;
*attributes = attr;
}
static void
glsl_layout_location (specifier_t spec, const expr_t *qual_name,
const expr_t *val)
{
const char *name = expr_string (qual_name);
set_attribute (&spec.sym->attributes, name, val);
}
static void
glsl_layout_constant_id (specifier_t spec, const expr_t *qual_name,
const expr_t *val)
{
if (spec.sym->sy_type == sy_const) {
auto expr = new_value_expr (spec.sym->value, false);
spec.sym->sy_type = sy_expr;
spec.sym->expr = expr;
}
if (spec.sym->sy_type == sy_expr && spec.sym->expr->type == ex_value) {
auto expr = new_unary_expr ('+', spec.sym->expr);
expr->expr.constant = true;
expr->expr.type = spec.sym->type;
spec.sym->expr = expr;
}
const char *name = expr_string (qual_name);
set_attribute (&spec.sym->attributes, name, val);
}
static void
glsl_layout_binding (specifier_t spec, const expr_t *qual_name,
const expr_t *val)
{
notice (qual_name, "%s %s", expr_string (qual_name),
get_value_string (val->value));
}
static void
glsl_layout_set_property (specifier_t spec, const expr_t *qual_name,
const expr_t *val)
{
auto interface = glsl_iftype_from_sc (spec.storage);
notice (qual_name, "%s %s %s", glsl_interface_names[interface],
expr_string (qual_name), get_value_string (val->value));
}
typedef enum {
decl_var = 1 << 0,
decl_qual = 1 << 1,
decl_block = 1 << 2,
decl_member = 1 << 3,
} glsl_obj_t;
typedef enum {
var_any,
var_opaque,
var_atomic,
var_subpass,
var_scalar,
var_image,
var_gl_FragCoord,
var_gl_FragDepth,
} glsl_var_t;
typedef struct layout_qual_s {
const char *name;
void (*apply) (specifier_t spec, const expr_t *qual_name);
void (*apply_expr) (specifier_t spec, const expr_t *qual_name,
const expr_t *val);
unsigned obj_mask;
glsl_var_t var_type;
unsigned if_mask;
const char **stage_filter;
} layout_qual_t;
#define A(a) a, nullptr
#define E(e) nullptr, e
#define D(d) (decl_##d)
#define D_all D(qual)|D(var)|D(block)|D(member)
#define V(v) (var_##v)
#define I(i) (1 << (glsl_##i))
#define C (const char *[])
static bool sorted_layout_qualifiers;
static layout_qual_t layout_qualifiers[] = {
{ .name = "shared",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "packed",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "std140",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "std430",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "row_major",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block)|D(member),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "column_major",
.apply = A(nullptr),
.obj_mask = D(qual)|D(block)|D(member),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "binding",
.apply = E(glsl_layout_binding),
.obj_mask = D(var)|D(block),
.var_type = V(opaque),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "offset",
.apply = E(nullptr),
.obj_mask = D(var)|D(member),
.var_type = V(opaque),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "align",
.apply = E(nullptr),
.obj_mask = D(var)|D(block),
.var_type = V(opaque),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "set",
.apply = E(nullptr),
.obj_mask = D(var)|D(block),
.var_type = V(opaque),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "push_constant",
.apply = A(nullptr),
.obj_mask = D(block),
.var_type = V(any),
.if_mask = I(uniform),
},
{ .name = "input_attachment_index",
.apply = E(nullptr),
.obj_mask = D(var),
.var_type = V(subpass),
.if_mask = I(uniform),
},
{ .name = "location",
.apply = E(glsl_layout_location_invalid),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(uniform)|I(buffer),
},
{ .name = "location",
.apply = E(glsl_layout_location),
.obj_mask = D(var)|D(block)|D(member),
.var_type = V(any),
.if_mask = I(in)|I(out),
.stage_filter = C { "!compute", nullptr },
},
{ .name = "component",
.apply = E(nullptr),
.obj_mask = D(var)|D(member),
.var_type = V(any),
.if_mask = I(in)|I(out),
.stage_filter = C { "!compute", nullptr },
},
{ .name = "index",
.apply = E(nullptr),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "triangles",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "quads",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "isolines",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "equal_spacing",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "fractional_even_spacing",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "fractional_odd_spacing",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "cw",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "ccw",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "point_mode",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "tessellation evaluation", nullptr },
},
{ .name = "points",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in)|I(out),
.stage_filter = C { "geometry", nullptr },
},
//? [ points ] // q _ _ _ geom in
{ .name = "lines",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "lines_adjacency",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "triangles",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "triangles_adjacency",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "invocations",
.apply = E(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "origin_upper_left",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "pixel_center_integer",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "early_fragment_tests",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "local_size_x",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "local_size_y",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "local_size_z",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "local_size_x_id",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "local_size_y_id",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "local_size_z_id",
.apply = E(glsl_layout_set_property),
.obj_mask = D(qual),
.if_mask = I(in),
.stage_filter = C { "compute", nullptr },
},
{ .name = "xfb_buffer",
.apply = E(nullptr),
.obj_mask = D_all,
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C {
"vertex",
"tessellation control",
"tessellation evaluation",
"geometry",
nullptr
},
},
{ .name = "xfb_stride",
.apply = E(nullptr),
.obj_mask = D_all,
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C {
"vertex",
"tessellation control",
"tessellation evaluation",
"geometry",
nullptr
},
},
{ .name = "xfb_offset",
.apply = E(nullptr),
.obj_mask = D_all,
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C {
"vertex",
"tessellation control",
"tessellation evaluation",
"geometry",
nullptr
},
},
{ .name = "vertices",
.apply = E(nullptr),
.obj_mask = D(qual),
.if_mask = I(out),
.stage_filter = C { "tessellation control", nullptr },
},
//? [ points ] // q _ _ _ geom out
{ .name = "line_strip",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(out),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "triangle_strip",
.apply = A(nullptr),
.obj_mask = D(qual),
.if_mask = I(out),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "max_vertices",
.apply = E(nullptr),
.obj_mask = D(qual),
.if_mask = I(out),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "stream",
.apply = E(nullptr),
.obj_mask = D_all,
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "geometry", nullptr },
},
{ .name = "depth_any",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "depth_greater",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "depth_less",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "depth_unchanged",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(any),
.if_mask = I(out),
.stage_filter = C { "fragment", nullptr },
},
{ .name = "constant_id",
.apply = E(glsl_layout_constant_id),
.obj_mask = D(var),
.var_type = V(scalar),
},
{ .name = "rgba32f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba16f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg32f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg16f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r11f_g11f_b10f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r32f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r16f",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba16",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgb10_a2",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba8",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg16",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg8",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r16",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r8",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba16_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba8_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg16_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg8_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r16_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r8_snorm",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba32i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba16i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba8i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg32i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg16i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg8i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r32i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r16i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r8i",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba32ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba16ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgb10_a2ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rgba8ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg32ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg16ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "rg8ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r32ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r16ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
{ .name = "r8ui",
.apply = A(nullptr),
.obj_mask = D(var),
.var_type = V(image),
.if_mask = I(uniform),
},
};
#undef A
#undef E
#undef D
#undef D_all
#undef V
#undef I
#undef C
#define num_quals (sizeof (layout_qualifiers) / sizeof (layout_qualifiers[0]))
#define layout_qual_sz num_quals, sizeof (layout_qual_t)
static int
layout_qual_cmp (const void *_a, const void *_b)
{
auto a = (const layout_qual_t *) _a;
auto b = (const layout_qual_t *) _b;
return strcasecmp (a->name, b->name);
}
static bool __attribute__((pure))
layout_check_qualifier (const layout_qual_t *qual, specifier_t spec)
{
unsigned obj_mask = 0;
glsl_var_t var_type = var_any;
unsigned if_mask = 0;
auto interface = glsl_iftype_from_sc (spec.storage);
if (interface < glsl_num_interfaces) {
if_mask = 1 << interface;
}
if (spec.sym) {
auto sym = spec.sym;
if (sym->type) {
if (is_handle (sym->type)) {
var_type = var_opaque;
} else if (spec.is_const && is_scalar (sym->type)) {
var_type = var_scalar;
} else if (strcmp (sym->name, "gl_FragCoord") == 0) {
var_type = var_gl_FragCoord;
} else if (strcmp (sym->name, "gl_FragDepth") == 0) {
var_type = var_gl_FragDepth;
}
obj_mask = decl_var;
if (sym->table->parent && sym->table->parent->type == stab_struct) {
obj_mask = decl_member;
}
} else {
obj_mask = decl_block;
}
} else {
obj_mask = decl_qual;
}
if (!(qual->obj_mask & obj_mask)) {
return false;
}
if (obj_mask == decl_var
&& qual->var_type != var_any
&& qual->var_type != var_type) {
return false;
}
if (qual->if_mask != if_mask && !(qual->if_mask & if_mask)) {
return false;
}
if (qual->stage_filter) {
bool ok = false;
for (auto sf = qual->stage_filter; *sf; sf++) {
if (**sf == '!') {
if (strcmp (*sf + 1, glsl_sublang.name) == 0) {
return false;
}
ok = true;
} else {
if (strcmp (*sf, glsl_sublang.name) == 0) {
return true;
}
}
}
return ok;
}
return true;
}
static void
layout_apply_qualifier (const expr_t *qualifier, specifier_t spec)
{
layout_qual_t key = {
.name = qualifier->type == ex_expr
? expr_string (qualifier->expr.e1)
: expr_string (qualifier)
};
auto val = qualifier->type == ex_expr
? qualifier->expr.e2
: nullptr;
const layout_qual_t *qual;
qual = bsearch (&key, layout_qualifiers, layout_qual_sz, layout_qual_cmp);
if (!qual) {
error (0, "invalid layout qualifier: %s", key.name);
return;
}
// there may be multiple entries with the same qualifier name, so find
// the first one
while (qual > layout_qualifiers && layout_qual_cmp (&key, qual - 1) == 0) {
qual--;
}
// check all matching entries
while (qual - layout_qualifiers < (ptrdiff_t) num_quals
&& layout_qual_cmp (&key, qual) == 0) {
if (layout_check_qualifier (qual, spec)) {
if (qual->apply_expr) {
if (!val) {
error (qualifier, "%s requires a value", key.name);
} else {
qual->apply_expr (spec, qualifier->expr.e1, val);
}
return;
} else if (qual->apply) {
if (!val) {
error (qualifier, "%s does not take a value", key.name);
} else {
qual->apply (spec, qualifier->expr.e1);
}
return;
} else {
error (0, "unsupported layout qualifier: %s", key.name);
return;
}
}
qual++;
}
error (0, "invalid layout qualifier: %s", key.name);
return;
}
void
glsl_layout (const ex_list_t *qualifiers, specifier_t spec)
{
if (!sorted_layout_qualifiers) {
heapsort (layout_qualifiers, layout_qual_sz, layout_qual_cmp);
}
for (auto q = qualifiers->head; q; q = q->next) {
layout_apply_qualifier (q->expr, spec);
}
}