Files
aego/render/shaderx/gen.go
T
sanspie a6822156cd
ci-platforms / windows amd64 smoke (push) Skipped
ci / release (push) Successful in 8m58s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 4m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 13m28s
Finish Stage 12 editor fixes and refreshed platformer
2026-11-05 17:36:42 +03:00

378 lines
12 KiB
Go

package shaderx
import (
"fmt"
"regexp"
"slices"
"strings"
"aego/render/shader"
)
var genLoc = Loc{File: -1}
// GeneratorVersion invalidates import caches when generated ABI glue changes
// without changing a project's source or any of its includes.
const GeneratorVersion = 8
type builder struct {
sb strings.Builder
locs []Loc
}
func (b *builder) gen(s string) {
for _, l := range strings.Split(s, "\n") {
b.add(l, genLoc)
}
}
func (b *builder) add(s string, l Loc) {
b.sb.WriteString(s)
b.sb.WriteByte('\n')
b.locs = append(b.locs, l)
}
const spriteInputs = `layout(location = 0) in vec4 aego_i_pos_size;
layout(location = 1) in vec4 aego_i_sincos_origin;
layout(location = 2) in vec4 aego_i_uv;
layout(location = 3) in vec4 aego_i_color;
layout(location = 9) in vec4 aego_i_affine;
layout(location = %d) in vec4 aego_i_local_rect;`
const spriteMain = ` vec2 corner = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1));
vec2 local = (corner - aego_i_sincos_origin.zw) * aego_i_pos_size.zw;
float s = aego_i_sincos_origin.x;
float c = aego_i_sincos_origin.y;
mat2 aego_basis = mat2(1.0 + aego_i_affine.x, aego_i_affine.y, aego_i_affine.z, aego_i_affine.w);
aego_world = vec3(aego_i_pos_size.xy + aego_basis * vec2(local.x * c - local.y * s, local.x * s + local.y * c), 0.0);
aego_uv = mix(aego_i_uv.xy, aego_i_uv.zw, corner);
aego_color = aego_i_color;
aego_local_rect = aego_i_local_rect;`
const spriteLocalCoordinates = ` if (aego_local_rect.x == aego_local_rect.z || aego_local_rect.y == aego_local_rect.w) {
// A disabled optional attribute has the generic value (0,0,0,1).
aego_local_rect = vec4(0.0, 0.0, 1.0, 1.0);
if (aego_i_uv.x > aego_i_uv.z) { aego_local_rect.xz = vec2(1.0, 0.0); }
if (aego_i_uv.y > aego_i_uv.w) { aego_local_rect.yw = vec2(1.0, 0.0); }
}
aego_local = mix(aego_local_rect.xy, aego_local_rect.zw, corner);
aego_region = aego_i_uv;`
const meshInputs = `layout(location = 0) in vec3 aego_i_pos;
layout(location = 1) in vec3 aego_i_normal;
layout(location = 2) in vec2 aego_i_uv;
layout(location = 3) in vec4 aego_i_color;
layout(std140) uniform ub3 {
mat4 aego_model;
vec4 aego_tint;
vec4 aego_uv_rect;
};`
const meshMain = ` aego_world = (aego_model * vec4(aego_i_pos, 1.0)).xyz;
aego_uv = mix(aego_uv_rect.xy, aego_uv_rect.zw, aego_i_uv);
aego_color = aego_i_color * aego_tint;
aego_local = aego_i_uv;
aego_local_rect = vec4(0.0, 0.0, 1.0, 1.0);
aego_region = aego_uv_rect;`
// GLSL outputs cannot be initialized; vertex/helpers use initialized private values published to unchanged varyings; macros name engine ABI, not material fields.
const vertexVaryings = `out vec2 aego_uv;
out vec4 aego_color;
out vec2 aego_local;
flat out vec4 aego_region;
flat out vec4 aego_local_rect;
vec3 aego_world = vec3(0.0);
vec2 aego_vertex_uv = vec2(0.0);
vec4 aego_vertex_color = vec4(0.0);
vec2 aego_vertex_local = vec2(0.0);
vec4 aego_vertex_region = vec4(0.0);
vec4 aego_vertex_local_rect = vec4(0.0);
#define aego_uv aego_vertex_uv
#define aego_color aego_vertex_color
#define aego_local aego_vertex_local
#define aego_region aego_vertex_region
#define aego_local_rect aego_vertex_local_rect`
const publishVertexVaryings = `#undef aego_uv
#undef aego_color
#undef aego_local
#undef aego_region
#undef aego_local_rect
aego_uv = aego_vertex_uv;
aego_color = aego_vertex_color;
aego_local = aego_vertex_local;
aego_region = aego_vertex_region;
aego_local_rect = aego_vertex_local_rect;`
const swizzle = "xyzw"
func (u *Unit) Source(frag, es bool, mask uint32) (string, []Loc) {
var b builder
if es {
b.gen(strings.TrimSuffix(Header, "\n"))
} else {
b.gen(SourceVersion)
}
f, v := 0, 1
if frag {
f, v = 1, 0
}
b.gen(fmt.Sprintf("#define AEGO_ABI %d\n#define AEGO_FRAGMENT %d\n#define AEGO_VERTEX %d", shader.ABI, f, v))
for i, k := range u.Keywords {
b.gen(fmt.Sprintf("#define %s %d", k, mask>>i&1))
}
if u.Domain == shader.DomainPostFX {
b.gen(shader.PostProcessUniforms)
if !frag {
b.gen(shader.PostProcessVertex)
} else {
u.uniforms(&b)
b.gen(strings.Replace(shader.PostProcessFragment, "uniform sampler2D tex0;", "", 1))
b.gen("#define MainTex tex0\n#define aego_uv uv\n#define aego_out color")
for _, l := range u.body {
b.add(l.text, l.loc)
}
b.gen("vec4 aego_effect(vec4 c,vec2 localUV,vec2 size,vec4 p[4]){fragment();return color;}")
b.gen(shader.PostProcessMain)
}
return b.sb.String(), b.locs
}
b.gen(shader.FrameBlock)
b.gen(shader.ViewBlock)
if u.Features&shader.FeatureLighting != 0 {
b.gen("#define AEGO_LIGHTING 1")
if frag {
b.gen(shader.LightingBlock)
}
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen("#define AEGO_SURFACE_CHANNELS 1")
b.gen(shader.SurfaceBlock)
if frag {
b.gen(shader.EmissionBinding)
}
}
}
u.uniforms(&b)
if frag {
u.fragment(&b)
} else {
u.vertex(&b)
}
return b.sb.String(), b.locs
}
func (u *Unit) uniforms(b *builder) {
if u.Size > 0 {
b.gen(fmt.Sprintf("layout(std140) uniform ub%d {", shader.SlotMaterial))
for _, q := range u.Params {
if q.Instance || q.Kind == shader.KindTexture {
continue
}
b.add(fmt.Sprintf(" %s %s;", q.Kind.GLSL(), q.Name), loc(q))
}
b.gen("};")
}
b.gen(fmt.Sprintf("uniform sampler2D tex%d;\n#define aego_main tex%d", shader.TexMain, shader.TexMain))
for _, q := range u.Params {
if q.Kind == shader.KindTexture {
if !q.Normal && !q.Emission {
b.add(fmt.Sprintf("uniform sampler2D tex%d;", q.Slot), loc(q))
}
b.gen(fmt.Sprintf("#define %s tex%d", q.Name, q.Slot))
}
}
}
// Instance parameters are typed GLSL variables resolved from prepared effective lanes before either entry point; same-named locals and structure fields retain GLSL scope.
func (u *Unit) laneDefines(b *builder) {
for _, q := range u.Params {
if q.Instance {
zero := "0.0"
switch q.Kind {
case shader.KindInt:
zero = "0"
case shader.KindBool:
zero = "false"
default:
if q.Kind.Lanes() > 1 {
zero = q.Kind.GLSL() + "(0.0)"
}
}
// Constant initialization makes helper reads definite to drivers whose warnings ignore the main() call graph; assignLanes installs the effective value before either entry.
b.gen(fmt.Sprintf("%s %s = %s;", q.Kind.GLSL(), q.Name, zero))
}
}
}
func (u *Unit) assignLanes(b *builder) {
for _, q := range u.Params {
if !q.Instance {
continue
}
access := fmt.Sprintf("aego_lane%d.%s", q.Lane, swizzle[q.Comp:q.Comp+q.Kind.Lanes()])
switch q.Kind {
case shader.KindInt:
access = "int(" + access + ")"
case shader.KindBool:
access = "(" + access + " != 0u)"
default:
access = "uintBitsToFloat(" + access + ")"
}
b.gen(fmt.Sprintf(" %s = %s;", q.Name, access))
}
}
func (u *Unit) fragment(b *builder) {
if u.Features&shader.FeaturePixelCoverage != 0 {
b.gen("in vec2 aego_coverage;")
}
b.gen("in vec2 aego_uv;\nin vec4 aego_color;\nin vec2 aego_local;\nflat in vec4 aego_region;\nflat in vec4 aego_local_rect;")
if u.Features&shader.FeatureLighting != 0 {
b.gen("in vec3 aego_surface_world;\nflat in vec4 aego_surface_basis;\nflat in vec4 aego_normal_region;\nflat in vec4 aego_normal_local_rect;")
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen("flat in vec4 aego_emission_region;\nflat in vec4 aego_emission_local_rect;")
}
if u.Domain == shader.DomainMesh2D {
b.gen("#define AEGO_MESH_SURFACE 1\nin vec2 aego_surface_local_position;")
}
}
for i := 0; i < u.Lanes; i++ {
b.gen(fmt.Sprintf("flat in uvec4 aego_lane%d;", i))
}
u.laneDefines(b)
b.gen("out vec4 aego_out;")
for _, l := range strip(u.body, reVert) {
b.add(l.text, l.loc)
}
b.gen("void main() {")
if u.Features&shader.FeaturePixelCoverage != 0 {
b.gen(" if (any(lessThan(aego_coverage,vec2(0))) || any(greaterThanEqual(aego_coverage,vec2(1)))) discard;")
}
u.assignLanes(b)
b.gen(" fragment();\n}")
}
func (u *Unit) vertex(b *builder) {
coverage := u.Features&shader.FeaturePixelCoverage != 0
if coverage {
b.gen("out vec2 aego_coverage;")
}
mesh := u.Domain == shader.DomainMesh2D
if mesh {
b.gen(meshInputs)
} else {
b.gen(fmt.Sprintf(spriteInputs, shader.TrimLocation))
}
for i := 0; i < u.Lanes; i++ {
b.gen(fmt.Sprintf("layout(location = %d) in uvec4 aego_i_lane%d;\nflat out uvec4 aego_lane%d;", shader.LaneLocation+i, i, i))
}
u.laneDefines(b)
b.gen(vertexVaryings)
if u.Features&shader.FeatureLighting != 0 {
b.gen("out vec3 aego_surface_world;\nflat out vec4 aego_surface_basis;\nflat out vec4 aego_normal_region;\nflat out vec4 aego_normal_local_rect;")
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen("flat out vec4 aego_emission_region;\nflat out vec4 aego_emission_local_rect;")
}
if mesh {
if u.Features&shader.FeatureSurfaceChannels == 0 {
b.gen("layout(std140) uniform ub5 { vec4 aego_surface_normal_uv; vec4 aego_surface_normal_local; };")
}
b.gen("out vec2 aego_surface_local_position;")
} else {
b.gen("layout(location = 10) in vec4 aego_i_normal_uv;\nlayout(location = 11) in vec4 aego_i_normal_local;")
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen("layout(location = 12) in vec4 aego_i_emission_uv;\nlayout(location = 13) in vec4 aego_i_emission_local;")
}
}
}
if u.Vertex {
for _, l := range strip(u.body, reFrag) {
b.add(l.text, l.loc)
}
}
b.gen("void main() {")
if mesh {
b.gen(meshMain)
} else {
b.gen(spriteMain)
if coverage {
b.gen(" if (aego_i_local_rect.x >= 2.0) aego_local_rect.x -= 2.0;")
}
b.gen(spriteLocalCoordinates)
}
for i := 0; i < u.Lanes; i++ {
b.gen(fmt.Sprintf(" aego_lane%d = aego_i_lane%d;", i, i))
}
u.assignLanes(b)
if u.Vertex {
b.gen(" vertex();")
}
if u.Features&shader.FeatureLighting != 0 {
b.gen(" aego_surface_world = aego_world;")
if mesh {
b.gen(" aego_surface_basis = vec4(aego_model[0].xy,aego_model[1].xy);\n aego_normal_region = aego_surface_normal_uv;\n aego_normal_local_rect = aego_surface_normal_local;\n aego_surface_local_position = aego_i_pos.xy;")
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen(" aego_emission_region = aego_surface_emission_uv;\n aego_emission_local_rect = aego_surface_emission_local;")
}
} else {
b.gen(" aego_surface_basis = vec4(aego_basis[0],aego_basis[1]);\n aego_normal_region = aego_i_normal_uv;\n aego_normal_local_rect = aego_i_normal_local;")
if u.Features&shader.FeatureSurfaceChannels != 0 {
b.gen(" aego_emission_region = aego_i_emission_uv;\n aego_emission_local_rect = aego_i_emission_local;")
}
}
}
b.gen(publishVertexVaryings)
b.gen(" gl_Position = aego_view_proj * vec4(aego_world, 1.0);")
if coverage {
b.gen(pixelCoverage)
}
b.gen("}")
}
func strip(body []srcLine, entry *regexp.Regexp) []srcLine {
text := []byte(joinBody(body))
name := "fragment"
if entry == reVert {
name = "vertex"
}
for {
start, end, found := entryRange(string(text), name)
if !found {
break
}
for i := start; i < end; i++ {
if text[i] != '\n' {
text[i] = ' '
}
}
}
out := slices.Clone(body)
for i, l := range strings.Split(string(text), "\n") {
if i < len(out) {
out[i].text = l
}
}
return out
}
// Integer pixel edges align coverage and interpolation centers across subpixel precisions; huge or highly stretched bounds use the regular rasterizer.
const pixelCoverage = ` aego_coverage = vec2(.5);
if (aego_i_local_rect.x >= 2.0 && gl_Position.w == 1.0) {
vec2 halfView = .5 * aego_viewport.zw;
vec2 dx = (aego_view_proj * vec4(aego_basis * (vec2(c,s)*aego_i_pos_size.z),0,0)).xy * halfView;
vec2 dy = (aego_view_proj * vec4(aego_basis * (vec2(-s,c)*aego_i_pos_size.w),0,0)).xy * halfView;
mat2 edges = mat2(dx,dy);
float area = abs(determinant(edges));
vec2 origin = (gl_Position.xy + 1.0)*halfView - edges*corner;
vec2 lo = floor(origin + min(dx,vec2(0)) + min(dy,vec2(0)));
vec2 hi = ceil(origin + max(dx,vec2(0)) + max(dy,vec2(0)));
vec2 extent = hi-lo;
if (area > .000001 && extent.x*extent.y <= max(64.0,8.0*area) && all(greaterThan(halfView,vec2(0)))) {
vec2 point = mix(lo,hi,corner);
aego_coverage = inverse(edges)*(point-origin);
aego_uv = mix(aego_i_uv.xy,aego_i_uv.zw,aego_coverage);
aego_local = mix(aego_local_rect.xy,aego_local_rect.zw,aego_coverage);
gl_Position.xy = point/halfView - 1.0;
}
}`