@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/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
- // Check that shaderSource was called with symbol aliases in template
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('vec3') && call[1].includes('VIEW_GL_ALIAS_')
636
+ (call: any[]) => call[1].includes(`vec3 ${colorAlias}`)
634
637
  )?.[1]
635
638
 
636
- expect(fragmentShaderSource).toMatch(/vec3 VIEW_GL_ALIAS_\d+ = vec3/)
637
- expect(fragmentShaderSource).toMatch(/vec4\(VIEW_GL_ALIAS_\d+, 1\.0\)/)
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).toMatch(/uniform float VIEW_GL_ALIAS_\d+;/)
1549
- expect(result).toMatch(/vec4\(VIEW_GL_ALIAS_\d+\)/)
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', () => {