@clypra/runtime 1.0.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/.releaserc.json +16 -0
- package/package.json +47 -0
- package/src/__tests__/integration.test.ts +345 -0
- package/src/graph/__tests__/builder.test.ts +204 -0
- package/src/graph/__tests__/validator.test.ts +381 -0
- package/src/graph/builder.ts +263 -0
- package/src/graph/index.ts +14 -0
- package/src/graph/types.ts +176 -0
- package/src/graph/validator.ts +208 -0
- package/src/index.ts +28 -0
- package/src/pixi/filters.ts +98 -0
- package/src/pixi/index.ts +11 -0
- package/src/pixi/renderer.ts +375 -0
- package/src/pixi/texture-pool.ts +159 -0
- package/src/pixi/types.ts +58 -0
- package/src/planner/index.ts +10 -0
- package/src/planner/optimizer.ts +247 -0
- package/src/planner/planner.ts +201 -0
- package/src/planner/types.ts +56 -0
- package/src/resources/cache.ts +166 -0
- package/src/resources/index.ts +9 -0
- package/src/resources/manager.ts +184 -0
- package/src/resources/types.ts +29 -0
- package/src/testing/benchmarkRunner.ts +399 -0
- package/src/testing/goldenTests.ts +390 -0
- package/src/validation/effectValidator.ts +571 -0
- package/src/validation/index.ts +9 -0
- package/src/validation/resource-validator.ts +173 -0
- package/src/validation/shader-validator.ts +154 -0
- package/src/validation/types.ts +31 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +18 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Pixi Renderer
|
|
3
|
+
*
|
|
4
|
+
* Main renderer that executes frame graphs using Pixi.js.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as PIXI from "pixi.js";
|
|
8
|
+
import type { FrameGraph, RenderPass } from "../planner/types";
|
|
9
|
+
import type { RendererConfig, RenderResult, RenderStats } from "./types";
|
|
10
|
+
import { TexturePool } from "./texture-pool";
|
|
11
|
+
import { createFilter, updateFilterUniforms } from "./filters";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pixi.js Renderer
|
|
15
|
+
*
|
|
16
|
+
* Executes frame graphs and manages GPU resources.
|
|
17
|
+
*/
|
|
18
|
+
export class PixiRenderer {
|
|
19
|
+
private app: PIXI.Application | null = null;
|
|
20
|
+
private initialized = false;
|
|
21
|
+
private texturePool: TexturePool;
|
|
22
|
+
private resources = new Map<string, PIXI.Texture>();
|
|
23
|
+
private filters = new Map<string, PIXI.Filter>();
|
|
24
|
+
private canvasElement?: HTMLCanvasElement;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.texturePool = new TexturePool(20);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the renderer
|
|
32
|
+
*/
|
|
33
|
+
async initialize(config: RendererConfig = {}): Promise<void> {
|
|
34
|
+
if (this.initialized) {
|
|
35
|
+
throw new Error("PixiRenderer already initialized");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.canvasElement = config.canvas;
|
|
39
|
+
|
|
40
|
+
const width = config.width ?? config.canvas?.clientWidth ?? 1920;
|
|
41
|
+
const height = config.height ?? config.canvas?.clientHeight ?? 1080;
|
|
42
|
+
|
|
43
|
+
this.app = new PIXI.Application();
|
|
44
|
+
await this.app.init({
|
|
45
|
+
canvas: config.canvas,
|
|
46
|
+
width,
|
|
47
|
+
height,
|
|
48
|
+
backgroundColor: config.backgroundColor ?? 0x0e0e12,
|
|
49
|
+
antialias: config.antialias ?? true,
|
|
50
|
+
preference: "webgl",
|
|
51
|
+
resolution: config.resolution ?? (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1),
|
|
52
|
+
autoDensity: true,
|
|
53
|
+
preserveDrawingBuffer: config.preserveDrawingBuffer ?? true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.initialized = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Render a frame graph
|
|
61
|
+
*/
|
|
62
|
+
async render(frameGraph: FrameGraph): Promise<RenderResult> {
|
|
63
|
+
if (!this.initialized || !this.app) {
|
|
64
|
+
throw new Error("PixiRenderer not initialized");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const startTime = performance.now();
|
|
68
|
+
|
|
69
|
+
// Allocate resources
|
|
70
|
+
for (const resource of frameGraph.resourceRequests) {
|
|
71
|
+
if (!this.resources.has(resource.id)) {
|
|
72
|
+
this.allocateResource(resource.id, resource.width, resource.height);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Execute passes
|
|
77
|
+
let totalGpuTime = 0;
|
|
78
|
+
for (const pass of frameGraph.passes) {
|
|
79
|
+
const passStart = performance.now();
|
|
80
|
+
await this.executePass(pass);
|
|
81
|
+
totalGpuTime += performance.now() - passStart;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Release transient resources
|
|
85
|
+
for (const resource of frameGraph.resourceRequests) {
|
|
86
|
+
if (resource.transient) {
|
|
87
|
+
this.releaseResource(resource.id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const totalCpuTime = performance.now() - startTime - totalGpuTime;
|
|
92
|
+
|
|
93
|
+
// Get output texture
|
|
94
|
+
const outputTexture = this.resources.get("output") || this.resources.values().next().value;
|
|
95
|
+
|
|
96
|
+
if (!outputTexture) {
|
|
97
|
+
throw new Error("No output texture available");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const stats: RenderStats = {
|
|
101
|
+
passCount: frameGraph.passes.length,
|
|
102
|
+
totalGpuTime,
|
|
103
|
+
totalCpuTime,
|
|
104
|
+
resourceCount: this.resources.size,
|
|
105
|
+
textureMemory: this.calculateTextureMemory(),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
outputTexture,
|
|
110
|
+
stats,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Execute a single render pass
|
|
116
|
+
*/
|
|
117
|
+
private async executePass(pass: RenderPass): Promise<void> {
|
|
118
|
+
if (!this.app) return;
|
|
119
|
+
|
|
120
|
+
// Get input and output textures
|
|
121
|
+
const inputTexture = pass.inputs.length > 0 ? this.resources.get(pass.inputs[0]) : null;
|
|
122
|
+
const outputTexture = this.resources.get(pass.output);
|
|
123
|
+
|
|
124
|
+
if (!outputTexture) {
|
|
125
|
+
throw new Error(`Output resource not found: ${pass.output}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Handle copy/blit passes
|
|
129
|
+
if (pass.shaderId === "copy" || pass.shaderId === "blit" || pass.shaderId === "blit-source") {
|
|
130
|
+
if (inputTexture) {
|
|
131
|
+
this.blitTexture(inputTexture, outputTexture as PIXI.RenderTexture, pass.clearBeforeRender);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!inputTexture) {
|
|
137
|
+
console.warn(`No input texture for pass: ${pass.id}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create sprite with input texture
|
|
142
|
+
const target = outputTexture as PIXI.RenderTexture;
|
|
143
|
+
const sprite = new PIXI.Sprite(inputTexture);
|
|
144
|
+
sprite.width = target.width;
|
|
145
|
+
sprite.height = target.height;
|
|
146
|
+
|
|
147
|
+
// Get or create filter
|
|
148
|
+
let filter: PIXI.Filter;
|
|
149
|
+
let disposeFilter = false;
|
|
150
|
+
|
|
151
|
+
if (this.filters.has(pass.shaderId)) {
|
|
152
|
+
filter = this.filters.get(pass.shaderId)!;
|
|
153
|
+
updateFilterUniforms(filter, pass.uniforms, pass.shaderId);
|
|
154
|
+
} else {
|
|
155
|
+
filter = createFilter(pass.shaderId, pass.uniforms);
|
|
156
|
+
|
|
157
|
+
// Cache simple filters
|
|
158
|
+
if (["brightness", "contrast", "saturation", "copy", "blit"].includes(pass.shaderId)) {
|
|
159
|
+
this.filters.set(pass.shaderId, filter);
|
|
160
|
+
} else {
|
|
161
|
+
disposeFilter = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Apply filter and render
|
|
166
|
+
sprite.filters = [filter];
|
|
167
|
+
this.app.renderer.render({
|
|
168
|
+
container: sprite,
|
|
169
|
+
target,
|
|
170
|
+
clear: pass.clearBeforeRender ?? true,
|
|
171
|
+
});
|
|
172
|
+
sprite.filters = null;
|
|
173
|
+
|
|
174
|
+
// Clean up if needed
|
|
175
|
+
if (disposeFilter) {
|
|
176
|
+
filter.destroy();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Blit texture to another
|
|
182
|
+
*/
|
|
183
|
+
private blitTexture(source: PIXI.Texture, target: PIXI.RenderTexture, clear: boolean = true): void {
|
|
184
|
+
if (!this.app) return;
|
|
185
|
+
|
|
186
|
+
const sprite = new PIXI.Sprite(source);
|
|
187
|
+
sprite.width = target.width;
|
|
188
|
+
sprite.height = target.height;
|
|
189
|
+
|
|
190
|
+
this.app.renderer.render({
|
|
191
|
+
container: sprite,
|
|
192
|
+
target,
|
|
193
|
+
clear,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Allocate a texture resource
|
|
199
|
+
*/
|
|
200
|
+
private allocateResource(id: string, width: number, height: number): void {
|
|
201
|
+
if (this.resources.has(id)) {
|
|
202
|
+
throw new Error(`Resource "${id}" already allocated`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const texture = PIXI.RenderTexture.create({ width, height });
|
|
206
|
+
this.resources.set(id, texture);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Release a texture resource
|
|
211
|
+
*/
|
|
212
|
+
private releaseResource(id: string): void {
|
|
213
|
+
const texture = this.resources.get(id);
|
|
214
|
+
if (!texture) return;
|
|
215
|
+
|
|
216
|
+
// Return to pool if it's a render texture
|
|
217
|
+
if (texture instanceof PIXI.RenderTexture) {
|
|
218
|
+
this.texturePool.release(texture, {
|
|
219
|
+
width: texture.width,
|
|
220
|
+
height: texture.height,
|
|
221
|
+
format: "rgba8",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.resources.delete(id);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Upload source image to resources
|
|
230
|
+
*/
|
|
231
|
+
uploadSourceImage(sourceImage: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement, resourceIds: readonly string[]): void {
|
|
232
|
+
if (!this.app) return;
|
|
233
|
+
|
|
234
|
+
const sourceTexture = PIXI.Texture.from(sourceImage);
|
|
235
|
+
|
|
236
|
+
for (const resourceId of resourceIds) {
|
|
237
|
+
const texture = this.resources.get(resourceId);
|
|
238
|
+
if (!texture || !(texture instanceof PIXI.RenderTexture)) continue;
|
|
239
|
+
|
|
240
|
+
// Contain-fit scaling
|
|
241
|
+
const fitScale = Math.min(texture.width / sourceTexture.width, texture.height / sourceTexture.height);
|
|
242
|
+
|
|
243
|
+
const sprite = new PIXI.Sprite(sourceTexture);
|
|
244
|
+
sprite.scale.set(fitScale);
|
|
245
|
+
sprite.position.set((texture.width - sourceTexture.width * fitScale) / 2, (texture.height - sourceTexture.height * fitScale) / 2);
|
|
246
|
+
|
|
247
|
+
this.app.renderer.render({ container: sprite, target: texture });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Present a resource to the canvas
|
|
253
|
+
*/
|
|
254
|
+
present(resourceId: string): void {
|
|
255
|
+
if (!this.app) return;
|
|
256
|
+
|
|
257
|
+
const texture = this.resources.get(resourceId);
|
|
258
|
+
if (!texture) return;
|
|
259
|
+
|
|
260
|
+
const screenW = this.app.renderer.screen.width;
|
|
261
|
+
const screenH = this.app.renderer.screen.height;
|
|
262
|
+
const scale = Math.min(screenW / texture.width, screenH / texture.height);
|
|
263
|
+
|
|
264
|
+
const sprite = new PIXI.Sprite(texture);
|
|
265
|
+
sprite.scale.set(scale);
|
|
266
|
+
sprite.position.set((screenW - texture.width * scale) / 2, (screenH - texture.height * scale) / 2);
|
|
267
|
+
|
|
268
|
+
this.app.stage.removeChildren();
|
|
269
|
+
this.app.stage.addChild(sprite);
|
|
270
|
+
this.app.renderer.render(this.app.stage);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Read pixels from a resource
|
|
275
|
+
*/
|
|
276
|
+
async readPixels(resourceId: string): Promise<Uint8Array> {
|
|
277
|
+
if (!this.app) {
|
|
278
|
+
throw new Error("PixiRenderer not initialized");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const texture = this.resources.get(resourceId);
|
|
282
|
+
if (!texture) {
|
|
283
|
+
throw new Error(`Resource "${resourceId}" not found`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const pixels = this.app.renderer.extract.pixels(texture);
|
|
287
|
+
return new Uint8Array(pixels.pixels);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get a resource texture
|
|
292
|
+
*/
|
|
293
|
+
getResource(id: string): PIXI.Texture | undefined {
|
|
294
|
+
return this.resources.get(id);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Resize the renderer
|
|
299
|
+
*/
|
|
300
|
+
resize(width: number, height: number): void {
|
|
301
|
+
if (!this.app) return;
|
|
302
|
+
this.app.renderer.resize(width, height);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get logical dimensions
|
|
307
|
+
*/
|
|
308
|
+
get width(): number {
|
|
309
|
+
return this.app?.renderer.width ?? 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get height(): number {
|
|
313
|
+
return this.app?.renderer.height ?? 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Calculate total texture memory
|
|
318
|
+
*/
|
|
319
|
+
private calculateTextureMemory(): number {
|
|
320
|
+
let total = 0;
|
|
321
|
+
for (const texture of this.resources.values()) {
|
|
322
|
+
total += texture.width * texture.height * 4; // RGBA8
|
|
323
|
+
}
|
|
324
|
+
return total;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Clear all resources
|
|
329
|
+
*/
|
|
330
|
+
clearResources(): void {
|
|
331
|
+
for (const id of [...this.resources.keys()]) {
|
|
332
|
+
this.releaseResource(id);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get texture pool stats
|
|
338
|
+
*/
|
|
339
|
+
getPoolStats() {
|
|
340
|
+
return this.texturePool.getStats();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Dispose the renderer
|
|
345
|
+
*/
|
|
346
|
+
dispose(): void {
|
|
347
|
+
if (!this.initialized) return;
|
|
348
|
+
|
|
349
|
+
// Clear resources
|
|
350
|
+
for (const texture of this.resources.values()) {
|
|
351
|
+
if (texture instanceof PIXI.RenderTexture) {
|
|
352
|
+
texture.destroy(true);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
this.resources.clear();
|
|
356
|
+
|
|
357
|
+
// Clear filters
|
|
358
|
+
for (const filter of this.filters.values()) {
|
|
359
|
+
filter.destroy();
|
|
360
|
+
}
|
|
361
|
+
this.filters.clear();
|
|
362
|
+
|
|
363
|
+
// Clear texture pool
|
|
364
|
+
this.texturePool.dispose();
|
|
365
|
+
|
|
366
|
+
// Destroy app
|
|
367
|
+
if (this.app) {
|
|
368
|
+
this.app.destroy(true);
|
|
369
|
+
this.app = null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
this.initialized = false;
|
|
373
|
+
this.canvasElement = undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Texture Pool
|
|
3
|
+
*
|
|
4
|
+
* LRU texture pooling for efficient resource management.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as PIXI from "pixi.js";
|
|
8
|
+
import type { PixiResourceDescriptor, TexturePoolStats } from "./types";
|
|
9
|
+
|
|
10
|
+
interface PooledTexture {
|
|
11
|
+
texture: PIXI.RenderTexture;
|
|
12
|
+
descriptor: PixiResourceDescriptor;
|
|
13
|
+
lastUsed: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Texture pool with LRU eviction
|
|
18
|
+
*/
|
|
19
|
+
export class TexturePool {
|
|
20
|
+
private pool: Map<string, PooledTexture> = new Map();
|
|
21
|
+
private maxSize: number;
|
|
22
|
+
private hits = 0;
|
|
23
|
+
private misses = 0;
|
|
24
|
+
|
|
25
|
+
constructor(maxSize: number = 20) {
|
|
26
|
+
this.maxSize = maxSize;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Acquire a texture from the pool or create a new one
|
|
31
|
+
*/
|
|
32
|
+
acquire(descriptor: PixiResourceDescriptor): PIXI.RenderTexture {
|
|
33
|
+
const key = this.descriptorKey(descriptor);
|
|
34
|
+
const pooled = this.pool.get(key);
|
|
35
|
+
|
|
36
|
+
if (pooled) {
|
|
37
|
+
// Hit: Reuse existing texture
|
|
38
|
+
this.pool.delete(key);
|
|
39
|
+
this.hits++;
|
|
40
|
+
return pooled.texture;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Miss: Create new texture
|
|
44
|
+
this.misses++;
|
|
45
|
+
const texture = PIXI.RenderTexture.create({
|
|
46
|
+
width: descriptor.width,
|
|
47
|
+
height: descriptor.height,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return texture;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Release a texture back to the pool
|
|
55
|
+
*/
|
|
56
|
+
release(texture: PIXI.RenderTexture, descriptor: PixiResourceDescriptor): void {
|
|
57
|
+
const key = this.descriptorKey(descriptor);
|
|
58
|
+
|
|
59
|
+
// Check if pool is full
|
|
60
|
+
if (this.pool.size >= this.maxSize) {
|
|
61
|
+
this.evictLRU();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.pool.set(key, {
|
|
65
|
+
texture,
|
|
66
|
+
descriptor,
|
|
67
|
+
lastUsed: Date.now(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Evict least recently used texture
|
|
73
|
+
*/
|
|
74
|
+
private evictLRU(): void {
|
|
75
|
+
let oldestKey: string | null = null;
|
|
76
|
+
let oldestTime = Infinity;
|
|
77
|
+
|
|
78
|
+
for (const [key, pooled] of this.pool.entries()) {
|
|
79
|
+
if (pooled.lastUsed < oldestTime) {
|
|
80
|
+
oldestTime = pooled.lastUsed;
|
|
81
|
+
oldestKey = key;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (oldestKey) {
|
|
86
|
+
const pooled = this.pool.get(oldestKey);
|
|
87
|
+
if (pooled) {
|
|
88
|
+
pooled.texture.destroy(true);
|
|
89
|
+
this.pool.delete(oldestKey);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate key for descriptor
|
|
96
|
+
*/
|
|
97
|
+
private descriptorKey(descriptor: PixiResourceDescriptor): string {
|
|
98
|
+
return `${descriptor.width}x${descriptor.height}:${descriptor.format}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get pool statistics
|
|
103
|
+
*/
|
|
104
|
+
getStats(): TexturePoolStats {
|
|
105
|
+
let totalMemory = 0;
|
|
106
|
+
|
|
107
|
+
for (const pooled of this.pool.values()) {
|
|
108
|
+
const bytesPerPixel = this.getBytesPerPixel(pooled.descriptor.format);
|
|
109
|
+
totalMemory += pooled.descriptor.width * pooled.descriptor.height * bytesPerPixel;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
allocated: this.pool.size,
|
|
114
|
+
available: this.maxSize - this.pool.size,
|
|
115
|
+
totalMemory,
|
|
116
|
+
hits: this.hits,
|
|
117
|
+
misses: this.misses,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get bytes per pixel for format
|
|
123
|
+
*/
|
|
124
|
+
private getBytesPerPixel(format: string): number {
|
|
125
|
+
switch (format) {
|
|
126
|
+
case "rgba8":
|
|
127
|
+
return 4;
|
|
128
|
+
case "rgba16f":
|
|
129
|
+
return 8;
|
|
130
|
+
case "rgba32f":
|
|
131
|
+
return 16;
|
|
132
|
+
case "r8":
|
|
133
|
+
return 1;
|
|
134
|
+
case "depth24":
|
|
135
|
+
return 3;
|
|
136
|
+
default:
|
|
137
|
+
return 4;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear the pool
|
|
143
|
+
*/
|
|
144
|
+
clear(): void {
|
|
145
|
+
for (const pooled of this.pool.values()) {
|
|
146
|
+
pooled.texture.destroy(true);
|
|
147
|
+
}
|
|
148
|
+
this.pool.clear();
|
|
149
|
+
this.hits = 0;
|
|
150
|
+
this.misses = 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Dispose the pool
|
|
155
|
+
*/
|
|
156
|
+
dispose(): void {
|
|
157
|
+
this.clear();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Pixi Backend Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FrameGraph } from "../planner/types";
|
|
6
|
+
import type * as PIXI from "pixi.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render backend configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface RendererConfig {
|
|
12
|
+
canvas?: HTMLCanvasElement;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
backgroundColor?: number;
|
|
16
|
+
antialias?: boolean;
|
|
17
|
+
resolution?: number;
|
|
18
|
+
preserveDrawingBuffer?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render result
|
|
23
|
+
*/
|
|
24
|
+
export interface RenderResult {
|
|
25
|
+
outputTexture: PIXI.Texture;
|
|
26
|
+
stats: RenderStats;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rendering statistics
|
|
31
|
+
*/
|
|
32
|
+
export interface RenderStats {
|
|
33
|
+
passCount: number;
|
|
34
|
+
totalGpuTime: number;
|
|
35
|
+
totalCpuTime: number;
|
|
36
|
+
resourceCount: number;
|
|
37
|
+
textureMemory: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Texture pool statistics
|
|
42
|
+
*/
|
|
43
|
+
export interface TexturePoolStats {
|
|
44
|
+
allocated: number;
|
|
45
|
+
available: number;
|
|
46
|
+
totalMemory: number;
|
|
47
|
+
hits: number;
|
|
48
|
+
misses: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Pixi-specific resource descriptor (simpler than full ResourceDescriptor)
|
|
53
|
+
*/
|
|
54
|
+
export interface PixiResourceDescriptor {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
format: "rgba8" | "rgba16f" | "rgba32f" | "r8" | "depth24";
|
|
58
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Frame Graph Planner
|
|
3
|
+
*
|
|
4
|
+
* Converts media processing graphs into executable frame graphs.
|
|
5
|
+
* Handles resource allocation, pass optimization, and execution ordering.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from "./types";
|
|
9
|
+
export * from "./planner";
|
|
10
|
+
export * from "./optimizer";
|