OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / glsl.test.js

106 lines · javascript ref:
import { test } from 'brittle'
import { parseFile } from '../lib/parsers/glsl.js'

test('glsl: compute shader with buffers and push constants', (t) => {
  const src = `#version 450

layout(local_size_x = 256) in;
layout(set = 0, binding = 0, std430) readonly buffer DensityBuf { float density[]; };
layout(set = 0, binding = 1, std430) writeonly buffer EdgeBuf { uint edges[]; };
layout(push_constant) uniform PushConstants { uint grid_size; float iso_level; } pc;

void main() {
    uint idx = 0u;
}
`

  const result = parseFile(src, 'compute.glsl')
  t.is(result.error, undefined, 'no parse error')

  t.is(result.buffers.length, 2, '2 buffers')
  t.is(result.buffers[0].name, 'DensityBuf', 'first buffer name')
  t.ok(result.buffers[0].readonly, 'first buffer is readonly')
  t.ok(result.buffers[1].writeonly, 'second buffer is writeonly')
  t.is(result.buffers[0].binding, '0', 'binding captured')
  t.is(result.buffers[0].set, '0', 'set captured')

  t.is(result.pushConstants.length, 1, '1 push constant block')
  t.is(result.pushConstants[0].name, 'PushConstants', 'push constant name')

  t.ok(result.functions.find(f => f.name === 'main'), 'main function found')
  t.ok(result.exports.find(e => e.name === 'main'), 'main is exported (shader entry)')
})

test('glsl: regular uniforms', (t) => {
  const src = `#version 330

uniform float u_time;
uniform sampler2D u_texture;
uniform vec3 u_color;

void main() { }
`

  const result = parseFile(src, 'frag.glsl')
  t.is(result.uniforms.length, 3, '3 uniforms')
  const names = result.uniforms.map(u => u.name)
  t.ok(names.includes('u_time'), 'u_time uniform')
  t.ok(names.includes('u_texture'), 'u_texture uniform')
  t.ok(names.includes('u_color'), 'u_color uniform')
})

test('glsl: #define becomes constant', (t) => {
  const src = `#define THREADS 256
#define MAX_ITERATIONS 100

void main() { }
`

  const result = parseFile(src, 'compute.glsl')
  const names = result.constants.map(c => c.name)
  t.ok(names.includes('THREADS'), 'THREADS define captured')
  t.ok(names.includes('MAX_ITERATIONS'), 'MAX_ITERATIONS define captured')
})

test('glsl: struct becomes a class with kind=struct', (t) => {
  const src = `struct Vertex {
    vec3 position;
    vec3 normal;
    vec2 uv;
};

void main() { }
`

  const result = parseFile(src, 'vert.glsl')
  t.is(result.classes.length, 1, '1 struct')
  t.is(result.classes[0].name, 'Vertex', 'struct name')
  t.is(result.classes[0].kind, 'struct', 'kind is struct')
})

test('glsl: Godot .gdshader hint syntax is stripped', (t) => {
  const src = `shader_type spatial;
render_mode unshaded, cull_disabled;

uniform float density_scale : hint_range(0.0, 20.0) = 8.0;
uniform sampler3D cloud_density : filter_linear_mipmap, repeat_enable;

void fragment() {
    ALBEDO = vec3(1.0);
}
`

  const result = parseFile(src, 'cloud.gdshader')
  t.is(result.error, undefined, 'no parse error after hint stripping')
  t.is(result.uniforms.length, 2, '2 uniforms despite hints')
  t.ok(result.functions.find(f => f.name === 'fragment'), 'fragment function')
})

test('glsl: parse error returns empty structure (no crash)', (t) => {
  const src = `this is not valid glsl !!!`
  const result = parseFile(src, 'broken.glsl')
  t.ok(result.error, 'error reported')
  t.is(result.functions.length, 0, 'functions is still an empty array')
  t.is(result.classes.length, 0, 'classes is still an empty array')
})