@flighthq/effects-gl 0.4.0-next.1602.af48ed1 → 0.4.0-next.1663.d9c5746

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.
@@ -31,16 +31,16 @@ void main() {
31
31
  int r = int(min(float(R), u_radius));
32
32
  vec3 means[4];
33
33
  float vars[4];
34
- ivec2 lo[4] = ivec2[4](ivec2(-1, -1), ivec2(0, -1), ivec2(-1, 0), ivec2(0, 0));
35
34
  for (int q = 0; q < 4; q++) {
35
+ int ox = (q & 1) == 0 ? -r : 0;
36
+ int oy = (q & 2) == 0 ? 0 : -r;
36
37
  vec3 sum = vec3(0.0);
37
38
  vec3 sumSq = vec3(0.0);
38
39
  float n = 0.0;
39
40
  for (int y = 0; y <= R; y++) {
40
41
  for (int x = 0; x <= R; x++) {
41
42
  if (x > r || y > r) continue;
42
- ivec2 d = ivec2(x, y) * sign(lo[q] + ivec2(1)) + lo[q] * r;
43
- vec2 off = vec2(float(d.x), float(d.y)) * texel;
43
+ vec2 off = vec2(float(ox + x), float(oy + y)) * texel;
44
44
  vec3 col = texture(u_texture0, v_texCoord + off).rgb;
45
45
  sum += col;
46
46
  sumSq += col * col;
@@ -54,12 +54,18 @@ void main() {
54
54
  }
55
55
  float minVar = vars[0];
56
56
  vec3 result = means[0];
57
+ float tiedCount = 1.0;
57
58
  for (int q = 1; q < 4; q++) {
58
59
  if (vars[q] < minVar) {
59
60
  minVar = vars[q];
60
61
  result = means[q];
62
+ tiedCount = 1.0;
63
+ } else if (vars[q] == minVar) {
64
+ result += means[q];
65
+ tiedCount += 1.0;
61
66
  }
62
67
  }
68
+ result /= tiedCount;
63
69
  o_color = vec4(result, texture(u_texture0, v_texCoord).a);
64
70
  }`;
65
71
  //# sourceMappingURL=glKuwaharaEffect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glKuwaharaEffect.js","sourceRoot":"","sources":["../src/glKuwaharaEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,kGAAkG;AAClG,sGAAsG;AACtG,wEAAwE;AACxE,MAAM,UAAU,uBAAuB,CACrC,KAAoB,EACpB,MAAgC,EAChC,IAA8B,EAC9B,MAAgC;IAEhC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,CAAC,CAAC;IACzF,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACrE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAChF,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,6BAA6B,GAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACjF,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAwB,CAAC,CAAC;AACrF,CAAC,CAAC;AAEF,MAAM,UAAU,wBAAwB,CAAC,KAAoB;IAC3D,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,6BAA6B,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2C5B,CAAC"}
1
+ {"version":3,"file":"glKuwaharaEffect.js","sourceRoot":"","sources":["../src/glKuwaharaEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,kGAAkG;AAClG,sGAAsG;AACtG,wEAAwE;AACxE,MAAM,UAAU,uBAAuB,CACrC,KAAoB,EACpB,MAAgC,EAChC,IAA8B,EAC9B,MAAgC;IAEhC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,CAAC,CAAC;IACzF,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACrE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAChF,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,6BAA6B,GAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACjF,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAwB,CAAC,CAAC;AACrF,CAAC,CAAC;AAEF,MAAM,UAAU,wBAAwB,CAAC,KAAoB;IAC3D,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,6BAA6B,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiD5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/effects-gl",
3
- "version": "0.4.0-next.1602.af48ed1",
3
+ "version": "0.4.0-next.1663.d9c5746",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,14 +38,14 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/adjustments": "0.4.0-next.1602.af48ed1",
42
- "@flighthq/color": "0.4.0-next.1602.af48ed1",
43
- "@flighthq/effects": "0.4.0-next.1602.af48ed1",
44
- "@flighthq/geometry": "0.4.0-next.1602.af48ed1",
45
- "@flighthq/log": "0.4.0-next.1602.af48ed1",
46
- "@flighthq/render-gl": "0.4.0-next.1602.af48ed1",
47
- "@flighthq/registry": "0.4.0-next.1602.af48ed1",
48
- "@flighthq/types": "0.4.0-next.1602.af48ed1"
41
+ "@flighthq/adjustments": "0.4.0-next.1663.d9c5746",
42
+ "@flighthq/color": "0.4.0-next.1663.d9c5746",
43
+ "@flighthq/effects": "0.4.0-next.1663.d9c5746",
44
+ "@flighthq/geometry": "0.4.0-next.1663.d9c5746",
45
+ "@flighthq/log": "0.4.0-next.1663.d9c5746",
46
+ "@flighthq/render-gl": "0.4.0-next.1663.d9c5746",
47
+ "@flighthq/registry": "0.4.0-next.1663.d9c5746",
48
+ "@flighthq/types": "0.4.0-next.1663.d9c5746"
49
49
  },
50
50
  "devDependencies": {
51
51
  "typescript": "^5.3.0"
@@ -1,23 +1,129 @@
1
+ import { createGlRenderState } from '@flighthq/render-gl/contract';
2
+ import type { DisplacementEffect, GlRenderState, GlRenderTarget } from '@flighthq/types/contract';
3
+
4
+ const programMock = vi.hoisted(() => ({
5
+ getGlEffectProgram: vi.fn((_state: unknown, _key: string, _source: string) => ({ program: {} })),
6
+ }));
7
+ const glMock = vi.hoisted(() => ({
8
+ uniform1f: vi.fn((_location: unknown, _value: number) => {}),
9
+ uniform2f: vi.fn((_location: unknown, _x: number, _y: number) => {}),
10
+ }));
11
+
12
+ vi.mock('./glEffectProgramCache', () => programMock);
13
+
14
+ vi.mock('@flighthq/render-gl/contract', async () => {
15
+ const actual = (await vi.importActual('@flighthq/render-gl/contract')) as Record<string, unknown>;
16
+ return {
17
+ ...actual,
18
+ drawGlFullscreenPass: vi.fn((_state, _program, _textures, _dest, setUniforms) => {
19
+ setUniforms({ ...glMock, getUniformLocation: (_program: unknown, name: string) => name }, { program: {} });
20
+ }),
21
+ };
22
+ });
23
+
1
24
  import {
2
25
  applyDisplacementEffectToGl,
3
26
  defaultGlDisplacementEffectRunner,
4
27
  registerGlDisplacementEffect,
5
28
  } from './glDisplacementEffect';
29
+ import { getGlRenderEffectRunner } from './glRenderEffectRegistry';
30
+ import { evaluateGlslScalarExpression, extractGlslExpression } from './glShaderTestHelper';
31
+
32
+ const SOURCE_WIDTH = 128;
33
+ const SOURCE_HEIGHT = 64;
34
+
35
+ beforeEach(() => {
36
+ programMock.getGlEffectProgram.mockClear();
37
+ glMock.uniform1f.mockClear();
38
+ glMock.uniform2f.mockClear();
39
+ });
40
+
41
+ function apply(effect: Readonly<Partial<DisplacementEffect>> = {}): void {
42
+ const target = { height: SOURCE_HEIGHT, texture: {}, width: SOURCE_WIDTH } as unknown as GlRenderTarget;
43
+ applyDisplacementEffectToGl({ gl: {} } as unknown as GlRenderState, target, target, {
44
+ kind: 'DisplacementEffect',
45
+ ...effect,
46
+ } as DisplacementEffect);
47
+ }
48
+
49
+ function shaderSource(): string {
50
+ apply();
51
+ return programMock.getGlEffectProgram.mock.calls[0]![2] as string;
52
+ }
53
+
54
+ function uniformValue(name: string): number {
55
+ const call = glMock.uniform1f.mock.calls.find((entry) => entry[0] === name);
56
+ if (call === undefined) throw new Error(`no uniform1f call for ${name}`);
57
+ return call[1] as number;
58
+ }
6
59
 
7
60
  describe('applyDisplacementEffectToGl', () => {
8
- it('is a function', () => {
9
- expect(typeof applyDisplacementEffectToGl).toBe('function');
61
+ // The defect repaired in 1d71634fc used the bottom-left texcoord directly for this phase. The test
62
+ // evaluates the shader's own image-row and wave expressions, so restoring that line either loses the
63
+ // required imageY binding or produces the opposite row's phase.
64
+ it('drives the horizontal warp from top-left image space', () => {
65
+ const source = shaderSource();
66
+ const imageY = evaluateGlslScalarExpression(extractGlslExpression(source, /float imageY = ([^;]+);/), {
67
+ 'v_texCoord.y': 0.8,
68
+ });
69
+ const warpX = evaluateGlslScalarExpression(extractGlslExpression(source, /vec2 warp = vec2\(\s*([^,]+),/s), {
70
+ f: 2.5,
71
+ imageY,
72
+ u_seed: 0.3,
73
+ });
74
+ const expected = Math.sin(0.2 * 2.5 + 0.3) + Math.sin(0.2 * 2.5 * 2.3 + 0.3 * 1.7) * 0.5;
75
+
76
+ expect(imageY).toBeCloseTo(0.2, 10);
77
+ expect(warpX).toBeCloseTo(expected, 10);
78
+ });
79
+
80
+ it('negates a vertical image-space offset on the way back to bottom-left texcoords', () => {
81
+ const expression = extractGlslExpression(shaderSource(), /vec2 displaced = vec2\([^,]+,\s*([^)]+)\);/);
82
+ const displacedY = evaluateGlslScalarExpression(expression, {
83
+ 'offset.y': 0.1,
84
+ 'v_texCoord.y': 0.4,
85
+ });
86
+
87
+ expect(displacedY).toBeCloseTo(0.3, 10);
88
+ });
89
+
90
+ it('passes the descriptor and source dimensions through to the shader uniforms', () => {
91
+ apply({ frequency: 7, intensity: 3, seed: 0.25 });
92
+
93
+ expect(uniformValue('u_intensity')).toBe(3);
94
+ expect(uniformValue('u_frequency')).toBe(7);
95
+ expect(uniformValue('u_seed')).toBeCloseTo(0.25, 6);
96
+ expect(glMock.uniform2f).toHaveBeenCalledWith('u_resolution', SOURCE_WIDTH, SOURCE_HEIGHT);
97
+ });
98
+
99
+ it('uses the documented displacement defaults', () => {
100
+ apply();
101
+
102
+ expect(uniformValue('u_intensity')).toBe(8);
103
+ expect(uniformValue('u_frequency')).toBe(12);
104
+ expect(uniformValue('u_seed')).toBe(0);
10
105
  });
11
106
  });
12
107
 
13
108
  describe('defaultGlDisplacementEffectRunner', () => {
14
- it('is a function', () => {
15
- expect(typeof defaultGlDisplacementEffectRunner).toBe('function');
109
+ it('routes the runner context through to the pass', () => {
110
+ const target = { height: 8, texture: {}, width: 8 } as unknown as GlRenderTarget;
111
+
112
+ defaultGlDisplacementEffectRunner(
113
+ { dest: target, pool: { free: [], inUse: [] }, source: target, state: { gl: {} } } as never,
114
+ { intensity: 4, kind: 'DisplacementEffect' } as DisplacementEffect,
115
+ );
116
+
117
+ expect(uniformValue('u_intensity')).toBe(4);
16
118
  });
17
119
  });
18
120
 
19
121
  describe('registerGlDisplacementEffect', () => {
20
- it('is a separately importable registration primitive', () => {
21
- expect(registerGlDisplacementEffect).toBeTypeOf('function');
122
+ it('makes the runner resolvable for the DisplacementEffect kind', () => {
123
+ const state = createGlRenderState(document.createElement('canvas'));
124
+
125
+ expect(getGlRenderEffectRunner(state, 'DisplacementEffect')).toBeNull();
126
+ registerGlDisplacementEffect(state);
127
+ expect(getGlRenderEffectRunner(state, 'DisplacementEffect')).toBe(defaultGlDisplacementEffectRunner);
22
128
  });
23
129
  });
@@ -1,23 +1,107 @@
1
+ import { createGlRenderState } from '@flighthq/render-gl/contract';
2
+ import type { GlRenderState, GlRenderTarget, ScanlinesEffect } from '@flighthq/types/contract';
3
+
4
+ const programMock = vi.hoisted(() => ({
5
+ getGlEffectProgram: vi.fn((_state: unknown, _key: string, _source: string) => ({ program: {} })),
6
+ }));
7
+ const glMock = vi.hoisted(() => ({
8
+ uniform1f: vi.fn((_location: unknown, _value: number) => {}),
9
+ }));
10
+
11
+ vi.mock('./glEffectProgramCache', () => programMock);
12
+
13
+ vi.mock('@flighthq/render-gl/contract', async () => {
14
+ const actual = (await vi.importActual('@flighthq/render-gl/contract')) as Record<string, unknown>;
15
+ return {
16
+ ...actual,
17
+ drawGlFullscreenPass: vi.fn((_state, _program, _textures, _dest, setUniforms) => {
18
+ setUniforms({ ...glMock, getUniformLocation: (_program: unknown, name: string) => name }, { program: {} });
19
+ }),
20
+ };
21
+ });
22
+
23
+ import { getGlRenderEffectRunner } from './glRenderEffectRegistry';
1
24
  import {
2
25
  applyScanlinesEffectToGl,
3
26
  defaultGlScanlinesEffectRunner,
4
27
  registerGlScanlinesEffect,
5
28
  } from './glScanlinesEffect';
29
+ import { evaluateGlslScalarExpression, extractGlslExpression } from './glShaderTestHelper';
30
+
31
+ beforeEach(() => {
32
+ programMock.getGlEffectProgram.mockClear();
33
+ glMock.uniform1f.mockClear();
34
+ });
35
+
36
+ function apply(effect: Readonly<Partial<ScanlinesEffect>> = {}): void {
37
+ const target = { height: 60, texture: {}, width: 80 } as unknown as GlRenderTarget;
38
+ applyScanlinesEffectToGl({ gl: {} } as unknown as GlRenderState, target, target, {
39
+ kind: 'ScanlinesEffect',
40
+ ...effect,
41
+ } as ScanlinesEffect);
42
+ }
43
+
44
+ function lineAtImageY(imageY: number, count: number): number {
45
+ apply();
46
+ const source = programMock.getGlEffectProgram.mock.calls[0]![2] as string;
47
+ const expression = extractGlslExpression(source, /float line = ([^;]+);/);
48
+ return evaluateGlslScalarExpression(expression, {
49
+ 'v_texCoord.y': 1 - imageY,
50
+ u_count: count,
51
+ });
52
+ }
53
+
54
+ function uniformValue(name: string): number {
55
+ const call = glMock.uniform1f.mock.calls.find((entry) => entry[0] === name);
56
+ if (call === undefined) throw new Error(`no uniform1f call for ${name}`);
57
+ return call[1] as number;
58
+ }
6
59
 
7
60
  describe('applyScanlinesEffectToGl', () => {
8
- it('is a function', () => {
9
- expect(typeof applyScanlinesEffectToGl).toBe('function');
61
+ // The shader runs against bottom-left texcoords while the effect is authored in top-left image space.
62
+ // A non-integral count makes the two rows observably different; an integral count can put both on the
63
+ // same sine phase and let the origin conversion disappear behind a passing assertion.
64
+ it('anchors the pattern to top-left image rows rather than bottom-left texcoords', () => {
65
+ const count = 2.5;
66
+
67
+ expect(lineAtImageY(0.2, count)).toBeCloseTo(1, 6);
68
+ expect(lineAtImageY(0.8, count)).toBeCloseTo(0.5, 6);
69
+ });
70
+
71
+ it('passes the descriptor values through to the shader uniforms', () => {
72
+ apply({ count: 17, intensity: 0.45 });
73
+
74
+ expect(uniformValue('u_count')).toBe(17);
75
+ expect(uniformValue('u_intensity')).toBeCloseTo(0.45, 6);
76
+ });
77
+
78
+ it('uses the documented scanline defaults', () => {
79
+ apply();
80
+
81
+ expect(uniformValue('u_count')).toBe(240);
82
+ expect(uniformValue('u_intensity')).toBeCloseTo(0.3, 6);
10
83
  });
11
84
  });
12
85
 
13
86
  describe('defaultGlScanlinesEffectRunner', () => {
14
- it('is a function', () => {
15
- expect(typeof defaultGlScanlinesEffectRunner).toBe('function');
87
+ it('routes the runner context through to the pass', () => {
88
+ const target = { height: 8, texture: {}, width: 8 } as unknown as GlRenderTarget;
89
+
90
+ defaultGlScanlinesEffectRunner(
91
+ { dest: target, pool: { free: [], inUse: [] }, source: target, state: { gl: {} } } as never,
92
+ { count: 13, kind: 'ScanlinesEffect' } as ScanlinesEffect,
93
+ );
94
+
95
+ expect(uniformValue('u_count')).toBe(13);
16
96
  });
17
97
  });
18
98
 
19
99
  describe('registerGlScanlinesEffect', () => {
20
- it('is a separately importable registration primitive', () => {
21
- expect(registerGlScanlinesEffect).toBeTypeOf('function');
100
+ it('makes the runner resolvable for the ScanlinesEffect kind', () => {
101
+ const state = createGlRenderState(document.createElement('canvas'));
102
+
103
+ expect(getGlRenderEffectRunner(state, 'ScanlinesEffect')).toBeNull();
104
+ registerGlScanlinesEffect(state);
105
+ expect(getGlRenderEffectRunner(state, 'ScanlinesEffect')).toBe(defaultGlScanlinesEffectRunner);
22
106
  });
23
107
  });