@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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Resource Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates resource bindings and availability.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResourceValidationResult, ValidationIssue } from "./types";
|
|
8
|
+
import type { FrameGraph, RenderPass } from "../planner/types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resource Validator
|
|
12
|
+
*
|
|
13
|
+
* Validates that all required resources are properly bound.
|
|
14
|
+
*/
|
|
15
|
+
export class ResourceValidator {
|
|
16
|
+
/**
|
|
17
|
+
* Validate a frame graph's resources
|
|
18
|
+
*/
|
|
19
|
+
validate(frameGraph: FrameGraph): ResourceValidationResult {
|
|
20
|
+
const issues: ValidationIssue[] = [];
|
|
21
|
+
const boundResources = new Set<string>();
|
|
22
|
+
const missingResources: string[] = [];
|
|
23
|
+
|
|
24
|
+
// Collect all allocated resources
|
|
25
|
+
for (const resource of frameGraph.resourceRequests) {
|
|
26
|
+
boundResources.add(resource.id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check each pass
|
|
30
|
+
for (const pass of frameGraph.passes) {
|
|
31
|
+
this.validatePass(pass, boundResources, issues, missingResources);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
valid: issues.filter((i) => i.type === "error").length === 0,
|
|
36
|
+
issues,
|
|
37
|
+
boundResources: Array.from(boundResources),
|
|
38
|
+
missingResources: Array.from(new Set(missingResources)),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate a single render pass
|
|
44
|
+
*/
|
|
45
|
+
private validatePass(pass: RenderPass, boundResources: Set<string>, issues: ValidationIssue[], missingResources: string[]): void {
|
|
46
|
+
// Check input resources
|
|
47
|
+
for (const input of pass.inputs) {
|
|
48
|
+
if (!boundResources.has(input)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
type: "error",
|
|
51
|
+
category: "resource",
|
|
52
|
+
message: `Pass "${pass.id}" references missing input resource: ${input}`,
|
|
53
|
+
details: { passId: pass.id, resourceId: input },
|
|
54
|
+
});
|
|
55
|
+
missingResources.push(input);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check output resource
|
|
60
|
+
if (!boundResources.has(pass.output)) {
|
|
61
|
+
issues.push({
|
|
62
|
+
type: "error",
|
|
63
|
+
category: "resource",
|
|
64
|
+
message: `Pass "${pass.id}" references missing output resource: ${pass.output}`,
|
|
65
|
+
details: { passId: pass.id, resourceId: pass.output },
|
|
66
|
+
});
|
|
67
|
+
missingResources.push(pass.output);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate uniforms
|
|
71
|
+
this.validateUniforms(pass, issues);
|
|
72
|
+
|
|
73
|
+
// Check for common issues
|
|
74
|
+
this.checkPassConfiguration(pass, issues);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate uniforms
|
|
79
|
+
*/
|
|
80
|
+
private validateUniforms(pass: RenderPass, issues: ValidationIssue[]): void {
|
|
81
|
+
const uniforms = pass.uniforms;
|
|
82
|
+
|
|
83
|
+
// Check for undefined uniforms
|
|
84
|
+
for (const [key, value] of Object.entries(uniforms)) {
|
|
85
|
+
if (value === undefined || value === null) {
|
|
86
|
+
issues.push({
|
|
87
|
+
type: "warning",
|
|
88
|
+
category: "resource",
|
|
89
|
+
message: `Pass "${pass.id}" has undefined uniform: ${key}`,
|
|
90
|
+
details: { passId: pass.id, uniformName: key },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for NaN values
|
|
95
|
+
if (typeof value === "number" && isNaN(value)) {
|
|
96
|
+
issues.push({
|
|
97
|
+
type: "error",
|
|
98
|
+
category: "resource",
|
|
99
|
+
message: `Pass "${pass.id}" has NaN uniform value: ${key}`,
|
|
100
|
+
details: { passId: pass.id, uniformName: key },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check pass configuration
|
|
108
|
+
*/
|
|
109
|
+
private checkPassConfiguration(pass: RenderPass, issues: ValidationIssue[]): void {
|
|
110
|
+
// Warn about passes with no inputs
|
|
111
|
+
if (pass.inputs.length === 0 && pass.shaderId !== "copy" && pass.shaderId !== "blit") {
|
|
112
|
+
issues.push({
|
|
113
|
+
type: "warning",
|
|
114
|
+
category: "resource",
|
|
115
|
+
message: `Pass "${pass.id}" has no input resources`,
|
|
116
|
+
details: { passId: pass.id },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Warn about empty shader ID
|
|
121
|
+
if (!pass.shaderId || pass.shaderId.trim() === "") {
|
|
122
|
+
issues.push({
|
|
123
|
+
type: "error",
|
|
124
|
+
category: "resource",
|
|
125
|
+
message: `Pass "${pass.id}" has empty shader ID`,
|
|
126
|
+
details: { passId: pass.id },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validate resource descriptors
|
|
133
|
+
*/
|
|
134
|
+
validateDescriptors(descriptors: Array<{ id: string; width: number; height: number }>): ValidationIssue[] {
|
|
135
|
+
const issues: ValidationIssue[] = [];
|
|
136
|
+
const ids = new Set<string>();
|
|
137
|
+
|
|
138
|
+
for (const descriptor of descriptors) {
|
|
139
|
+
// Check for duplicate IDs
|
|
140
|
+
if (ids.has(descriptor.id)) {
|
|
141
|
+
issues.push({
|
|
142
|
+
type: "error",
|
|
143
|
+
category: "resource",
|
|
144
|
+
message: `Duplicate resource ID: ${descriptor.id}`,
|
|
145
|
+
details: { resourceId: descriptor.id },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
ids.add(descriptor.id);
|
|
149
|
+
|
|
150
|
+
// Check dimensions
|
|
151
|
+
if (descriptor.width <= 0 || descriptor.height <= 0) {
|
|
152
|
+
issues.push({
|
|
153
|
+
type: "error",
|
|
154
|
+
category: "resource",
|
|
155
|
+
message: `Invalid resource dimensions for "${descriptor.id}": ${descriptor.width}x${descriptor.height}`,
|
|
156
|
+
details: { resourceId: descriptor.id, width: descriptor.width, height: descriptor.height },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Warn about very large textures
|
|
161
|
+
if (descriptor.width > 4096 || descriptor.height > 4096) {
|
|
162
|
+
issues.push({
|
|
163
|
+
type: "warning",
|
|
164
|
+
category: "performance",
|
|
165
|
+
message: `Large texture size for "${descriptor.id}": ${descriptor.width}x${descriptor.height}`,
|
|
166
|
+
details: { resourceId: descriptor.id, width: descriptor.width, height: descriptor.height },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return issues;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Shader Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates GLSL shaders for compilation and correctness.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ShaderValidationResult, ValidationIssue } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Shader Validator
|
|
11
|
+
*
|
|
12
|
+
* Validates GLSL shaders without requiring a full WebGL context.
|
|
13
|
+
*/
|
|
14
|
+
export class ShaderValidator {
|
|
15
|
+
/**
|
|
16
|
+
* Validate a GLSL shader
|
|
17
|
+
*/
|
|
18
|
+
validate(source: string, type: "vertex" | "fragment"): ShaderValidationResult {
|
|
19
|
+
const issues: ValidationIssue[] = [];
|
|
20
|
+
const uniformsFound: string[] = [];
|
|
21
|
+
const attributesFound: string[] = [];
|
|
22
|
+
|
|
23
|
+
// Parse uniforms
|
|
24
|
+
const uniformMatches = source.matchAll(/uniform\s+(\w+)\s+(\w+);/g);
|
|
25
|
+
for (const match of uniformMatches) {
|
|
26
|
+
uniformsFound.push(match[2]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Parse attributes (vertex shaders only)
|
|
30
|
+
if (type === "vertex") {
|
|
31
|
+
const attributeMatches = source.matchAll(/attribute\s+(\w+)\s+(\w+);/g);
|
|
32
|
+
for (const match of attributeMatches) {
|
|
33
|
+
attributesFound.push(match[2]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for common issues
|
|
38
|
+
this.checkSyntax(source, issues);
|
|
39
|
+
this.checkPrecision(source, issues);
|
|
40
|
+
this.checkMainFunction(source, issues);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
valid: issues.filter((i) => i.type === "error").length === 0,
|
|
44
|
+
compiled: true, // Will be set by actual compilation
|
|
45
|
+
issues,
|
|
46
|
+
uniformsFound,
|
|
47
|
+
attributesFound,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate shader compilation with WebGL context
|
|
53
|
+
*/
|
|
54
|
+
validateWithContext(gl: WebGLRenderingContext | WebGL2RenderingContext, source: string, type: "vertex" | "fragment"): ShaderValidationResult {
|
|
55
|
+
const result = this.validate(source, type);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const shaderType = type === "vertex" ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER;
|
|
59
|
+
const shader = gl.createShader(shaderType);
|
|
60
|
+
|
|
61
|
+
if (!shader) {
|
|
62
|
+
result.issues.push({
|
|
63
|
+
type: "error",
|
|
64
|
+
category: "shader",
|
|
65
|
+
message: "Failed to create shader",
|
|
66
|
+
});
|
|
67
|
+
result.compiled = false;
|
|
68
|
+
result.valid = false;
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
gl.shaderSource(shader, source);
|
|
73
|
+
gl.compileShader(shader);
|
|
74
|
+
|
|
75
|
+
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
76
|
+
result.compiled = compiled;
|
|
77
|
+
|
|
78
|
+
if (!compiled) {
|
|
79
|
+
const log = gl.getShaderInfoLog(shader);
|
|
80
|
+
result.issues.push({
|
|
81
|
+
type: "error",
|
|
82
|
+
category: "shader",
|
|
83
|
+
message: `Shader compilation failed: ${log}`,
|
|
84
|
+
});
|
|
85
|
+
result.valid = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
gl.deleteShader(shader);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
result.issues.push({
|
|
91
|
+
type: "error",
|
|
92
|
+
category: "shader",
|
|
93
|
+
message: `Shader validation error: ${error}`,
|
|
94
|
+
});
|
|
95
|
+
result.compiled = false;
|
|
96
|
+
result.valid = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check syntax issues
|
|
104
|
+
*/
|
|
105
|
+
private checkSyntax(source: string, issues: ValidationIssue[]): void {
|
|
106
|
+
// Check for missing semicolons
|
|
107
|
+
const lines = source.split("\n");
|
|
108
|
+
lines.forEach((line, index) => {
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
|
|
111
|
+
// Skip empty lines and preprocessor directives
|
|
112
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for statements that should end with semicolon
|
|
117
|
+
if (!trimmed.endsWith(";") && !trimmed.endsWith("{") && !trimmed.endsWith("}") && trimmed.length > 0) {
|
|
118
|
+
issues.push({
|
|
119
|
+
type: "warning",
|
|
120
|
+
category: "shader",
|
|
121
|
+
message: "Possible missing semicolon",
|
|
122
|
+
location: { line: index + 1 },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check precision declarations
|
|
130
|
+
*/
|
|
131
|
+
private checkPrecision(source: string, issues: ValidationIssue[]): void {
|
|
132
|
+
// Fragment shaders should have precision declarations
|
|
133
|
+
if (!source.includes("precision")) {
|
|
134
|
+
issues.push({
|
|
135
|
+
type: "warning",
|
|
136
|
+
category: "shader",
|
|
137
|
+
message: "Missing precision qualifier (e.g., precision highp float;)",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check for main function
|
|
144
|
+
*/
|
|
145
|
+
private checkMainFunction(source: string, issues: ValidationIssue[]): void {
|
|
146
|
+
if (!source.includes("void main()")) {
|
|
147
|
+
issues.push({
|
|
148
|
+
type: "error",
|
|
149
|
+
category: "shader",
|
|
150
|
+
message: "Missing main function",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clypra/runtime — Validation Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ValidationIssue {
|
|
6
|
+
type: "error" | "warning";
|
|
7
|
+
category: "shader" | "resource" | "graph" | "performance";
|
|
8
|
+
message: string;
|
|
9
|
+
details?: any;
|
|
10
|
+
location?: {
|
|
11
|
+
file?: string;
|
|
12
|
+
line?: number;
|
|
13
|
+
column?: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ValidationResult {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
issues: ValidationIssue[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ShaderValidationResult extends ValidationResult {
|
|
23
|
+
compiled: boolean;
|
|
24
|
+
uniformsFound: string[];
|
|
25
|
+
attributesFound: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResourceValidationResult extends ValidationResult {
|
|
29
|
+
boundResources: string[];
|
|
30
|
+
missingResources: string[];
|
|
31
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022", "DOM"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"outDir": "./dist",
|
|
17
|
+
"rootDir": "./src"
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
21
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: {
|
|
5
|
+
index: "src/index.ts",
|
|
6
|
+
"graph/index": "src/graph/index.ts",
|
|
7
|
+
"planner/index": "src/planner/index.ts",
|
|
8
|
+
"pixi/index": "src/pixi/index.ts",
|
|
9
|
+
"resources/index": "src/resources/index.ts",
|
|
10
|
+
"validation/index": "src/validation/index.ts",
|
|
11
|
+
},
|
|
12
|
+
format: ["esm"],
|
|
13
|
+
dts: true,
|
|
14
|
+
sourcemap: true,
|
|
15
|
+
clean: true,
|
|
16
|
+
splitting: false,
|
|
17
|
+
treeshake: true,
|
|
18
|
+
});
|