@flighthq/render-wgpu 0.2.1-next.666.4af7f8b → 0.2.1-next.738.07b221d
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/wgpuBackground.d.ts +8 -0
- package/dist/wgpuBackground.d.ts.map +1 -1
- package/dist/wgpuBackground.js +27 -11
- package/dist/wgpuBackground.js.map +1 -1
- package/dist/wgpuCompressedTexture.d.ts +8 -0
- package/dist/wgpuCompressedTexture.d.ts.map +1 -0
- package/dist/wgpuCompressedTexture.js +181 -0
- package/dist/wgpuCompressedTexture.js.map +1 -0
- package/dist/wgpuDraw.d.ts +3 -1
- package/dist/wgpuDraw.d.ts.map +1 -1
- package/dist/wgpuDraw.js +78 -4
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +23 -2
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuShader.d.ts +2 -1
- package/dist/wgpuShader.d.ts.map +1 -1
- package/dist/wgpuShader.js +27 -15
- package/dist/wgpuShader.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTestHelper.js +2 -0
- package/dist/wgpuTestHelper.js.map +1 -1
- package/package.json +6 -6
- package/src/wgpuBackground.test.ts +34 -1
- package/src/wgpuCompressedTexture.test.ts +219 -0
- package/src/wgpuDraw.test.ts +64 -1
- package/src/wgpuRenderState.test.ts +10 -9
- package/src/wgpuShader.test.ts +42 -0
package/src/wgpuDraw.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createBitmap } from '@flighthq/displayobject';
|
|
2
2
|
import { getOrCreateRenderProxy2D, prepareDisplayObjectRender } from '@flighthq/render';
|
|
3
|
-
import type { ImageResource } from '@flighthq/types';
|
|
3
|
+
import type { ImageResource, VideoTexture } from '@flighthq/types';
|
|
4
4
|
import { BlendMode } from '@flighthq/types';
|
|
5
5
|
|
|
6
6
|
import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
|
|
@@ -8,10 +8,12 @@ import {
|
|
|
8
8
|
applyWgpuBlendMode,
|
|
9
9
|
bindWgpuImageResourceTexture,
|
|
10
10
|
bindWgpuTexture,
|
|
11
|
+
bindWgpuVideoTexture,
|
|
11
12
|
buildWgpuRenderTargetBindGroup,
|
|
12
13
|
createWgpuTextureEntry,
|
|
13
14
|
drawWgpuQuad,
|
|
14
15
|
drawWgpuQuadWithTransform,
|
|
16
|
+
destroyWgpuVideoTexture,
|
|
15
17
|
enableWgpuBlendModeSupport,
|
|
16
18
|
getWgpuRenderProxyColorTransform,
|
|
17
19
|
submitWgpuQuadDraw,
|
|
@@ -125,6 +127,61 @@ describe('bindWgpuTexture', () => {
|
|
|
125
127
|
});
|
|
126
128
|
});
|
|
127
129
|
|
|
130
|
+
describe('bindWgpuVideoTexture', () => {
|
|
131
|
+
function videoTexture(frameId: number, readyState = 4, width = 320, height = 240): VideoTexture {
|
|
132
|
+
return {
|
|
133
|
+
frameId,
|
|
134
|
+
sampler: {
|
|
135
|
+
anisotropy: 1,
|
|
136
|
+
magFilter: 'linear',
|
|
137
|
+
minFilter: 'linear',
|
|
138
|
+
mipmaps: false,
|
|
139
|
+
wrapU: 'clamp-to-edge',
|
|
140
|
+
wrapV: 'clamp-to-edge',
|
|
141
|
+
},
|
|
142
|
+
source: { element: { readyState, videoHeight: height, videoWidth: width } as HTMLVideoElement },
|
|
143
|
+
} as unknown as VideoTexture;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
it('uploads once per frameId and caches the texture by VideoTexture identity', async () => {
|
|
147
|
+
const state = await createWgpuRenderStateForTest();
|
|
148
|
+
const copy = vi.spyOn(state.device.queue, 'copyExternalImageToTexture');
|
|
149
|
+
const video = videoTexture(1);
|
|
150
|
+
const first = bindWgpuVideoTexture(state, video);
|
|
151
|
+
expect(first).not.toBeNull();
|
|
152
|
+
expect(copy).toHaveBeenCalledTimes(1);
|
|
153
|
+
expect(bindWgpuVideoTexture(state, video)).toBe(first);
|
|
154
|
+
expect(copy).toHaveBeenCalledTimes(1);
|
|
155
|
+
video.frameId = 2;
|
|
156
|
+
expect(bindWgpuVideoTexture(state, video)).toBe(first);
|
|
157
|
+
expect(copy).toHaveBeenCalledTimes(2);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('waits for a decoded frame and recreates the texture after a resolution change', async () => {
|
|
161
|
+
const state = await createWgpuRenderStateForTest();
|
|
162
|
+
const video = videoTexture(1, 1, 0, 0);
|
|
163
|
+
expect(bindWgpuVideoTexture(state, video)).toBeNull();
|
|
164
|
+
const element = video.source.element!;
|
|
165
|
+
Object.assign(element, { readyState: 4, videoHeight: 120, videoWidth: 160 });
|
|
166
|
+
const first = bindWgpuVideoTexture(state, video)!;
|
|
167
|
+
const destroy = vi.spyOn(first.texture, 'destroy');
|
|
168
|
+
Object.assign(element, { videoHeight: 240, videoWidth: 320 });
|
|
169
|
+
const second = bindWgpuVideoTexture(state, video)!;
|
|
170
|
+
expect(second).not.toBe(first);
|
|
171
|
+
expect(destroy).toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('destroys and removes an uploaded video texture', async () => {
|
|
175
|
+
const state = await createWgpuRenderStateForTest();
|
|
176
|
+
const video = videoTexture(1);
|
|
177
|
+
const entry = bindWgpuVideoTexture(state, video)!;
|
|
178
|
+
const destroy = vi.spyOn(entry.texture, 'destroy');
|
|
179
|
+
expect(destroyWgpuVideoTexture(state, video)).toBe(true);
|
|
180
|
+
expect(destroy).toHaveBeenCalled();
|
|
181
|
+
expect(destroyWgpuVideoTexture(state, video)).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
128
185
|
describe('buildWgpuRenderTargetBindGroup', () => {
|
|
129
186
|
it('returns a bind group for a view', async () => {
|
|
130
187
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -146,6 +203,12 @@ describe('createWgpuTextureEntry', () => {
|
|
|
146
203
|
});
|
|
147
204
|
});
|
|
148
205
|
|
|
206
|
+
describe('destroyWgpuVideoTexture', () => {
|
|
207
|
+
it('is the public disposal entry point for dynamic video uploads', () => {
|
|
208
|
+
expect(typeof destroyWgpuVideoTexture).toBe('function');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
149
212
|
describe('drawWgpuQuad', () => {
|
|
150
213
|
it('does not throw when render pass is open', async () => {
|
|
151
214
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -83,12 +83,14 @@ describe('getWgpuRenderStateRuntime', () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
describe('getWgpuSampler', () => {
|
|
86
|
-
it('caches a sampler per filter+wrap+mip+anisotropy
|
|
86
|
+
it('caches a sampler per filter+wrap+mip+anisotropy config and reuses it (one createSampler)', async () => {
|
|
87
87
|
const state = await createWgpuRenderStateForTest();
|
|
88
88
|
const a = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
89
89
|
const b = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
90
90
|
expect(a).toBe(b);
|
|
91
|
-
|
|
91
|
+
// The cache is keyed by a packed NUMBER (no per-call string allocation), and one config caches once.
|
|
92
|
+
expect([...getWgpuRenderStateRuntime(state).samplerCache.keys()].every((k) => typeof k === 'number')).toBe(true);
|
|
93
|
+
expect(getWgpuRenderStateRuntime(state).samplerCache.size).toBe(1);
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
it('returns a distinct sampler for a different wrap or filter', async () => {
|
|
@@ -105,17 +107,16 @@ describe('getWgpuSampler', () => {
|
|
|
105
107
|
const noMip = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
106
108
|
const trilinear = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear');
|
|
107
109
|
expect(noMip).not.toBe(trilinear);
|
|
108
|
-
expect(getWgpuRenderStateRuntime(state).samplerCache.
|
|
110
|
+
expect(getWgpuRenderStateRuntime(state).samplerCache.size).toBe(2);
|
|
109
111
|
});
|
|
110
112
|
|
|
111
113
|
it('forces linear filtering and a linear mip filter when anisotropy exceeds 1', async () => {
|
|
112
114
|
// WebGPU rejects maxAnisotropy > 1 unless min/mag/mip are all linear, so a nearest+aniso request
|
|
113
|
-
// collapses to the linear anisotropic
|
|
115
|
+
// collapses to the SAME sampler as the explicit linear/trilinear anisotropic request.
|
|
114
116
|
const state = await createWgpuRenderStateForTest();
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
expect(
|
|
118
|
-
expect(cache.has('nearest|clamp-to-edge|clamp-to-edge|none|8')).toBe(false);
|
|
117
|
+
const collapsed = getWgpuSampler(state, 'nearest', 'clamp-to-edge', 'clamp-to-edge', undefined, 8);
|
|
118
|
+
const explicit = getWgpuSampler(state, 'linear', 'clamp-to-edge', 'clamp-to-edge', 'linear', 8);
|
|
119
|
+
expect(collapsed).toBe(explicit);
|
|
119
120
|
});
|
|
120
121
|
|
|
121
122
|
it('floors and clamps the anisotropy level into the cache key', async () => {
|
|
@@ -123,7 +124,7 @@ describe('getWgpuSampler', () => {
|
|
|
123
124
|
const a = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear', 4.9);
|
|
124
125
|
const b = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear', 4);
|
|
125
126
|
expect(a).toBe(b);
|
|
126
|
-
expect(getWgpuRenderStateRuntime(state).samplerCache.
|
|
127
|
+
expect(getWgpuRenderStateRuntime(state).samplerCache.size).toBe(1);
|
|
127
128
|
});
|
|
128
129
|
});
|
|
129
130
|
|
package/src/wgpuShader.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createWgpuBindGroupLayouts,
|
|
7
7
|
createWgpuPipelineLayout,
|
|
8
8
|
getActiveWgpuPipeline,
|
|
9
|
+
getWgpuBlendState,
|
|
9
10
|
getWgpuPipeline,
|
|
10
11
|
setWgpuMatrixFromTransform,
|
|
11
12
|
UNIFORM_BYTE_SIZE,
|
|
@@ -51,6 +52,29 @@ describe('getActiveWgpuPipeline', () => {
|
|
|
51
52
|
});
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
describe('getWgpuBlendState', () => {
|
|
56
|
+
it('resolves all six fixed states and falls back to Normal for null or unknown modes', () => {
|
|
57
|
+
const color = (mode: BlendMode | null) => getWgpuBlendState(mode).color;
|
|
58
|
+
const normal = { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' };
|
|
59
|
+
expect(color(null)).toEqual(normal);
|
|
60
|
+
expect(color(AdvancedBlendMode.Overlay)).toEqual(normal);
|
|
61
|
+
expect(color(BlendMode.Normal)).toEqual(normal);
|
|
62
|
+
expect(color(BlendMode.Add)).toEqual({ srcFactor: 'one', dstFactor: 'one', operation: 'add' });
|
|
63
|
+
expect(color(BlendMode.Multiply)).toEqual({
|
|
64
|
+
srcFactor: 'dst',
|
|
65
|
+
dstFactor: 'one-minus-src-alpha',
|
|
66
|
+
operation: 'add',
|
|
67
|
+
});
|
|
68
|
+
expect(color(BlendMode.Screen)).toEqual({
|
|
69
|
+
srcFactor: 'one',
|
|
70
|
+
dstFactor: 'one-minus-src',
|
|
71
|
+
operation: 'add',
|
|
72
|
+
});
|
|
73
|
+
expect(color(BlendMode.Darken).operation).toBe('min');
|
|
74
|
+
expect(color(BlendMode.Lighten).operation).toBe('max');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
54
78
|
describe('getWgpuPipeline', () => {
|
|
55
79
|
it('returns a cached pipeline on second call', async () => {
|
|
56
80
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -177,4 +201,22 @@ describe('writeWgpuQuadUniforms', () => {
|
|
|
177
201
|
expect(runtime.uniformData[floatBase + 28]).toBeCloseTo(0.1);
|
|
178
202
|
expect(runtime.uniformData[floatBase + 12]).toBeCloseTo(0.5); // alpha
|
|
179
203
|
});
|
|
204
|
+
|
|
205
|
+
it('writes the native straight-alpha texture flag at u32 offset 14', async () => {
|
|
206
|
+
const state = await createWgpuRenderStateForTest();
|
|
207
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
208
|
+
renderWgpuBackground(state);
|
|
209
|
+
const fakeNode = {
|
|
210
|
+
alpha: 1,
|
|
211
|
+
useColorTransform: false,
|
|
212
|
+
colorTransform: null,
|
|
213
|
+
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const straightOffset = writeWgpuQuadUniforms(state, fakeNode, null, 0, 0, 1, 1, 0, 0, 1, 1, true);
|
|
217
|
+
const premultipliedOffset = writeWgpuQuadUniforms(state, fakeNode, null, 0, 0, 1, 1, 0, 0, 1, 1);
|
|
218
|
+
|
|
219
|
+
expect(runtime.uniformDataU32[(straightOffset >> 2) + 14]).toBe(1);
|
|
220
|
+
expect(runtime.uniformDataU32[(premultipliedOffset >> 2) + 14]).toBe(0);
|
|
221
|
+
});
|
|
180
222
|
});
|