@bigmistqke/view.gl 0.1.8 → 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
@@ -1715,6 +1715,468 @@ describe('compile', () => {
1715
1715
  })
1716
1716
  })
1717
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
+ })
2178
+ })
2179
+
1718
2180
  describe('compile.toString', () => {
1719
2181
  it('should convert glsl tag to shader string', () => {
1720
2182
  const shader = glsl`