@flighthq/render 0.1.0
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 +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/renderAppearance.d.ts +3 -0
- package/dist/renderAppearance.d.ts.map +1 -0
- package/dist/renderAppearance.js +35 -0
- package/dist/renderAppearance.js.map +1 -0
- package/dist/renderCache.d.ts +36 -0
- package/dist/renderCache.d.ts.map +1 -0
- package/dist/renderCache.js +83 -0
- package/dist/renderCache.js.map +1 -0
- package/dist/renderColor.d.ts +3 -0
- package/dist/renderColor.d.ts.map +1 -0
- package/dist/renderColor.js +17 -0
- package/dist/renderColor.js.map +1 -0
- package/dist/renderMaterial.d.ts +3 -0
- package/dist/renderMaterial.d.ts.map +1 -0
- package/dist/renderMaterial.js +9 -0
- package/dist/renderMaterial.js.map +1 -0
- package/dist/renderProxy.d.ts +19 -0
- package/dist/renderProxy.d.ts.map +1 -0
- package/dist/renderProxy.js +203 -0
- package/dist/renderProxy.js.map +1 -0
- package/dist/renderProxyAdapter.d.ts +7 -0
- package/dist/renderProxyAdapter.d.ts.map +1 -0
- package/dist/renderProxyAdapter.js +32 -0
- package/dist/renderProxyAdapter.js.map +1 -0
- package/dist/renderQueue.d.ts +9 -0
- package/dist/renderQueue.d.ts.map +1 -0
- package/dist/renderQueue.js +97 -0
- package/dist/renderQueue.js.map +1 -0
- package/dist/renderState.d.ts +5 -0
- package/dist/renderState.d.ts.map +1 -0
- package/dist/renderState.js +42 -0
- package/dist/renderState.js.map +1 -0
- package/dist/renderTarget.d.ts +21 -0
- package/dist/renderTarget.d.ts.map +1 -0
- package/dist/renderTarget.js +39 -0
- package/dist/renderTarget.js.map +1 -0
- package/dist/renderTransform2d.d.ts +3 -0
- package/dist/renderTransform2d.d.ts.map +1 -0
- package/dist/renderTransform2d.js +26 -0
- package/dist/renderTransform2d.js.map +1 -0
- package/dist/renderViewport.d.ts +6 -0
- package/dist/renderViewport.d.ts.map +1 -0
- package/dist/renderViewport.js +57 -0
- package/dist/renderViewport.js.map +1 -0
- package/dist/renderer.d.ts +6 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +24 -0
- package/dist/renderer.js.map +1 -0
- package/dist/sceneRender.d.ts +4 -0
- package/dist/sceneRender.d.ts.map +1 -0
- package/dist/sceneRender.js +175 -0
- package/dist/sceneRender.js.map +1 -0
- package/package.json +48 -0
- package/src/renderAppearance.test.ts +109 -0
- package/src/renderCache.test.ts +151 -0
- package/src/renderColor.test.ts +10 -0
- package/src/renderMaterial.test.ts +30 -0
- package/src/renderProxy.test.ts +531 -0
- package/src/renderProxyAdapter.test.ts +101 -0
- package/src/renderQueue.test.ts +210 -0
- package/src/renderState.test.ts +93 -0
- package/src/renderTarget.test.ts +84 -0
- package/src/renderTransform2d.test.ts +59 -0
- package/src/renderViewport.test.ts +187 -0
- package/src/renderer.test.ts +128 -0
- package/src/sceneRender.test.ts +207 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/render",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/entity": "0.1.0",
|
|
31
|
+
"@flighthq/geometry": "0.1.0",
|
|
32
|
+
"@flighthq/materials": "0.1.0",
|
|
33
|
+
"@flighthq/node": "0.1.0",
|
|
34
|
+
"@flighthq/signals": "0.1.0",
|
|
35
|
+
"@flighthq/types": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@flighthq/camera": "*",
|
|
39
|
+
"@flighthq/displayobject": "*",
|
|
40
|
+
"@flighthq/lighting": "*",
|
|
41
|
+
"@flighthq/mesh": "*",
|
|
42
|
+
"@flighthq/scene": "*",
|
|
43
|
+
"@flighthq/sprite": "*",
|
|
44
|
+
"typescript": "^5.3.0"
|
|
45
|
+
},
|
|
46
|
+
"description": "Renderer registration, render state, render queue, and backend draw contracts",
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createDisplayObject } from '@flighthq/displayobject';
|
|
2
|
+
import { invalidateNodeAppearance } from '@flighthq/node';
|
|
3
|
+
import { BlendMode } from '@flighthq/types';
|
|
4
|
+
|
|
5
|
+
import { updateRenderProxyAppearance } from './renderAppearance';
|
|
6
|
+
import { createRenderProxy } from './renderProxy';
|
|
7
|
+
import { createRenderState, getRenderStateRuntime } from './renderState';
|
|
8
|
+
|
|
9
|
+
describe('updateRenderProxyAppearance', () => {
|
|
10
|
+
it('returns true on first call (lastAppearanceId starts at -1)', () => {
|
|
11
|
+
const state = createRenderState();
|
|
12
|
+
const obj = createDisplayObject();
|
|
13
|
+
const data = createRenderProxy(state, obj);
|
|
14
|
+
expect(updateRenderProxyAppearance(state, data)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns false when appearance has not changed since last call', () => {
|
|
18
|
+
const state = createRenderState();
|
|
19
|
+
const obj = createDisplayObject();
|
|
20
|
+
const data = createRenderProxy(state, obj);
|
|
21
|
+
updateRenderProxyAppearance(state, data);
|
|
22
|
+
expect(updateRenderProxyAppearance(state, data)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns true after invalidateNodeAppearance is called', () => {
|
|
26
|
+
const state = createRenderState();
|
|
27
|
+
const obj = createDisplayObject();
|
|
28
|
+
const data = createRenderProxy(state, obj);
|
|
29
|
+
updateRenderProxyAppearance(state, data);
|
|
30
|
+
invalidateNodeAppearance(obj);
|
|
31
|
+
expect(updateRenderProxyAppearance(state, data)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns true when parent appearanceFrameId matches currentFrameId', () => {
|
|
35
|
+
const state = createRenderState();
|
|
36
|
+
getRenderStateRuntime(state).currentFrameId = 5;
|
|
37
|
+
const obj = createDisplayObject();
|
|
38
|
+
const parentObj = createDisplayObject();
|
|
39
|
+
const data = createRenderProxy(state, obj);
|
|
40
|
+
const parentData = createRenderProxy(state, parentObj);
|
|
41
|
+
updateRenderProxyAppearance(state, data);
|
|
42
|
+
parentData.appearanceFrameId = getRenderStateRuntime(state).currentFrameId;
|
|
43
|
+
expect(updateRenderProxyAppearance(state, data, parentData)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('propagates parent visibility: invisible parent makes child invisible', () => {
|
|
47
|
+
const state = createRenderState();
|
|
48
|
+
getRenderStateRuntime(state).currentFrameId = 1;
|
|
49
|
+
const obj = createDisplayObject();
|
|
50
|
+
obj.visible = true;
|
|
51
|
+
const parentObj = createDisplayObject();
|
|
52
|
+
const data = createRenderProxy(state, obj);
|
|
53
|
+
const parentData = createRenderProxy(state, parentObj);
|
|
54
|
+
parentData.visible = false;
|
|
55
|
+
parentData.appearanceFrameId = getRenderStateRuntime(state).currentFrameId;
|
|
56
|
+
updateRenderProxyAppearance(state, data, parentData);
|
|
57
|
+
expect(data.visible).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('multiplies alpha with parent alpha', () => {
|
|
61
|
+
const state = createRenderState();
|
|
62
|
+
getRenderStateRuntime(state).currentFrameId = 1;
|
|
63
|
+
const obj = createDisplayObject();
|
|
64
|
+
obj.alpha = 0.5;
|
|
65
|
+
const parentObj = createDisplayObject();
|
|
66
|
+
const data = createRenderProxy(state, obj);
|
|
67
|
+
const parentData = createRenderProxy(state, parentObj);
|
|
68
|
+
parentData.visible = true;
|
|
69
|
+
parentData.alpha = 0.4;
|
|
70
|
+
parentData.appearanceFrameId = getRenderStateRuntime(state).currentFrameId;
|
|
71
|
+
updateRenderProxyAppearance(state, data, parentData);
|
|
72
|
+
expect(data.alpha).toBeCloseTo(0.2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('uses state renderAlpha when no parent', () => {
|
|
76
|
+
const state = createRenderState();
|
|
77
|
+
state.renderAlpha = 0.5;
|
|
78
|
+
const obj = createDisplayObject();
|
|
79
|
+
obj.alpha = 0.6;
|
|
80
|
+
const data = createRenderProxy(state, obj);
|
|
81
|
+
updateRenderProxyAppearance(state, data);
|
|
82
|
+
expect(data.alpha).toBeCloseTo(0.3);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('sets appearanceFrameId to currentFrameId after update', () => {
|
|
86
|
+
const state = createRenderState();
|
|
87
|
+
getRenderStateRuntime(state).currentFrameId = 7;
|
|
88
|
+
const obj = createDisplayObject();
|
|
89
|
+
const data = createRenderProxy(state, obj);
|
|
90
|
+
updateRenderProxyAppearance(state, data);
|
|
91
|
+
expect(data.appearanceFrameId).toBe(7);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("uses the node's own blend mode and does not inherit from the parent", () => {
|
|
95
|
+
const state = createRenderState();
|
|
96
|
+
getRenderStateRuntime(state).currentFrameId = 1;
|
|
97
|
+
const obj = createDisplayObject();
|
|
98
|
+
obj.blendMode = BlendMode.Screen;
|
|
99
|
+
const parentObj = createDisplayObject();
|
|
100
|
+
const data = createRenderProxy(state, obj);
|
|
101
|
+
const parentData = createRenderProxy(state, parentObj);
|
|
102
|
+
parentData.blendMode = BlendMode.Multiply;
|
|
103
|
+
parentData.visible = true;
|
|
104
|
+
parentData.alpha = 1;
|
|
105
|
+
parentData.appearanceFrameId = getRenderStateRuntime(state).currentFrameId;
|
|
106
|
+
updateRenderProxyAppearance(state, data, parentData);
|
|
107
|
+
expect(data.blendMode).toBe(BlendMode.Screen);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createDisplayObject } from '@flighthq/displayobject';
|
|
2
|
+
import { connectSignal } from '@flighthq/signals';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createRenderCache,
|
|
6
|
+
createRenderCacheAdapter,
|
|
7
|
+
enableRenderCacheAdapterSignals,
|
|
8
|
+
getRenderProxyCache,
|
|
9
|
+
isRenderCache,
|
|
10
|
+
isRenderCacheAdapter,
|
|
11
|
+
registerRenderCacheRenderer,
|
|
12
|
+
RenderCacheKind,
|
|
13
|
+
useRenderCache,
|
|
14
|
+
} from './renderCache';
|
|
15
|
+
import { createRenderProxy2D } from './renderProxy';
|
|
16
|
+
import { getRenderProxyAdapter } from './renderProxyAdapter';
|
|
17
|
+
import { createRenderState, getRenderStateRuntime } from './renderState';
|
|
18
|
+
|
|
19
|
+
describe('createRenderCache', () => {
|
|
20
|
+
it('creates a handle with the render cache kind and a transform', () => {
|
|
21
|
+
const cache = createRenderCache();
|
|
22
|
+
expect(cache.kind).toBe(RenderCacheKind);
|
|
23
|
+
expect(cache.transform).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('createRenderCacheAdapter', () => {
|
|
28
|
+
it('adapt returns null and renders normally when no cache is attached', () => {
|
|
29
|
+
const state = createRenderState();
|
|
30
|
+
const obj = createDisplayObject();
|
|
31
|
+
const data = createRenderProxy2D(state, obj);
|
|
32
|
+
const adapter = createRenderCacheAdapter();
|
|
33
|
+
expect(adapter.adapt(state, obj, data)).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('switches the render node to the cache kind and stops traversal, keeping the source node', () => {
|
|
37
|
+
const state = createRenderState();
|
|
38
|
+
const obj = createDisplayObject();
|
|
39
|
+
const data = createRenderProxy2D(state, obj);
|
|
40
|
+
const cache = createRenderCache();
|
|
41
|
+
cache.transform.tx = 7;
|
|
42
|
+
const adapter = createRenderCacheAdapter(cache);
|
|
43
|
+
const result = adapter.adapt(state, obj, data);
|
|
44
|
+
expect(result).toBe(false);
|
|
45
|
+
expect(data.kind).toBe(RenderCacheKind);
|
|
46
|
+
// Source stays the original node so the appearance/transform passes keep working each frame.
|
|
47
|
+
expect(data.source).toBe(obj);
|
|
48
|
+
expect(data.transform2D.tx).toBe(7);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('emits onPrepare when signals are enabled', () => {
|
|
52
|
+
const state = createRenderState();
|
|
53
|
+
const obj = createDisplayObject();
|
|
54
|
+
const data = createRenderProxy2D(state, obj);
|
|
55
|
+
const adapter = createRenderCacheAdapter();
|
|
56
|
+
enableRenderCacheAdapterSignals(adapter);
|
|
57
|
+
const listener = vi.fn();
|
|
58
|
+
connectSignal(adapter.signals!.onPrepare, listener);
|
|
59
|
+
adapter.adapt(state, obj, data);
|
|
60
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('enableRenderCacheAdapterSignals', () => {
|
|
65
|
+
it('allocates the onPrepare signal', () => {
|
|
66
|
+
const adapter = createRenderCacheAdapter();
|
|
67
|
+
expect(adapter.signals).toBeNull();
|
|
68
|
+
enableRenderCacheAdapterSignals(adapter);
|
|
69
|
+
expect(adapter.signals).not.toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('is idempotent', () => {
|
|
73
|
+
const adapter = createRenderCacheAdapter();
|
|
74
|
+
enableRenderCacheAdapterSignals(adapter);
|
|
75
|
+
const signals = adapter.signals;
|
|
76
|
+
enableRenderCacheAdapterSignals(adapter);
|
|
77
|
+
expect(adapter.signals).toBe(signals);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('getRenderProxyCache', () => {
|
|
82
|
+
it('returns the cache attached to a source, or null when none', () => {
|
|
83
|
+
const state = createRenderState();
|
|
84
|
+
const obj = createDisplayObject();
|
|
85
|
+
expect(getRenderProxyCache(state, obj as any)).toBeNull();
|
|
86
|
+
const cache = createRenderCache();
|
|
87
|
+
useRenderCache(state, obj as any, cache);
|
|
88
|
+
expect(getRenderProxyCache(state, obj as any)).toBe(cache);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('isRenderCache', () => {
|
|
93
|
+
it('returns true for a render cache handle', () => {
|
|
94
|
+
expect(isRenderCache(createRenderCache())).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns false for other kinds and null', () => {
|
|
98
|
+
expect(isRenderCache({ kind: 'other' })).toBe(false);
|
|
99
|
+
expect(isRenderCache(null)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('isRenderCacheAdapter', () => {
|
|
104
|
+
it('returns true for a render cache adapter', () => {
|
|
105
|
+
expect(isRenderCacheAdapter(createRenderCacheAdapter())).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns false for objects missing adapt or cache, and null', () => {
|
|
109
|
+
expect(isRenderCacheAdapter({ cache: null })).toBe(false);
|
|
110
|
+
expect(isRenderCacheAdapter({ adapt: () => null })).toBe(false);
|
|
111
|
+
expect(isRenderCacheAdapter(null)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('registerRenderCacheRenderer', () => {
|
|
116
|
+
it('registers the renderer for the render cache kind', () => {
|
|
117
|
+
const state = createRenderState();
|
|
118
|
+
const renderer = { createData: () => null, submit: vi.fn() };
|
|
119
|
+
registerRenderCacheRenderer(state, renderer as any);
|
|
120
|
+
expect(getRenderStateRuntime(state).rendererMap.get(RenderCacheKind)).toBe(renderer);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('useRenderCache', () => {
|
|
125
|
+
it('attaches a cache adapter to the source on the state', () => {
|
|
126
|
+
const state = createRenderState();
|
|
127
|
+
const obj = createDisplayObject();
|
|
128
|
+
const cache = createRenderCache();
|
|
129
|
+
const adapter = useRenderCache(state, obj as any, cache);
|
|
130
|
+
expect(getRenderProxyAdapter(state, obj as any)).toBe(adapter);
|
|
131
|
+
expect(adapter.cache).toBe(cache);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('reuses an existing cache adapter and swaps the handle', () => {
|
|
135
|
+
const state = createRenderState();
|
|
136
|
+
const obj = createDisplayObject();
|
|
137
|
+
const first = useRenderCache(state, obj as any, createRenderCache());
|
|
138
|
+
const second = createRenderCache();
|
|
139
|
+
const adapter = useRenderCache(state, obj as any, second);
|
|
140
|
+
expect(adapter).toBe(first);
|
|
141
|
+
expect(adapter.cache).toBe(second);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('isolates attachment between render states', () => {
|
|
145
|
+
const stateA = createRenderState();
|
|
146
|
+
const stateB = createRenderState();
|
|
147
|
+
const obj = createDisplayObject();
|
|
148
|
+
useRenderCache(stateA, obj as any, createRenderCache());
|
|
149
|
+
expect(getRenderProxyAdapter(stateB, obj as any)).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { setRenderStateBackgroundColor } from './renderColor';
|
|
2
|
+
import { createRenderState } from './renderState';
|
|
3
|
+
|
|
4
|
+
describe('setRenderStateBackgroundColor', () => {
|
|
5
|
+
it('sets the background color on the render state', () => {
|
|
6
|
+
const state = createRenderState();
|
|
7
|
+
setRenderStateBackgroundColor(state, 0xff0000ff);
|
|
8
|
+
expect(state.backgroundColor).toBe(0xff0000ff);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createMaterial } from '@flighthq/materials';
|
|
2
|
+
import type { Renderable } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { updateRenderProxyMaterial } from './renderMaterial';
|
|
5
|
+
import { createRenderProxy } from './renderProxy';
|
|
6
|
+
import { createRenderState } from './renderState';
|
|
7
|
+
|
|
8
|
+
const TestKind = 'Test';
|
|
9
|
+
|
|
10
|
+
describe('updateRenderProxyMaterial', () => {
|
|
11
|
+
it('resolves the source material and material data onto the render node', () => {
|
|
12
|
+
const state = createRenderState();
|
|
13
|
+
const material = createMaterial(TestKind);
|
|
14
|
+
const materialData = {};
|
|
15
|
+
const source = { kind: TestKind, material, materialData } as unknown as Renderable;
|
|
16
|
+
const data = createRenderProxy(state, source);
|
|
17
|
+
updateRenderProxyMaterial(state, data);
|
|
18
|
+
expect(data.material).toBe(material);
|
|
19
|
+
expect(data.materialData).toBe(materialData);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('resolves to null when the source has no material', () => {
|
|
23
|
+
const state = createRenderState();
|
|
24
|
+
const source = { kind: TestKind } as unknown as Renderable;
|
|
25
|
+
const data = createRenderProxy(state, source);
|
|
26
|
+
updateRenderProxyMaterial(state, data);
|
|
27
|
+
expect(data.material).toBeNull();
|
|
28
|
+
expect(data.materialData).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
});
|