@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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Graph Types
|
|
3
|
+
*
|
|
4
|
+
* Defines immutable node, value, capability, and lifecycle schemas for the Media Processing Graph.
|
|
5
|
+
* These types are shared across all Labs (Video, Transition, Body).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type NodeLifecycleState = "Created" | "Validated" | "Compiled" | "Prepared" | "Executing" | "Completed" | "Disposed";
|
|
9
|
+
|
|
10
|
+
export type GraphDataType = "Texture" | "Depth" | "MotionField" | "BoundingBoxes" | "Mask" | "Pose" | "FaceMesh" | "Particles" | "AudioSpectrum" | "Metadata";
|
|
11
|
+
|
|
12
|
+
export interface GraphValue<T = any> {
|
|
13
|
+
readonly type: GraphDataType;
|
|
14
|
+
readonly payload: T;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EffectCapabilities {
|
|
18
|
+
readonly temporal: boolean;
|
|
19
|
+
readonly stateful: boolean;
|
|
20
|
+
readonly spatial: boolean;
|
|
21
|
+
readonly geometry: boolean;
|
|
22
|
+
readonly inputsCount: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EffectRequirements {
|
|
26
|
+
readonly temporalRadius: number;
|
|
27
|
+
readonly preferredPrecision: "fp8" | "fp16" | "fp32";
|
|
28
|
+
readonly multipass: boolean;
|
|
29
|
+
readonly supportsHalfResolution: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GraphPin {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly type: GraphDataType;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GraphNode {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly type: string;
|
|
41
|
+
readonly version: number; // Current parameter version for dirty propagation
|
|
42
|
+
readonly params: Readonly<Record<string, any>>;
|
|
43
|
+
readonly inputs: Readonly<Record<string, GraphPin>>;
|
|
44
|
+
readonly outputs: Readonly<Record<string, GraphPin>>;
|
|
45
|
+
readonly capabilities: EffectCapabilities;
|
|
46
|
+
readonly requirements: EffectRequirements;
|
|
47
|
+
readonly lifecycle: NodeLifecycleState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GraphEdge {
|
|
51
|
+
readonly fromNodeId: string;
|
|
52
|
+
readonly fromPinId: string;
|
|
53
|
+
readonly toNodeId: string;
|
|
54
|
+
readonly toPinId: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface MediaProcessingGraph {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly nodes: readonly GraphNode[];
|
|
60
|
+
readonly edges: readonly GraphEdge[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Immutable graph helper functions
|
|
65
|
+
*/
|
|
66
|
+
export class GraphHelper {
|
|
67
|
+
static create(id: string): MediaProcessingGraph {
|
|
68
|
+
return { id, nodes: [], edges: [] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static withNode(graph: MediaProcessingGraph, node: GraphNode): MediaProcessingGraph {
|
|
72
|
+
return {
|
|
73
|
+
...graph,
|
|
74
|
+
nodes: [...graph.nodes.filter((n) => n.id !== node.id), node],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static withEdge(graph: MediaProcessingGraph, fromNodeId: string, fromPinId: string, toNodeId: string, toPinId: string): MediaProcessingGraph {
|
|
79
|
+
const edge: GraphEdge = { fromNodeId, fromPinId, toNodeId, toPinId };
|
|
80
|
+
return {
|
|
81
|
+
...graph,
|
|
82
|
+
edges: [...graph.edges, edge],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static getIncomingEdges(graph: MediaProcessingGraph, nodeId: string): GraphEdge[] {
|
|
87
|
+
return graph.edges.filter((e) => e.toNodeId === nodeId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static getOutgoingEdges(graph: MediaProcessingGraph, nodeId: string): GraphEdge[] {
|
|
91
|
+
return graph.edges.filter((e) => e.fromNodeId === nodeId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static findNode(graph: MediaProcessingGraph, nodeId: string): GraphNode | undefined {
|
|
95
|
+
return graph.nodes.find((n) => n.id === nodeId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static getDependencies(graph: MediaProcessingGraph, nodeId: string): GraphNode[] {
|
|
99
|
+
const incoming = this.getIncomingEdges(graph, nodeId);
|
|
100
|
+
return incoming.map((edge) => this.findNode(graph, edge.fromNodeId)).filter((node): node is GraphNode => node !== undefined);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static hasCycles(graph: MediaProcessingGraph): boolean {
|
|
104
|
+
const visited = new Set<string>();
|
|
105
|
+
const recursionStack = new Set<string>();
|
|
106
|
+
|
|
107
|
+
const dfs = (nodeId: string): boolean => {
|
|
108
|
+
visited.add(nodeId);
|
|
109
|
+
recursionStack.add(nodeId);
|
|
110
|
+
|
|
111
|
+
const outgoing = this.getOutgoingEdges(graph, nodeId);
|
|
112
|
+
for (const edge of outgoing) {
|
|
113
|
+
if (!visited.has(edge.toNodeId)) {
|
|
114
|
+
if (dfs(edge.toNodeId)) return true;
|
|
115
|
+
} else if (recursionStack.has(edge.toNodeId)) {
|
|
116
|
+
return true; // Cycle detected
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
recursionStack.delete(nodeId);
|
|
121
|
+
return false;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
for (const node of graph.nodes) {
|
|
125
|
+
if (!visited.has(node.id)) {
|
|
126
|
+
if (dfs(node.id)) return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static topologicalSort(graph: MediaProcessingGraph): GraphNode[] {
|
|
134
|
+
const inDegree = new Map<string, number>();
|
|
135
|
+
const sorted: GraphNode[] = [];
|
|
136
|
+
const queue: GraphNode[] = [];
|
|
137
|
+
|
|
138
|
+
// Initialize in-degrees
|
|
139
|
+
for (const node of graph.nodes) {
|
|
140
|
+
inDegree.set(node.id, 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Calculate in-degrees
|
|
144
|
+
for (const edge of graph.edges) {
|
|
145
|
+
inDegree.set(edge.toNodeId, (inDegree.get(edge.toNodeId) || 0) + 1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Find nodes with no incoming edges
|
|
149
|
+
for (const node of graph.nodes) {
|
|
150
|
+
if (inDegree.get(node.id) === 0) {
|
|
151
|
+
queue.push(node);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Process nodes
|
|
156
|
+
while (queue.length > 0) {
|
|
157
|
+
const node = queue.shift()!;
|
|
158
|
+
sorted.push(node);
|
|
159
|
+
|
|
160
|
+
const outgoing = this.getOutgoingEdges(graph, node.id);
|
|
161
|
+
for (const edge of outgoing) {
|
|
162
|
+
const targetNode = this.findNode(graph, edge.toNodeId);
|
|
163
|
+
if (!targetNode) continue;
|
|
164
|
+
|
|
165
|
+
const newInDegree = (inDegree.get(edge.toNodeId) || 0) - 1;
|
|
166
|
+
inDegree.set(edge.toNodeId, newInDegree);
|
|
167
|
+
|
|
168
|
+
if (newInDegree === 0) {
|
|
169
|
+
queue.push(targetNode);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return sorted;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Graph Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates media processing graphs for correctness.
|
|
5
|
+
* Checks for cycles, type mismatches, missing connections, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { MediaProcessingGraph, GraphNode, GraphEdge } from "./types";
|
|
9
|
+
import { GraphHelper } from "./types";
|
|
10
|
+
|
|
11
|
+
export interface ValidationError {
|
|
12
|
+
type: "cycle" | "type-mismatch" | "missing-connection" | "invalid-node";
|
|
13
|
+
message: string;
|
|
14
|
+
nodeId?: string;
|
|
15
|
+
edgeFrom?: string;
|
|
16
|
+
edgeTo?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GraphValidationResult {
|
|
20
|
+
valid: boolean;
|
|
21
|
+
errors: ValidationError[];
|
|
22
|
+
warnings: ValidationWarning[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ValidationWarning {
|
|
26
|
+
type: "performance" | "compatibility" | "best-practice";
|
|
27
|
+
message: string;
|
|
28
|
+
severity: "low" | "medium" | "high";
|
|
29
|
+
nodeId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* GraphValidator - Validates graph structure and semantics
|
|
34
|
+
*/
|
|
35
|
+
export class GraphValidator {
|
|
36
|
+
/**
|
|
37
|
+
* Validate a media processing graph
|
|
38
|
+
*/
|
|
39
|
+
validate(graph: MediaProcessingGraph): GraphValidationResult {
|
|
40
|
+
const errors: ValidationError[] = [];
|
|
41
|
+
const warnings: ValidationWarning[] = [];
|
|
42
|
+
|
|
43
|
+
// Check for cycles
|
|
44
|
+
if (GraphHelper.hasCycles(graph)) {
|
|
45
|
+
errors.push({
|
|
46
|
+
type: "cycle",
|
|
47
|
+
message: "Graph contains cycles",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate each node
|
|
52
|
+
for (const node of graph.nodes) {
|
|
53
|
+
const nodeErrors = this.validateNode(graph, node);
|
|
54
|
+
errors.push(...nodeErrors);
|
|
55
|
+
|
|
56
|
+
const nodeWarnings = this.generateWarnings(graph, node);
|
|
57
|
+
warnings.push(...nodeWarnings);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate each edge
|
|
61
|
+
for (const edge of graph.edges) {
|
|
62
|
+
const edgeErrors = this.validateEdge(graph, edge);
|
|
63
|
+
errors.push(...edgeErrors);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
valid: errors.length === 0,
|
|
68
|
+
errors,
|
|
69
|
+
warnings,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate a single node
|
|
75
|
+
*/
|
|
76
|
+
private validateNode(graph: MediaProcessingGraph, node: GraphNode): ValidationError[] {
|
|
77
|
+
const errors: ValidationError[] = [];
|
|
78
|
+
|
|
79
|
+
// Check if node type is valid
|
|
80
|
+
if (!node.type) {
|
|
81
|
+
errors.push({
|
|
82
|
+
type: "invalid-node",
|
|
83
|
+
message: `Node ${node.id} has no type`,
|
|
84
|
+
nodeId: node.id,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if required inputs are connected
|
|
89
|
+
const inputKeys = Object.keys(node.inputs);
|
|
90
|
+
const incomingEdges = GraphHelper.getIncomingEdges(graph, node.id);
|
|
91
|
+
|
|
92
|
+
for (const inputKey of inputKeys) {
|
|
93
|
+
const hasConnection = incomingEdges.some((edge) => edge.toPinId === inputKey);
|
|
94
|
+
if (!hasConnection && node.type !== "MediaInput") {
|
|
95
|
+
errors.push({
|
|
96
|
+
type: "missing-connection",
|
|
97
|
+
message: `Node ${node.id} input '${inputKey}' is not connected`,
|
|
98
|
+
nodeId: node.id,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return errors;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate a single edge
|
|
108
|
+
*/
|
|
109
|
+
private validateEdge(graph: MediaProcessingGraph, edge: GraphEdge): ValidationError[] {
|
|
110
|
+
const errors: ValidationError[] = [];
|
|
111
|
+
|
|
112
|
+
const fromNode = GraphHelper.findNode(graph, edge.fromNodeId);
|
|
113
|
+
const toNode = GraphHelper.findNode(graph, edge.toNodeId);
|
|
114
|
+
|
|
115
|
+
if (!fromNode) {
|
|
116
|
+
errors.push({
|
|
117
|
+
type: "invalid-node",
|
|
118
|
+
message: `Edge references non-existent source node: ${edge.fromNodeId}`,
|
|
119
|
+
edgeFrom: edge.fromNodeId,
|
|
120
|
+
edgeTo: edge.toNodeId,
|
|
121
|
+
});
|
|
122
|
+
return errors;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!toNode) {
|
|
126
|
+
errors.push({
|
|
127
|
+
type: "invalid-node",
|
|
128
|
+
message: `Edge references non-existent target node: ${edge.toNodeId}`,
|
|
129
|
+
edgeFrom: edge.fromNodeId,
|
|
130
|
+
edgeTo: edge.toNodeId,
|
|
131
|
+
});
|
|
132
|
+
return errors;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if pins exist
|
|
136
|
+
const fromPin = fromNode.outputs[edge.fromPinId];
|
|
137
|
+
const toPin = toNode.inputs[edge.toPinId];
|
|
138
|
+
|
|
139
|
+
if (!fromPin) {
|
|
140
|
+
errors.push({
|
|
141
|
+
type: "missing-connection",
|
|
142
|
+
message: `Source node ${edge.fromNodeId} has no output pin '${edge.fromPinId}'`,
|
|
143
|
+
edgeFrom: edge.fromNodeId,
|
|
144
|
+
edgeTo: edge.toNodeId,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!toPin) {
|
|
149
|
+
errors.push({
|
|
150
|
+
type: "missing-connection",
|
|
151
|
+
message: `Target node ${edge.toNodeId} has no input pin '${edge.toPinId}'`,
|
|
152
|
+
edgeFrom: edge.fromNodeId,
|
|
153
|
+
edgeTo: edge.toNodeId,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check type compatibility
|
|
158
|
+
if (fromPin && toPin && fromPin.type !== toPin.type) {
|
|
159
|
+
errors.push({
|
|
160
|
+
type: "type-mismatch",
|
|
161
|
+
message: `Type mismatch: ${edge.fromNodeId}.${edge.fromPinId} (${fromPin.type}) → ${edge.toNodeId}.${edge.toPinId} (${toPin.type})`,
|
|
162
|
+
edgeFrom: edge.fromNodeId,
|
|
163
|
+
edgeTo: edge.toNodeId,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate performance and best-practice warnings
|
|
172
|
+
*/
|
|
173
|
+
private generateWarnings(graph: MediaProcessingGraph, node: GraphNode): ValidationWarning[] {
|
|
174
|
+
const warnings: ValidationWarning[] = [];
|
|
175
|
+
|
|
176
|
+
// Warn about multipass effects
|
|
177
|
+
if (node.requirements.multipass) {
|
|
178
|
+
warnings.push({
|
|
179
|
+
type: "performance",
|
|
180
|
+
message: `Node ${node.id} requires multiple render passes`,
|
|
181
|
+
severity: "medium",
|
|
182
|
+
nodeId: node.id,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Warn about high temporal radius
|
|
187
|
+
if (node.requirements.temporalRadius > 5) {
|
|
188
|
+
warnings.push({
|
|
189
|
+
type: "performance",
|
|
190
|
+
message: `Node ${node.id} has high temporal radius (${node.requirements.temporalRadius} frames)`,
|
|
191
|
+
severity: "high",
|
|
192
|
+
nodeId: node.id,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Warn about stateful effects
|
|
197
|
+
if (node.capabilities.stateful) {
|
|
198
|
+
warnings.push({
|
|
199
|
+
type: "compatibility",
|
|
200
|
+
message: `Node ${node.id} is stateful - may not work well with random access`,
|
|
201
|
+
severity: "low",
|
|
202
|
+
nodeId: node.id,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return warnings;
|
|
207
|
+
}
|
|
208
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime
|
|
3
|
+
*
|
|
4
|
+
* Shared runtime infrastructure for all Clypra Studio Labs.
|
|
5
|
+
* This package contains the core execution engine used by Video Lab, Transition Lab, and Body Lab.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Graph building
|
|
9
|
+
export * from "./graph";
|
|
10
|
+
|
|
11
|
+
// Frame planning
|
|
12
|
+
export * from "./planner";
|
|
13
|
+
|
|
14
|
+
// Pixi rendering backend
|
|
15
|
+
export * from "./pixi";
|
|
16
|
+
|
|
17
|
+
// Resource management
|
|
18
|
+
export * from "./resources";
|
|
19
|
+
|
|
20
|
+
// Runtime validation
|
|
21
|
+
export * from "./validation";
|
|
22
|
+
|
|
23
|
+
// Testing & Publishing (Phase 6)
|
|
24
|
+
export * from "./testing/goldenTests";
|
|
25
|
+
export * from "./testing/benchmarkRunner";
|
|
26
|
+
export { validateEffect, EffectValidator, type ValidationResult, type EffectDefinition } from "./validation/effectValidator";
|
|
27
|
+
|
|
28
|
+
export const RUNTIME_VERSION = "1.0.0";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Pixi Filter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for creating and configuring Pixi filters.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as PIXI from "pixi.js";
|
|
8
|
+
import { AdjustmentFilter } from "pixi-filters";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a filter for a given shader ID
|
|
12
|
+
*/
|
|
13
|
+
export function createFilter(shaderId: string, uniforms: Record<string, any> = {}): PIXI.Filter {
|
|
14
|
+
switch (shaderId) {
|
|
15
|
+
case "brightness":
|
|
16
|
+
return new AdjustmentFilter({ brightness: uniforms.brightness ?? 1.0 });
|
|
17
|
+
|
|
18
|
+
case "contrast":
|
|
19
|
+
return new AdjustmentFilter({ contrast: uniforms.contrast ?? 1.0 });
|
|
20
|
+
|
|
21
|
+
case "saturation":
|
|
22
|
+
return new AdjustmentFilter({ saturation: uniforms.saturation ?? 1.0 });
|
|
23
|
+
|
|
24
|
+
case "gaussian-blur":
|
|
25
|
+
case "gaussian-blur-h":
|
|
26
|
+
case "gaussian-blur-v":
|
|
27
|
+
return new PIXI.BlurFilter({
|
|
28
|
+
strength: uniforms.strength ?? 8,
|
|
29
|
+
quality: uniforms.quality ?? 4,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
case "color-adjustments":
|
|
33
|
+
return new AdjustmentFilter({
|
|
34
|
+
brightness: uniforms.brightness ?? 1.0,
|
|
35
|
+
contrast: uniforms.contrast ?? 1.0,
|
|
36
|
+
saturation: uniforms.saturation ?? 1.0,
|
|
37
|
+
red: uniforms.red ?? 1.0,
|
|
38
|
+
green: uniforms.green ?? 1.0,
|
|
39
|
+
blue: uniforms.blue ?? 1.0,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
case "copy":
|
|
43
|
+
case "blit":
|
|
44
|
+
// No-op filter
|
|
45
|
+
return new AdjustmentFilter({ brightness: 1, contrast: 1, saturation: 1 });
|
|
46
|
+
|
|
47
|
+
default:
|
|
48
|
+
// Default adjustment filter
|
|
49
|
+
return new AdjustmentFilter({});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update filter uniforms
|
|
55
|
+
*/
|
|
56
|
+
export function updateFilterUniforms(filter: PIXI.Filter, uniforms: Record<string, any>, shaderId?: string): void {
|
|
57
|
+
if (filter instanceof AdjustmentFilter) {
|
|
58
|
+
if (uniforms.brightness !== undefined) {
|
|
59
|
+
filter.brightness = Number(uniforms.brightness);
|
|
60
|
+
}
|
|
61
|
+
if (uniforms.contrast !== undefined) {
|
|
62
|
+
filter.contrast = Number(uniforms.contrast);
|
|
63
|
+
}
|
|
64
|
+
if (uniforms.saturation !== undefined) {
|
|
65
|
+
filter.saturation = Number(uniforms.saturation);
|
|
66
|
+
}
|
|
67
|
+
if (uniforms.red !== undefined) {
|
|
68
|
+
filter.red = Number(uniforms.red);
|
|
69
|
+
}
|
|
70
|
+
if (uniforms.green !== undefined) {
|
|
71
|
+
filter.green = Number(uniforms.green);
|
|
72
|
+
}
|
|
73
|
+
if (uniforms.blue !== undefined) {
|
|
74
|
+
filter.blue = Number(uniforms.blue);
|
|
75
|
+
}
|
|
76
|
+
} else if (filter instanceof PIXI.BlurFilter) {
|
|
77
|
+
if (uniforms.strength !== undefined) {
|
|
78
|
+
filter.strength = Number(uniforms.strength);
|
|
79
|
+
}
|
|
80
|
+
if (uniforms.quality !== undefined) {
|
|
81
|
+
filter.quality = Number(uniforms.quality);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Normalize color adjustment uniforms
|
|
88
|
+
*/
|
|
89
|
+
export function normalizeColorUniforms(uniforms: Record<string, any>): Record<string, number> {
|
|
90
|
+
return {
|
|
91
|
+
brightness: uniforms.brightness !== undefined ? 1.0 + Number(uniforms.brightness) : 1.0,
|
|
92
|
+
contrast: uniforms.contrast !== undefined ? 1.0 + Number(uniforms.contrast) : 1.0,
|
|
93
|
+
saturation: uniforms.saturation !== undefined ? 1.0 + Number(uniforms.saturation) : 1.0,
|
|
94
|
+
red: uniforms.red ?? 1.0,
|
|
95
|
+
green: uniforms.green ?? 1.0,
|
|
96
|
+
blue: uniforms.blue ?? 1.0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Pixi.js Rendering Backend
|
|
3
|
+
*
|
|
4
|
+
* GPU-accelerated rendering using Pixi.js.
|
|
5
|
+
* Executes frame graphs and manages texture resources.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from "./types";
|
|
9
|
+
export * from "./renderer";
|
|
10
|
+
export * from "./filters";
|
|
11
|
+
export * from "./texture-pool";
|