@bigmistqke/view.gl 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +2 -1
- package/README.md +83 -42
- package/dev/src/examples/bezier.ts +3 -2
- package/dev/src/examples/game-of-life.ts +13 -30
- package/dev/src/examples/glsl-composition.ts +3 -3
- package/dev/src/examples/grid.ts +20 -14
- package/dev/src/examples/pix-sim.ts +29 -22
- package/dev/src/examples/ray-casting.ts +455 -0
- package/dev/src/examples/sdf.ts +98 -0
- package/dev/src/examples/symbol.ts +2 -2
- package/dev/src/index.ts +12 -11
- package/dev/src/utils.ts +18 -4
- package/dist/index.d.mts +2 -2
- package/dist/tag.d.mts +86 -69
- package/dist/tag.mjs +78 -37
- package/dist/tag.mjs.map +1 -1
- package/dist/{types-C4TRuvYh.d.mts → types-DaV9McmR.d.mts} +40 -33
- package/package.json +1 -1
- package/src/tag.ts +128 -59
- package/src/types.ts +76 -49
- package/test/tag.test.ts +697 -6
package/test/tag.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { toID } from '../src'
|
|
2
3
|
import { attribute, compile, glsl, interleave, uniform } from '../src/tag'
|
|
3
4
|
import type { GL } from '../src/types'
|
|
4
5
|
import { createMockGL } from './mocks/webgl'
|
|
@@ -628,13 +629,15 @@ void main() { gl_FragColor = vec4(0.5); }`
|
|
|
628
629
|
|
|
629
630
|
compile(gl, glsl``, fragment)
|
|
630
631
|
|
|
631
|
-
|
|
632
|
+
const colorAlias = toID(color_symbol)
|
|
633
|
+
|
|
634
|
+
// Check that shaderSource was called with exact symbol alias
|
|
632
635
|
const fragmentShaderSource = (gl.shaderSource as any).mock.calls.find(
|
|
633
|
-
(call: any[]) => call[1].includes(
|
|
636
|
+
(call: any[]) => call[1].includes(`vec3 ${colorAlias}`)
|
|
634
637
|
)?.[1]
|
|
635
638
|
|
|
636
|
-
expect(fragmentShaderSource).
|
|
637
|
-
expect(fragmentShaderSource).
|
|
639
|
+
expect(fragmentShaderSource).toContain(`vec3 ${colorAlias} = vec3(1.0, 0.0, 0.0);`)
|
|
640
|
+
expect(fragmentShaderSource).toContain(`gl_FragColor = vec4(${colorAlias}, 1.0);`)
|
|
638
641
|
})
|
|
639
642
|
})
|
|
640
643
|
|
|
@@ -1485,6 +1488,693 @@ describe('compile', () => {
|
|
|
1485
1488
|
expect(schema.uniforms[u_vertex_only]).toEqual({ kind: 'vec2' })
|
|
1486
1489
|
expect(schema.uniforms[u_fragment_only]).toEqual({ kind: 'vec3' })
|
|
1487
1490
|
})
|
|
1491
|
+
|
|
1492
|
+
it('should return compiled vertex and fragment shader strings', () => {
|
|
1493
|
+
const vertex = glsl`
|
|
1494
|
+
${attribute.vec2('a_position')}
|
|
1495
|
+
${uniform.mat4('u_mvpMatrix')}
|
|
1496
|
+
|
|
1497
|
+
void main() {
|
|
1498
|
+
gl_Position = u_mvpMatrix * vec4(a_position, 0.0, 1.0);
|
|
1499
|
+
}
|
|
1500
|
+
`
|
|
1501
|
+
|
|
1502
|
+
const fragment = glsl`
|
|
1503
|
+
precision mediump float;
|
|
1504
|
+
${uniform.vec4('u_color')}
|
|
1505
|
+
|
|
1506
|
+
void main() {
|
|
1507
|
+
gl_FragColor = u_color;
|
|
1508
|
+
}
|
|
1509
|
+
`
|
|
1510
|
+
|
|
1511
|
+
const result = compile(gl, vertex, fragment)
|
|
1512
|
+
|
|
1513
|
+
// Check vertex shader string
|
|
1514
|
+
expect(result.vertex).toContain('attribute vec2 a_position;')
|
|
1515
|
+
expect(result.vertex).toContain('uniform mat4 u_mvpMatrix;')
|
|
1516
|
+
expect(result.vertex).toContain('gl_Position = u_mvpMatrix * vec4(a_position, 0.0, 1.0);')
|
|
1517
|
+
|
|
1518
|
+
// Check fragment shader string
|
|
1519
|
+
expect(result.fragment).toContain('precision mediump float;')
|
|
1520
|
+
expect(result.fragment).toContain('uniform vec4 u_color;')
|
|
1521
|
+
expect(result.fragment).toContain('gl_FragColor = u_color;')
|
|
1522
|
+
})
|
|
1523
|
+
|
|
1524
|
+
it('should return WebGL2 shader strings with proper syntax', () => {
|
|
1525
|
+
const vertex = glsl`#version 300 es
|
|
1526
|
+
${attribute.vec3('a_position')}
|
|
1527
|
+
${attribute.vec3('a_normal')}
|
|
1528
|
+
out vec3 v_normal;
|
|
1529
|
+
|
|
1530
|
+
void main() {
|
|
1531
|
+
v_normal = a_normal;
|
|
1532
|
+
gl_Position = vec4(a_position, 1.0);
|
|
1533
|
+
}
|
|
1534
|
+
`
|
|
1535
|
+
|
|
1536
|
+
const fragment = glsl`#version 300 es
|
|
1537
|
+
precision mediump float;
|
|
1538
|
+
${uniform.vec3('u_lightDir')}
|
|
1539
|
+
in vec3 v_normal;
|
|
1540
|
+
out vec4 fragColor;
|
|
1541
|
+
|
|
1542
|
+
void main() {
|
|
1543
|
+
float light = dot(v_normal, u_lightDir);
|
|
1544
|
+
fragColor = vec4(vec3(light), 1.0);
|
|
1545
|
+
}
|
|
1546
|
+
`
|
|
1547
|
+
|
|
1548
|
+
const result = compile(gl, vertex, fragment)
|
|
1549
|
+
|
|
1550
|
+
// Check vertex uses 'in' for attributes in WebGL2
|
|
1551
|
+
expect(result.vertex).toContain('in vec3 a_position;')
|
|
1552
|
+
expect(result.vertex).toContain('in vec3 a_normal;')
|
|
1553
|
+
expect(result.vertex).toContain('out vec3 v_normal;')
|
|
1554
|
+
|
|
1555
|
+
// Check fragment shader WebGL2 syntax
|
|
1556
|
+
expect(result.fragment).toContain('#version 300 es')
|
|
1557
|
+
expect(result.fragment).toContain('in vec3 v_normal;')
|
|
1558
|
+
expect(result.fragment).toContain('out vec4 fragColor;')
|
|
1559
|
+
})
|
|
1560
|
+
|
|
1561
|
+
it('should return shader strings with resolved symbols', () => {
|
|
1562
|
+
const u_time_symbol = Symbol('u_time')
|
|
1563
|
+
const a_pos_symbol = Symbol('a_position')
|
|
1564
|
+
|
|
1565
|
+
const vertex = glsl`
|
|
1566
|
+
${attribute.vec3(a_pos_symbol)}
|
|
1567
|
+
${uniform.float(u_time_symbol)}
|
|
1568
|
+
|
|
1569
|
+
void main() {
|
|
1570
|
+
gl_Position = vec4(${a_pos_symbol}, ${u_time_symbol});
|
|
1571
|
+
}
|
|
1572
|
+
`
|
|
1573
|
+
|
|
1574
|
+
const fragment = glsl`
|
|
1575
|
+
precision mediump float;
|
|
1576
|
+
${uniform.float(u_time_symbol)}
|
|
1577
|
+
|
|
1578
|
+
void main() {
|
|
1579
|
+
gl_FragColor = vec4(${u_time_symbol});
|
|
1580
|
+
}
|
|
1581
|
+
`
|
|
1582
|
+
|
|
1583
|
+
const result = compile(gl, vertex, fragment)
|
|
1584
|
+
|
|
1585
|
+
// Get exact symbol aliases
|
|
1586
|
+
const posAlias = toID(a_pos_symbol)
|
|
1587
|
+
const timeAlias = toID(u_time_symbol)
|
|
1588
|
+
|
|
1589
|
+
// Check vertex shader contains expected exact strings
|
|
1590
|
+
expect(result.vertex).toContain(`attribute vec3 ${posAlias};`)
|
|
1591
|
+
expect(result.vertex).toContain(`uniform float ${timeAlias};`)
|
|
1592
|
+
expect(result.vertex).toContain(`gl_Position = vec4(${posAlias}, ${timeAlias});`)
|
|
1593
|
+
|
|
1594
|
+
// Check fragment shader contains expected exact strings
|
|
1595
|
+
expect(result.fragment).toContain(`uniform float ${timeAlias};`)
|
|
1596
|
+
expect(result.fragment).toContain(`gl_FragColor = vec4(${timeAlias});`)
|
|
1597
|
+
expect(result.fragment).toContain('precision mediump float;')
|
|
1598
|
+
})
|
|
1599
|
+
|
|
1600
|
+
it('should return shader strings with interleaved attributes', () => {
|
|
1601
|
+
const vertex = glsl`
|
|
1602
|
+
${interleave('vertexData', [
|
|
1603
|
+
attribute.vec3('a_position'),
|
|
1604
|
+
attribute.vec2('a_uv'),
|
|
1605
|
+
attribute.vec3('a_normal')
|
|
1606
|
+
])}
|
|
1607
|
+
|
|
1608
|
+
void main() {
|
|
1609
|
+
gl_Position = vec4(a_position + a_normal, 0.0, 1.0);
|
|
1610
|
+
}
|
|
1611
|
+
`
|
|
1612
|
+
|
|
1613
|
+
const fragment = glsl`
|
|
1614
|
+
precision mediump float;
|
|
1615
|
+
|
|
1616
|
+
void main() {
|
|
1617
|
+
gl_FragColor = vec4(1.0);
|
|
1618
|
+
}
|
|
1619
|
+
`
|
|
1620
|
+
|
|
1621
|
+
const result = compile(gl, vertex, fragment)
|
|
1622
|
+
|
|
1623
|
+
// Check all interleaved attributes are expanded in vertex shader
|
|
1624
|
+
expect(result.vertex).toContain('attribute vec3 a_position;')
|
|
1625
|
+
expect(result.vertex).toContain('attribute vec2 a_uv;')
|
|
1626
|
+
expect(result.vertex).toContain('attribute vec3 a_normal;')
|
|
1627
|
+
expect(result.vertex).toContain('gl_Position = vec4(a_position + a_normal, 0.0, 1.0);')
|
|
1628
|
+
|
|
1629
|
+
// Fragment shader should be returned as-is
|
|
1630
|
+
expect(result.fragment).toContain('precision mediump float;')
|
|
1631
|
+
expect(result.fragment).toContain('gl_FragColor = vec4(1.0);')
|
|
1632
|
+
})
|
|
1633
|
+
|
|
1634
|
+
it('should return shader strings with composed GLSL fragments', () => {
|
|
1635
|
+
const sharedUniforms = glsl`
|
|
1636
|
+
${uniform.float('u_time')}
|
|
1637
|
+
${uniform.vec2('u_resolution')}
|
|
1638
|
+
`
|
|
1639
|
+
|
|
1640
|
+
const utils = glsl`
|
|
1641
|
+
float rand(vec2 co) {
|
|
1642
|
+
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
|
1643
|
+
}
|
|
1644
|
+
`
|
|
1645
|
+
|
|
1646
|
+
const vertex = glsl`
|
|
1647
|
+
${sharedUniforms}
|
|
1648
|
+
${attribute.vec2('a_position')}
|
|
1649
|
+
|
|
1650
|
+
void main() {
|
|
1651
|
+
gl_Position = vec4(a_position * u_resolution, 0.0, 1.0);
|
|
1652
|
+
}
|
|
1653
|
+
`
|
|
1654
|
+
|
|
1655
|
+
const fragment = glsl`
|
|
1656
|
+
precision mediump float;
|
|
1657
|
+
${sharedUniforms}
|
|
1658
|
+
${utils}
|
|
1659
|
+
|
|
1660
|
+
void main() {
|
|
1661
|
+
float r = rand(gl_FragCoord.xy * u_time);
|
|
1662
|
+
gl_FragColor = vec4(r);
|
|
1663
|
+
}
|
|
1664
|
+
`
|
|
1665
|
+
|
|
1666
|
+
const result = compile(gl, vertex, fragment)
|
|
1667
|
+
|
|
1668
|
+
// Check vertex shader includes composed uniforms
|
|
1669
|
+
expect(result.vertex).toContain('uniform float u_time;')
|
|
1670
|
+
expect(result.vertex).toContain('uniform vec2 u_resolution;')
|
|
1671
|
+
expect(result.vertex).toContain('attribute vec2 a_position;')
|
|
1672
|
+
|
|
1673
|
+
// Check fragment shader includes both uniforms and utils
|
|
1674
|
+
expect(result.fragment).toContain('uniform float u_time;')
|
|
1675
|
+
expect(result.fragment).toContain('uniform vec2 u_resolution;')
|
|
1676
|
+
expect(result.fragment).toContain('float rand(vec2 co)')
|
|
1677
|
+
expect(result.fragment).toContain('return fract(sin(dot(co.xy')
|
|
1678
|
+
})
|
|
1679
|
+
|
|
1680
|
+
it('should return shader strings with array interpolations', () => {
|
|
1681
|
+
const vertex = glsl`
|
|
1682
|
+
${[
|
|
1683
|
+
uniform.mat4('u_model'),
|
|
1684
|
+
uniform.mat4('u_view'),
|
|
1685
|
+
uniform.mat4('u_projection')
|
|
1686
|
+
]}
|
|
1687
|
+
${[attribute.vec3('a_position'), attribute.vec3('a_normal')]}
|
|
1688
|
+
|
|
1689
|
+
void main() {
|
|
1690
|
+
gl_Position = u_projection * u_view * u_model * vec4(a_position, 1.0);
|
|
1691
|
+
}
|
|
1692
|
+
`
|
|
1693
|
+
|
|
1694
|
+
const fragment = glsl`
|
|
1695
|
+
precision mediump float;
|
|
1696
|
+
${[uniform.vec3('u_color'), uniform.float('u_alpha')]}
|
|
1697
|
+
|
|
1698
|
+
void main() {
|
|
1699
|
+
gl_FragColor = vec4(u_color, u_alpha);
|
|
1700
|
+
}
|
|
1701
|
+
`
|
|
1702
|
+
|
|
1703
|
+
const result = compile(gl, vertex, fragment)
|
|
1704
|
+
|
|
1705
|
+
// Check vertex shader has all uniforms and attributes from arrays
|
|
1706
|
+
expect(result.vertex).toContain('uniform mat4 u_model;')
|
|
1707
|
+
expect(result.vertex).toContain('uniform mat4 u_view;')
|
|
1708
|
+
expect(result.vertex).toContain('uniform mat4 u_projection;')
|
|
1709
|
+
expect(result.vertex).toContain('attribute vec3 a_position;')
|
|
1710
|
+
expect(result.vertex).toContain('attribute vec3 a_normal;')
|
|
1711
|
+
|
|
1712
|
+
// Check fragment shader
|
|
1713
|
+
expect(result.fragment).toContain('uniform vec3 u_color;')
|
|
1714
|
+
expect(result.fragment).toContain('uniform float u_alpha;')
|
|
1715
|
+
})
|
|
1716
|
+
})
|
|
1717
|
+
|
|
1718
|
+
describe('compile.toQuad', () => {
|
|
1719
|
+
let gl: GL
|
|
1720
|
+
let mockProgram: WebGLProgram
|
|
1721
|
+
|
|
1722
|
+
beforeEach(() => {
|
|
1723
|
+
gl = createMockGL()
|
|
1724
|
+
mockProgram = gl.createProgram()!
|
|
1725
|
+
|
|
1726
|
+
// Mock shader creation and compilation
|
|
1727
|
+
gl.createShader = vi.fn(type => ({ type } as WebGLShader))
|
|
1728
|
+
gl.shaderSource = vi.fn()
|
|
1729
|
+
gl.compileShader = vi.fn()
|
|
1730
|
+
gl.getShaderParameter = vi.fn(() => true)
|
|
1731
|
+
gl.attachShader = vi.fn()
|
|
1732
|
+
gl.linkProgram = vi.fn()
|
|
1733
|
+
gl.getProgramParameter = vi.fn(() => true)
|
|
1734
|
+
gl.createProgram = vi.fn(() => mockProgram)
|
|
1735
|
+
gl.createBuffer = vi.fn(() => ({} as WebGLBuffer))
|
|
1736
|
+
gl.bindBuffer = vi.fn()
|
|
1737
|
+
gl.bufferData = vi.fn()
|
|
1738
|
+
})
|
|
1739
|
+
|
|
1740
|
+
it('should create a quad with fragment shader only', () => {
|
|
1741
|
+
const fragment = glsl`
|
|
1742
|
+
precision mediump float;
|
|
1743
|
+
${uniform.float('u_time')}
|
|
1744
|
+
${uniform.vec2('u_resolution')}
|
|
1745
|
+
|
|
1746
|
+
in vec2 uv;
|
|
1747
|
+
out vec4 fragColor;
|
|
1748
|
+
|
|
1749
|
+
void main() {
|
|
1750
|
+
vec2 pos = uv * u_resolution;
|
|
1751
|
+
float pattern = sin(pos.x * 0.01 + u_time) * sin(pos.y * 0.01 + u_time);
|
|
1752
|
+
fragColor = vec4(vec3(pattern), 1.0);
|
|
1753
|
+
}
|
|
1754
|
+
`
|
|
1755
|
+
|
|
1756
|
+
const result = compile.toQuad(gl, fragment)
|
|
1757
|
+
|
|
1758
|
+
expect(result.program).toBe(mockProgram)
|
|
1759
|
+
|
|
1760
|
+
// Should have quad attribute in schema
|
|
1761
|
+
expect(result.schema.attributes).toEqual({
|
|
1762
|
+
a_quad: { kind: 'vec2', buffer: expect.any(Object) }
|
|
1763
|
+
})
|
|
1764
|
+
|
|
1765
|
+
// Should have fragment uniforms
|
|
1766
|
+
expect(result.schema.uniforms).toEqual({
|
|
1767
|
+
u_time: { kind: 'float' },
|
|
1768
|
+
u_resolution: { kind: 'vec2' }
|
|
1769
|
+
})
|
|
1770
|
+
})
|
|
1771
|
+
|
|
1772
|
+
it('should generate vertex shader with correct uv output', () => {
|
|
1773
|
+
const fragment = glsl`#version 300 es
|
|
1774
|
+
precision mediump float;
|
|
1775
|
+
${uniform.vec3('u_color')}
|
|
1776
|
+
|
|
1777
|
+
in vec2 v_uv;
|
|
1778
|
+
out vec4 fragColor;
|
|
1779
|
+
|
|
1780
|
+
void main() {
|
|
1781
|
+
fragColor = vec4(u_color * v_uv.x, 1.0);
|
|
1782
|
+
}
|
|
1783
|
+
`
|
|
1784
|
+
|
|
1785
|
+
const result = compile.toQuad(gl, fragment)
|
|
1786
|
+
|
|
1787
|
+
// Check that vertex shader contains expected content
|
|
1788
|
+
expect(result.vertex).toContain('#version 300 es')
|
|
1789
|
+
expect(result.vertex).toContain('in vec2 a_quad;')
|
|
1790
|
+
expect(result.vertex).toContain('out vec2 v_uv;')
|
|
1791
|
+
expect(result.vertex).toContain('v_uv = a_quad;')
|
|
1792
|
+
expect(result.vertex).toContain('gl_Position = vec4(v_uv, 0.0, 1.0);')
|
|
1793
|
+
})
|
|
1794
|
+
|
|
1795
|
+
it('should handle fragment shader with multiple uniforms', () => {
|
|
1796
|
+
const fragment = glsl`#version 300 es
|
|
1797
|
+
precision mediump float;
|
|
1798
|
+
|
|
1799
|
+
${uniform.float('u_time')}
|
|
1800
|
+
${uniform.vec2('u_resolution')}
|
|
1801
|
+
${uniform.vec3('u_color')}
|
|
1802
|
+
${uniform.sampler2D('u_texture')}
|
|
1803
|
+
|
|
1804
|
+
in vec2 uv;
|
|
1805
|
+
out vec4 fragColor;
|
|
1806
|
+
|
|
1807
|
+
void main() {
|
|
1808
|
+
vec2 pos = uv * u_resolution + u_time;
|
|
1809
|
+
vec4 tex = texture(u_texture, pos);
|
|
1810
|
+
fragColor = vec4(u_color * tex.rgb, 1.0);
|
|
1811
|
+
}
|
|
1812
|
+
`
|
|
1813
|
+
|
|
1814
|
+
const result = compile.toQuad(gl, fragment)
|
|
1815
|
+
|
|
1816
|
+
expect(result.schema.uniforms).toEqual({
|
|
1817
|
+
u_time: { kind: 'float' },
|
|
1818
|
+
u_resolution: { kind: 'vec2' },
|
|
1819
|
+
u_color: { kind: 'vec3' },
|
|
1820
|
+
u_texture: { kind: 'sampler2D' }
|
|
1821
|
+
})
|
|
1822
|
+
|
|
1823
|
+
expect(result.schema.attributes).toEqual({
|
|
1824
|
+
a_quad: { kind: 'vec2', buffer: expect.any(Object) }
|
|
1825
|
+
})
|
|
1826
|
+
})
|
|
1827
|
+
|
|
1828
|
+
it('should automatically set up quad buffer data', () => {
|
|
1829
|
+
const fragment = glsl`
|
|
1830
|
+
precision mediump float;
|
|
1831
|
+
void main() { gl_FragColor = vec4(1.0); }
|
|
1832
|
+
`
|
|
1833
|
+
|
|
1834
|
+
const result = compile.toQuad(gl, fragment)
|
|
1835
|
+
|
|
1836
|
+
// Check that buffer was created and data was set
|
|
1837
|
+
expect(gl.createBuffer).toHaveBeenCalled()
|
|
1838
|
+
expect(gl.bindBuffer).toHaveBeenCalled()
|
|
1839
|
+
expect(gl.bufferData).toHaveBeenCalled()
|
|
1840
|
+
|
|
1841
|
+
// Verify the quad data contains correct triangle vertices
|
|
1842
|
+
const bufferDataCall = (gl.bufferData as any).mock.calls[0]
|
|
1843
|
+
const quadData = bufferDataCall[1] as Float32Array
|
|
1844
|
+
|
|
1845
|
+
// Should contain 6 vertices for 2 triangles forming a quad
|
|
1846
|
+
expect(quadData.length).toBe(12) // 6 vertices * 2 components
|
|
1847
|
+
expect(Array.from(quadData)).toEqual([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])
|
|
1848
|
+
})
|
|
1849
|
+
|
|
1850
|
+
it('should work with ray-marching fragment shader', () => {
|
|
1851
|
+
const fragment = glsl`#version 300 es
|
|
1852
|
+
precision mediump float;
|
|
1853
|
+
|
|
1854
|
+
${uniform.float('u_time')}
|
|
1855
|
+
${uniform.vec2('u_resolution')}
|
|
1856
|
+
${uniform.vec3('u_cameraPos')}
|
|
1857
|
+
|
|
1858
|
+
in vec2 uv;
|
|
1859
|
+
out vec4 fragColor;
|
|
1860
|
+
|
|
1861
|
+
float sdSphere(vec3 pos, float radius) {
|
|
1862
|
+
return length(pos) - radius;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
void main() {
|
|
1866
|
+
vec2 screenPos = uv * u_resolution;
|
|
1867
|
+
vec3 rayDir = normalize(vec3(screenPos, -1.0));
|
|
1868
|
+
|
|
1869
|
+
float dist = sdSphere(u_cameraPos, 1.0);
|
|
1870
|
+
vec3 color = vec3(smoothstep(0.0, 0.1, dist));
|
|
1871
|
+
|
|
1872
|
+
fragColor = vec4(color, 1.0);
|
|
1873
|
+
}
|
|
1874
|
+
`
|
|
1875
|
+
|
|
1876
|
+
const result = compile.toQuad(gl, fragment)
|
|
1877
|
+
|
|
1878
|
+
expect(result.schema.uniforms).toEqual({
|
|
1879
|
+
u_time: { kind: 'float' },
|
|
1880
|
+
u_resolution: { kind: 'vec2' },
|
|
1881
|
+
u_cameraPos: { kind: 'vec3' }
|
|
1882
|
+
})
|
|
1883
|
+
|
|
1884
|
+
// Should compile without errors
|
|
1885
|
+
expect(result.program).toBe(mockProgram)
|
|
1886
|
+
expect(result.vertex).toContain('in vec2 a_quad;')
|
|
1887
|
+
expect(result.fragment).toContain('float sdSphere')
|
|
1888
|
+
})
|
|
1889
|
+
|
|
1890
|
+
it('should support options parameter', () => {
|
|
1891
|
+
const fragment = glsl`
|
|
1892
|
+
precision mediump float;
|
|
1893
|
+
${uniform.float('u_time')}
|
|
1894
|
+
void main() { gl_FragColor = vec4(1.0); }
|
|
1895
|
+
`
|
|
1896
|
+
|
|
1897
|
+
const options = {
|
|
1898
|
+
schema: {
|
|
1899
|
+
uniforms: {
|
|
1900
|
+
u_customUniform: { kind: 'vec3' as const }
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const result = compile.toQuad(gl, fragment, options)
|
|
1906
|
+
|
|
1907
|
+
// Should merge the custom schema
|
|
1908
|
+
expect(result.schema.uniforms).toEqual({
|
|
1909
|
+
u_time: { kind: 'float' },
|
|
1910
|
+
u_customUniform: { kind: 'vec3' }
|
|
1911
|
+
})
|
|
1912
|
+
})
|
|
1913
|
+
|
|
1914
|
+
it('should handle fragment shader with symbol uniforms', () => {
|
|
1915
|
+
const u_time_symbol = Symbol('u_time')
|
|
1916
|
+
const u_color_symbol = Symbol('u_color')
|
|
1917
|
+
|
|
1918
|
+
const fragment = glsl`
|
|
1919
|
+
precision mediump float;
|
|
1920
|
+
|
|
1921
|
+
${uniform.float(u_time_symbol)}
|
|
1922
|
+
${uniform.vec3(u_color_symbol)}
|
|
1923
|
+
|
|
1924
|
+
in vec2 uv;
|
|
1925
|
+
out vec4 fragColor;
|
|
1926
|
+
|
|
1927
|
+
void main() {
|
|
1928
|
+
float wave = sin(${u_time_symbol} + uv.x * 10.0);
|
|
1929
|
+
fragColor = vec4(${u_color_symbol} * wave, 1.0);
|
|
1930
|
+
}
|
|
1931
|
+
`
|
|
1932
|
+
|
|
1933
|
+
const result = compile.toQuad(gl, fragment)
|
|
1934
|
+
|
|
1935
|
+
expect(result.schema.uniforms).toEqual({
|
|
1936
|
+
[u_time_symbol]: { kind: 'float' },
|
|
1937
|
+
[u_color_symbol]: { kind: 'vec3' }
|
|
1938
|
+
})
|
|
1939
|
+
})
|
|
1940
|
+
|
|
1941
|
+
it('should handle fragment shader with composed GLSL', () => {
|
|
1942
|
+
const commonUniforms = glsl`
|
|
1943
|
+
${uniform.float('u_time')}
|
|
1944
|
+
${uniform.vec2('u_resolution')}
|
|
1945
|
+
`
|
|
1946
|
+
|
|
1947
|
+
const noiseCode = glsl`
|
|
1948
|
+
float random(vec2 st) {
|
|
1949
|
+
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
|
|
1950
|
+
}
|
|
1951
|
+
`
|
|
1952
|
+
|
|
1953
|
+
const fragment = glsl`
|
|
1954
|
+
precision mediump float;
|
|
1955
|
+
|
|
1956
|
+
${commonUniforms}
|
|
1957
|
+
${noiseCode}
|
|
1958
|
+
|
|
1959
|
+
in vec2 uv;
|
|
1960
|
+
out vec4 fragColor;
|
|
1961
|
+
|
|
1962
|
+
void main() {
|
|
1963
|
+
float noise = random(uv * u_resolution + u_time);
|
|
1964
|
+
fragColor = vec4(vec3(noise), 1.0);
|
|
1965
|
+
}
|
|
1966
|
+
`
|
|
1967
|
+
|
|
1968
|
+
const result = compile.toQuad(gl, fragment)
|
|
1969
|
+
|
|
1970
|
+
expect(result.schema.uniforms).toEqual({
|
|
1971
|
+
u_time: { kind: 'float' },
|
|
1972
|
+
u_resolution: { kind: 'vec2' }
|
|
1973
|
+
})
|
|
1974
|
+
|
|
1975
|
+
expect(result.fragment).toContain('float random(vec2 st)')
|
|
1976
|
+
})
|
|
1977
|
+
|
|
1978
|
+
it('should work with empty fragment shader', () => {
|
|
1979
|
+
const fragment = glsl`
|
|
1980
|
+
precision mediump float;
|
|
1981
|
+
void main() {
|
|
1982
|
+
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
1983
|
+
}
|
|
1984
|
+
`
|
|
1985
|
+
|
|
1986
|
+
const result = compile.toQuad(gl, fragment)
|
|
1987
|
+
|
|
1988
|
+
expect(result.schema.uniforms).toEqual({})
|
|
1989
|
+
expect(result.schema.attributes).toEqual({
|
|
1990
|
+
a_quad: { kind: 'vec2', buffer: expect.any(Object) }
|
|
1991
|
+
})
|
|
1992
|
+
expect(result.program).toBe(mockProgram)
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
it('should reuse buffers across multiple calls', () => {
|
|
1996
|
+
const fragment1 = glsl`void main() { gl_FragColor = vec4(1.0); }`
|
|
1997
|
+
const fragment2 = glsl`void main() { gl_FragColor = vec4(0.5); }`
|
|
1998
|
+
|
|
1999
|
+
compile.toQuad(gl, fragment1)
|
|
2000
|
+
compile.toQuad(gl, fragment2)
|
|
2001
|
+
|
|
2002
|
+
// Buffer should only be created once (reused)
|
|
2003
|
+
expect(gl.createBuffer).toHaveBeenCalledTimes(1)
|
|
2004
|
+
})
|
|
2005
|
+
|
|
2006
|
+
it('should expose quad attribute in view for binding', () => {
|
|
2007
|
+
const fragment = glsl`
|
|
2008
|
+
precision mediump float;
|
|
2009
|
+
void main() { gl_FragColor = vec4(1.0); }
|
|
2010
|
+
`
|
|
2011
|
+
|
|
2012
|
+
const result = compile.toQuad(gl, fragment)
|
|
2013
|
+
|
|
2014
|
+
// The view should have the a_quad attribute available
|
|
2015
|
+
expect('a_quad' in result.view.attributes).toBe(true)
|
|
2016
|
+
})
|
|
2017
|
+
|
|
2018
|
+
describe('WebGL version detection', () => {
|
|
2019
|
+
it('should generate WebGL1 vertex shader for fragment without version directive', () => {
|
|
2020
|
+
const fragment = glsl`
|
|
2021
|
+
precision mediump float;
|
|
2022
|
+
${uniform.float('u_time')}
|
|
2023
|
+
|
|
2024
|
+
varying vec2 v_uv;
|
|
2025
|
+
|
|
2026
|
+
void main() {
|
|
2027
|
+
gl_FragColor = vec4(v_uv, u_time, 1.0);
|
|
2028
|
+
}
|
|
2029
|
+
`
|
|
2030
|
+
|
|
2031
|
+
const result = compile.toQuad(gl, fragment)
|
|
2032
|
+
|
|
2033
|
+
// Should generate WebGL1 vertex shader
|
|
2034
|
+
expect(result.vertex).not.toContain('#version 300 es')
|
|
2035
|
+
expect(result.vertex).toContain('attribute vec2 a_quad;')
|
|
2036
|
+
expect(result.vertex).toContain('varying vec2 v_uv;')
|
|
2037
|
+
expect(result.vertex).not.toContain('in vec2 a_quad;')
|
|
2038
|
+
expect(result.vertex).not.toContain('out vec2 v_uv;')
|
|
2039
|
+
})
|
|
2040
|
+
|
|
2041
|
+
it('should generate WebGL2 vertex shader for fragment with #version 300 es', () => {
|
|
2042
|
+
const fragment = glsl`#version 300 es
|
|
2043
|
+
precision mediump float;
|
|
2044
|
+
${uniform.float('u_time')}
|
|
2045
|
+
|
|
2046
|
+
in vec2 v_uv;
|
|
2047
|
+
out vec4 fragColor;
|
|
2048
|
+
|
|
2049
|
+
void main() {
|
|
2050
|
+
fragColor = vec4(v_uv, u_time, 1.0);
|
|
2051
|
+
}
|
|
2052
|
+
`
|
|
2053
|
+
|
|
2054
|
+
const result = compile.toQuad(gl, fragment)
|
|
2055
|
+
|
|
2056
|
+
// Should generate WebGL2 vertex shader
|
|
2057
|
+
expect(result.vertex).toContain('#version 300 es')
|
|
2058
|
+
expect(result.vertex).toContain('in vec2 a_quad;')
|
|
2059
|
+
expect(result.vertex).toContain('out vec2 v_uv;')
|
|
2060
|
+
expect(result.vertex).not.toContain('attribute vec2 a_quad;')
|
|
2061
|
+
expect(result.vertex).not.toContain('varying vec2 v_uv;')
|
|
2062
|
+
})
|
|
2063
|
+
|
|
2064
|
+
it('should respect explicit webgl2 option over fragment version detection', () => {
|
|
2065
|
+
const fragment = glsl`
|
|
2066
|
+
precision mediump float;
|
|
2067
|
+
${uniform.float('u_time')}
|
|
2068
|
+
|
|
2069
|
+
void main() {
|
|
2070
|
+
gl_FragColor = vec4(u_time);
|
|
2071
|
+
}
|
|
2072
|
+
`
|
|
2073
|
+
|
|
2074
|
+
// Force WebGL2 despite no version directive in fragment
|
|
2075
|
+
const result = compile.toQuad(gl, fragment, { webgl2: true })
|
|
2076
|
+
|
|
2077
|
+
// Should generate WebGL2 vertex shader
|
|
2078
|
+
expect(result.vertex).toContain('#version 300 es')
|
|
2079
|
+
expect(result.vertex).toContain('in vec2 a_quad;')
|
|
2080
|
+
expect(result.vertex).toContain('out vec2 v_uv;')
|
|
2081
|
+
})
|
|
2082
|
+
|
|
2083
|
+
it('should respect explicit webgl2: false option over fragment version detection', () => {
|
|
2084
|
+
const fragment = glsl`#version 300 es
|
|
2085
|
+
precision mediump float;
|
|
2086
|
+
${uniform.float('u_time')}
|
|
2087
|
+
|
|
2088
|
+
in vec2 v_uv;
|
|
2089
|
+
out vec4 fragColor;
|
|
2090
|
+
|
|
2091
|
+
void main() {
|
|
2092
|
+
fragColor = vec4(u_time);
|
|
2093
|
+
}
|
|
2094
|
+
`
|
|
2095
|
+
|
|
2096
|
+
// Force WebGL1 despite version directive in fragment
|
|
2097
|
+
const result = compile.toQuad(gl, fragment, { webgl2: false })
|
|
2098
|
+
|
|
2099
|
+
// Should generate WebGL1 vertex shader
|
|
2100
|
+
expect(result.vertex).not.toContain('#version 300 es')
|
|
2101
|
+
expect(result.vertex).toContain('attribute vec2 a_quad;')
|
|
2102
|
+
expect(result.vertex).toContain('varying vec2 v_uv;')
|
|
2103
|
+
})
|
|
2104
|
+
|
|
2105
|
+
it('should handle fragment with mixed content and version detection', () => {
|
|
2106
|
+
const commonUniforms = glsl`
|
|
2107
|
+
${uniform.float('u_time')}
|
|
2108
|
+
${uniform.vec2('u_resolution')}
|
|
2109
|
+
`
|
|
2110
|
+
|
|
2111
|
+
const fragment = glsl`#version 300 es
|
|
2112
|
+
precision mediump float;
|
|
2113
|
+
|
|
2114
|
+
${commonUniforms}
|
|
2115
|
+
|
|
2116
|
+
in vec2 v_uv;
|
|
2117
|
+
out vec4 fragColor;
|
|
2118
|
+
|
|
2119
|
+
void main() {
|
|
2120
|
+
vec2 uv = v_uv * u_resolution;
|
|
2121
|
+
fragColor = vec4(sin(uv.x + u_time), cos(uv.y + u_time), 0.5, 1.0);
|
|
2122
|
+
}
|
|
2123
|
+
`
|
|
2124
|
+
|
|
2125
|
+
const result = compile.toQuad(gl, fragment)
|
|
2126
|
+
|
|
2127
|
+
// Should detect WebGL2 from version directive
|
|
2128
|
+
expect(result.vertex).toContain('#version 300 es')
|
|
2129
|
+
expect(result.vertex).toContain('in vec2 a_quad;')
|
|
2130
|
+
expect(result.vertex).toContain('out vec2 v_uv;')
|
|
2131
|
+
|
|
2132
|
+
// Should preserve fragment uniforms
|
|
2133
|
+
expect(result.schema.uniforms).toEqual({
|
|
2134
|
+
u_time: { kind: 'float' },
|
|
2135
|
+
u_resolution: { kind: 'vec2' }
|
|
2136
|
+
})
|
|
2137
|
+
})
|
|
2138
|
+
|
|
2139
|
+
it('should work with WebGL1 fragment containing complex shader logic', () => {
|
|
2140
|
+
const lightingCode = glsl`
|
|
2141
|
+
vec3 calculateLighting(vec3 normal, vec3 lightDir) {
|
|
2142
|
+
return max(dot(normal, lightDir), 0.0) * vec3(1.0);
|
|
2143
|
+
}
|
|
2144
|
+
`
|
|
2145
|
+
|
|
2146
|
+
const fragment = glsl`
|
|
2147
|
+
precision mediump float;
|
|
2148
|
+
|
|
2149
|
+
${uniform.vec3('u_lightPos')}
|
|
2150
|
+
${uniform.vec3('u_color')}
|
|
2151
|
+
${lightingCode}
|
|
2152
|
+
|
|
2153
|
+
varying vec2 v_uv;
|
|
2154
|
+
|
|
2155
|
+
void main() {
|
|
2156
|
+
vec3 normal = normalize(vec3(v_uv, 1.0));
|
|
2157
|
+
vec3 lightDir = normalize(u_lightPos);
|
|
2158
|
+
vec3 lighting = calculateLighting(normal, lightDir);
|
|
2159
|
+
gl_FragColor = vec4(u_color * lighting, 1.0);
|
|
2160
|
+
}
|
|
2161
|
+
`
|
|
2162
|
+
|
|
2163
|
+
const result = compile.toQuad(gl, fragment)
|
|
2164
|
+
|
|
2165
|
+
// Should generate WebGL1 vertex shader
|
|
2166
|
+
expect(result.vertex).not.toContain('#version 300 es')
|
|
2167
|
+
expect(result.vertex).toContain('attribute vec2 a_quad;')
|
|
2168
|
+
expect(result.vertex).toContain('varying vec2 v_uv;')
|
|
2169
|
+
|
|
2170
|
+
// Should preserve composed GLSL and uniforms
|
|
2171
|
+
expect(result.fragment).toContain('calculateLighting')
|
|
2172
|
+
expect(result.schema.uniforms).toEqual({
|
|
2173
|
+
u_lightPos: { kind: 'vec3' },
|
|
2174
|
+
u_color: { kind: 'vec3' }
|
|
2175
|
+
})
|
|
2176
|
+
})
|
|
2177
|
+
})
|
|
1488
2178
|
})
|
|
1489
2179
|
|
|
1490
2180
|
describe('compile.toString', () => {
|
|
@@ -1544,9 +2234,10 @@ describe('compile.toString', () => {
|
|
|
1544
2234
|
}`
|
|
1545
2235
|
|
|
1546
2236
|
const result = compile.toString(shader)
|
|
2237
|
+
const timeAlias = toID(u_time_symbol)
|
|
1547
2238
|
|
|
1548
|
-
expect(result).
|
|
1549
|
-
expect(result).
|
|
2239
|
+
expect(result).toContain(`uniform float ${timeAlias};`)
|
|
2240
|
+
expect(result).toContain(`gl_FragColor = vec4(${timeAlias});`)
|
|
1550
2241
|
})
|
|
1551
2242
|
|
|
1552
2243
|
it('should handle interleaved attributes', () => {
|