├── .gitignore ├── .gitattributes ├── .github └── FUNDING.yml ├── types ├── ast │ ├── iexpression.d.ts │ ├── parse_context.d.ts │ └── base_node.d.ts ├── reflect │ ├── get_type_name.d.ts │ ├── info.d.ts │ └── reflect.d.ts ├── exec │ ├── base_data.d.ts │ ├── exec_stack.d.ts │ ├── util.d.ts │ ├── exec_interface.d.ts │ ├── stack_frame.d.ts │ ├── data.d.ts │ ├── exec_context.d.ts │ ├── command.d.ts │ ├── typed_data.d.ts │ └── builtin_functions.d.ts ├── index.d.ts ├── wgsl_reflect.d.ts ├── utils │ ├── float.d.ts │ ├── cast.d.ts │ ├── texture_sample.d.ts │ ├── base_node.d.ts │ ├── matrix.d.ts │ └── texture_format_info.d.ts ├── wgsl_debug.d.ts ├── wgsl_exec.d.ts ├── wgsl_parser.d.ts └── wgsl_scanner.d.ts ├── src ├── index.ts ├── ast │ └── parse_context.ts ├── exec │ ├── exec_stack.ts │ ├── exec_interface.ts │ ├── stack_frame.ts │ ├── command.ts │ └── exec_context.ts ├── wgsl_reflect.ts ├── utils │ ├── cast.ts │ ├── float.ts │ ├── matrix.ts │ ├── texture_format_info.ts │ └── texture_sample.ts └── reflect │ └── info.ts ├── test ├── index.html ├── test.css ├── test_node.js ├── test_all.js ├── tests │ ├── struct_layout.js │ ├── struct.js │ ├── test_debug.js │ └── test_scanner.js └── test.js ├── tsconfig.json ├── rollup.config.js ├── LICENSE.md ├── package.json ├── example.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # For all files in the repo, default to LF line endings 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: brendan-duncan 4 | -------------------------------------------------------------------------------- /types/ast/iexpression.d.ts: -------------------------------------------------------------------------------- 1 | export interface IExpression { 2 | postfix: IExpression | null; 3 | } 4 | -------------------------------------------------------------------------------- /types/reflect/get_type_name.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeInfo } from "./info.js"; 2 | import { Type } from "../wgsl_ast.js"; 3 | export declare function getTypeName(type: TypeInfo | Type): string; 4 | -------------------------------------------------------------------------------- /types/exec/base_data.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeInfo } from "../reflect/info.js"; 2 | export declare class BaseData { 3 | typeInfo: TypeInfo; 4 | constructor(typeInfo: TypeInfo); 5 | toString(): string; 6 | } 7 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./wgsl_ast"; 2 | export * from "./wgsl_parser"; 3 | export * from "./wgsl_reflect"; 4 | export * from "./wgsl_scanner"; 5 | export * from "./wgsl_exec"; 6 | export * from "./wgsl_debug"; 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./wgsl_ast"; 2 | export * from "./wgsl_parser"; 3 | export * from "./wgsl_reflect"; 4 | export * from "./wgsl_scanner"; 5 | export * from "./wgsl_exec"; 6 | export * from "./wgsl_debug"; 7 | -------------------------------------------------------------------------------- /types/exec/exec_stack.d.ts: -------------------------------------------------------------------------------- 1 | import { StackFrame } from "./stack_frame.js"; 2 | export declare class ExecStack { 3 | states: StackFrame[]; 4 | get isEmpty(): boolean; 5 | get last(): StackFrame | null; 6 | pop(): void; 7 | } 8 | -------------------------------------------------------------------------------- /types/ast/parse_context.d.ts: -------------------------------------------------------------------------------- 1 | import { Const, Alias, Struct } from "../wgsl_ast.js"; 2 | export declare class ParseContext { 3 | constants: Map; 4 | aliases: Map; 5 | structs: Map; 6 | } 7 | -------------------------------------------------------------------------------- /types/wgsl_reflect.d.ts: -------------------------------------------------------------------------------- 1 | import { Reflect } from "./reflect/reflect.js"; 2 | export * from "./reflect/info.js"; 3 | export declare class WgslReflect extends Reflect { 4 | constructor(code?: string); 5 | update(code: string): void; 6 | } 7 | -------------------------------------------------------------------------------- /src/ast/parse_context.ts: -------------------------------------------------------------------------------- 1 | import { Const, Alias, Struct } from "../wgsl_ast.js"; 2 | 3 | export class ParseContext { 4 | constants: Map = new Map(); 5 | aliases: Map = new Map(); 6 | structs: Map = new Map(); 7 | } 8 | -------------------------------------------------------------------------------- /types/utils/float.d.ts: -------------------------------------------------------------------------------- 1 | export declare function float16ToFloat32(float16: number): number; 2 | export declare function float32ToFloat16(float32: number): number; 3 | export declare function float11ToFloat32(f11: number): number; 4 | export declare function float10ToFloat32(f10: number): number; 5 | -------------------------------------------------------------------------------- /types/exec/util.d.ts: -------------------------------------------------------------------------------- 1 | export declare function isArray(value: any): boolean; 2 | export declare function isNumber(value: any): boolean; 3 | export declare function castScalar(v: number, from: string, to: string): number; 4 | export declare function castVector(v: number[], from: string, to: string): number[]; 5 | -------------------------------------------------------------------------------- /types/utils/cast.d.ts: -------------------------------------------------------------------------------- 1 | export declare function isArray(value: any): boolean; 2 | export declare function isNumber(value: any): boolean; 3 | export declare function castScalar(v: number, from: string, to: string): number; 4 | export declare function castVector(v: number[], from: string, to: string): number[]; 5 | -------------------------------------------------------------------------------- /src/exec/exec_stack.ts: -------------------------------------------------------------------------------- 1 | import { StackFrame } from "./stack_frame.js"; 2 | 3 | export class ExecStack { 4 | states: StackFrame[] = []; 5 | 6 | get isEmpty(): boolean { return this.states.length == 0; } 7 | 8 | get last(): StackFrame | null { return this.states[this.states.length - 1] ?? null; } 9 | 10 | pop(): void { 11 | this.states.pop(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /types/utils/texture_sample.d.ts: -------------------------------------------------------------------------------- 1 | export declare function setTexturePixel(imageData: Uint8Array, x: number, y: number, z: number, mipLevel: number, height: number, bytesPerRow: number, texelByteSize: number, format: string, value: number[]): void; 2 | export declare function getTexturePixel(imageData: Uint8Array, x: number, y: number, z: number, mipLevel: number, height: number, bytesPerRow: number, texelByteSize: number, format: string): number[] | null; 3 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | wgsl_reflect Tests 5 | 6 | 7 | 8 |
wgsl_reflect Tests
9 |
10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /types/exec/exec_interface.d.ts: -------------------------------------------------------------------------------- 1 | import { Node, Type } from "../wgsl_ast.js"; 2 | import { ExecContext } from "./exec_context.js"; 3 | import { TypeInfo } from "../wgsl_reflect.js"; 4 | import { Data } from "../wgsl_ast.js"; 5 | export declare class ExecInterface { 6 | evalExpression(node: Node, context: ExecContext): Data | null; 7 | getTypeInfo(type: Type | string): TypeInfo | null; 8 | getVariableName(node: Node, context: ExecContext): string | null; 9 | } 10 | -------------------------------------------------------------------------------- /src/wgsl_reflect.ts: -------------------------------------------------------------------------------- 1 | import { WgslParser } from "./wgsl_parser.js"; 2 | import { Reflect } from "./reflect/reflect.js"; 3 | 4 | export * from "./reflect/info.js"; 5 | 6 | export class WgslReflect extends Reflect { 7 | constructor(code?: string) { 8 | super(); 9 | if (code) { 10 | this.update(code); 11 | } 12 | } 13 | 14 | update(code: string): void { 15 | const parser = new WgslParser(); 16 | const ast = parser.parse(code); 17 | this.updateAST(ast); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /types/exec/stack_frame.d.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "./command.js"; 2 | import { CallExpr } from "../wgsl_ast.js"; 3 | import { ExecContext } from "./exec_context.js"; 4 | export declare class StackFrame { 5 | parent: StackFrame | null; 6 | context: ExecContext; 7 | commands: Command[]; 8 | current: number; 9 | parentCallExpr: CallExpr | null; 10 | constructor(context: ExecContext, parent?: StackFrame); 11 | get isAtEnd(): boolean; 12 | getNextCommand(): Command | null; 13 | getCurrentCommand(): Command | null; 14 | } 15 | -------------------------------------------------------------------------------- /types/ast/base_node.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BaseNode { 2 | static _id: number; 3 | id: number; 4 | line: number; 5 | constructor(); 6 | get isAstNode(): boolean; 7 | get astNodeType(): string; 8 | search(callback: (node: BaseNode) => void): void; 9 | searchBlock(block: BaseNode[] | null, callback: (node: BaseNode) => void): void; 10 | } 11 | export declare class _BlockStart extends BaseNode { 12 | static instance: _BlockStart; 13 | } 14 | export declare class _BlockEnd extends BaseNode { 15 | static instance: _BlockEnd; 16 | } 17 | -------------------------------------------------------------------------------- /types/utils/base_node.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BaseNode { 2 | static _id: number; 3 | id: number; 4 | line: number; 5 | constructor(); 6 | get isAstNode(): boolean; 7 | get astNodeType(): string; 8 | search(callback: (node: BaseNode) => void): void; 9 | searchBlock(block: BaseNode[] | null, callback: (node: BaseNode) => void): void; 10 | } 11 | export declare class _BlockStart extends BaseNode { 12 | static instance: _BlockStart; 13 | } 14 | export declare class _BlockEnd extends BaseNode { 15 | static instance: _BlockEnd; 16 | } 17 | -------------------------------------------------------------------------------- /src/exec/exec_interface.ts: -------------------------------------------------------------------------------- 1 | import { Node, Type } from "../wgsl_ast.js"; 2 | import { ExecContext } from "./exec_context.js"; 3 | import { TypeInfo } from "../wgsl_reflect.js"; 4 | import { Data } from "../wgsl_ast.js"; 5 | 6 | export class ExecInterface { 7 | evalExpression(node: Node, context: ExecContext): Data | null { 8 | return null; 9 | } 10 | 11 | getTypeInfo(type: Type | string): TypeInfo | null { 12 | return null; 13 | } 14 | 15 | getVariableName(node: Node, context: ExecContext): string | null { 16 | return ""; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /types/exec/data.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeInfo } from "../reflect/info.js"; 2 | import { ExecContext } from "./exec_context.js"; 3 | import { ExecInterface } from "./exec_interface.js"; 4 | import { BaseNode } from "../ast/base_node.js"; 5 | export declare class Data { 6 | static _id: number; 7 | typeInfo: TypeInfo; 8 | parent: Data | null; 9 | id: number; 10 | constructor(typeInfo: TypeInfo, parent: Data | null); 11 | clone(): Data; 12 | setDataValue(exec: ExecInterface, value: Data, postfix: BaseNode | null, context: ExecContext): void; 13 | getSubData(exec: ExecInterface, postfix: BaseNode | null, context: ExecContext): Data | null; 14 | toString(): string; 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es5", "es6", "dom"], 5 | "noEmitHelpers": false, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "pretty": true, 9 | "allowUnreachableCode": true, 10 | "allowUnusedLabels": true, 11 | "noImplicitAny": false, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": false, 14 | "allowSyntheticDefaultImports": true, 15 | "sourceMap": true, 16 | "inlineSources": true, 17 | "moduleResolution": "node", 18 | "declaration": true, 19 | "declarationDir": "./types" 20 | }, 21 | "exclude": ["node_modules"], 22 | "files": [ 23 | "./src/index.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | .test_title { 2 | font-size: 24pt; 3 | color: blue; 4 | } 5 | 6 | .test_group { 7 | font-size: 24pt; 8 | margin-top: 30px; 9 | } 10 | 11 | .test_fail { 12 | color: red; 13 | font-size: 12pt; 14 | margin-left: 40px; 15 | } 16 | 17 | .test_pass { 18 | color: green; 19 | font-size: 12pt; 20 | margin-left: 40px; 21 | } 22 | 23 | .test_status_pass { 24 | color: green; 25 | font-size: 15pt; 26 | margin-left: 20px; 27 | font-weight: 300; 28 | } 29 | 30 | .test_status_fail { 31 | color: red; 32 | font-weight: 300; 33 | font-size: 15pt; 34 | margin-left: 20px; 35 | } 36 | 37 | .test_log { 38 | background-color: #ccc; 39 | margin-left: 40px; 40 | width: 100%; 41 | } 42 | 43 | .test_log_space { 44 | margin: 0; 45 | padding: 0; 46 | display: inline-block; 47 | width: 10px; 48 | } 49 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import terser from "@rollup/plugin-terser"; 3 | 4 | function build(input, format, file, sourcemap) { 5 | return { 6 | input, 7 | plugins: [ 8 | typescript(), 9 | terser({ 10 | ecma: 2020, 11 | compress: { 12 | module: true, 13 | toplevel: true, 14 | keep_classnames: true, 15 | unsafe_arrows: true, 16 | drop_console: false, 17 | drop_debugger: false 18 | }, 19 | output: { quote_style: 1 } 20 | }) 21 | ], 22 | output: [ { format, file, sourcemap, } ] 23 | }; 24 | } 25 | 26 | let builds = [ 27 | build('src/index.ts', 'esm', 'wgsl_reflect.module.js', true), 28 | build('src/index.ts', 'cjs', 'wgsl_reflect.node.js', true) 29 | ]; 30 | 31 | export default builds; 32 | -------------------------------------------------------------------------------- /test/test_node.js: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from 'node:fs/promises'; 2 | import process from 'node:process'; 3 | import {JSDOM as Jsdom} from 'jsdom' 4 | import { failedTests, runTests } from './test_all.js' 5 | import { create, globals as gpuGlobals } from 'webgpu'; 6 | 7 | Object.assign(globalThis, gpuGlobals); 8 | const dom = new Jsdom(await readFile('index.html')); 9 | const document = dom.window.document; 10 | // remove autorun script to harvest a static html result file 11 | for (const e of document.querySelectorAll("script")) { 12 | e.remove() 13 | } 14 | globalThis.document = document; 15 | globalThis.navigator.gpu = create([]); 16 | 17 | try { 18 | await runTests() 19 | const fileContents = dom.serialize() 20 | await writeFile("result.html", fileContents) 21 | } finally { 22 | // https://www.npmjs.com/package/webgpu#notes 23 | globalThis.document = null 24 | globalThis.navigator.gpu = null 25 | } 26 | if (failedTests()) { 27 | console.error("test errors:", failedTests()) 28 | process.exitCode = 1; 29 | } 30 | -------------------------------------------------------------------------------- /src/exec/stack_frame.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "./command.js"; 2 | import { CallExpr } from "../wgsl_ast.js"; 3 | import { ExecContext } from "./exec_context.js"; 4 | 5 | export class StackFrame { 6 | parent: StackFrame | null = null; 7 | context: ExecContext; 8 | commands: Command[] = []; 9 | current: number = 0; 10 | parentCallExpr: CallExpr | null = null; 11 | 12 | constructor(context: ExecContext, parent?: StackFrame) { 13 | this.context = context; 14 | this.parent = parent ?? null; 15 | } 16 | 17 | get isAtEnd(): boolean { return this.current >= this.commands.length; } 18 | 19 | getNextCommand(): Command | null { 20 | if (this.current >= this.commands.length) { 21 | return null; 22 | } 23 | const command = this.commands[this.current]; 24 | this.current++; 25 | return command; 26 | } 27 | 28 | getCurrentCommand(): Command | null { 29 | if (this.current >= this.commands.length) { 30 | return null; 31 | } 32 | return this.commands[this.current]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 Brendan Duncan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /types/exec/exec_context.d.ts: -------------------------------------------------------------------------------- 1 | import { Let, Var, Argument, Function } from "../wgsl_ast.js"; 2 | import { Data } from "../wgsl_ast.js"; 3 | type ASTVarNode = Let | Var | Argument; 4 | export declare class VarRef { 5 | name: string; 6 | value: Data; 7 | node: ASTVarNode | null; 8 | readonly id: number; 9 | constructor(n: string, v: Data, node: ASTVarNode | null); 10 | clone(): VarRef; 11 | } 12 | export declare class FunctionRef { 13 | name: string; 14 | node: Function; 15 | readonly id: number; 16 | constructor(node: Function); 17 | clone(): FunctionRef; 18 | } 19 | export declare class ExecContext { 20 | parent: ExecContext | null; 21 | variables: Map; 22 | functions: Map; 23 | currentFunctionName: string; 24 | readonly id: number; 25 | constructor(parent?: ExecContext); 26 | getVariable(name: string): VarRef | null; 27 | getFunction(name: string): FunctionRef | null; 28 | createVariable(name: string, value: Data, node?: ASTVarNode): void; 29 | setVariable(name: string, value: Data, node?: ASTVarNode): void; 30 | getVariableValue(name: string): Data | null; 31 | clone(): ExecContext; 32 | } 33 | export {}; 34 | -------------------------------------------------------------------------------- /test/test_all.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { shutdownDevice } from './test.js' 3 | import * as test from './test.js' 4 | 5 | import * as scanner from './tests/test_scanner.js' 6 | import * as parser from './tests/test_parser.js' 7 | import * as struct from './tests/struct.js' 8 | import * as reflect from './tests/test_reflect.js' 9 | import * as struct_layout from './tests/struct_layout.js' 10 | import * as exec from './tests/test_exec.js' 11 | import * as debug from './tests/test_debug.js' 12 | 13 | export function displayResults () { 14 | document.body.appendChild(document.createElement('p')) 15 | document.body.append(document.createElement('hr')) 16 | document.body.appendChild(document.createElement('p')) 17 | document.body.append( 18 | document.createTextNode('TOTAL TESTS: ' + test.__test.totalTests) 19 | ) 20 | document.body.appendChild(document.createElement('br')) 21 | document.body.append( 22 | document.createTextNode('FAILED TESTS: ' + test.__test.totalFailed) 23 | ) 24 | } 25 | 26 | export function failedTests() { 27 | return test.__test.totalFailed 28 | } 29 | 30 | export async function runTests () { 31 | try { 32 | await scanner.run(); 33 | await parser.run(); 34 | await struct.run(); 35 | await reflect.run(); 36 | await struct_layout.run(); 37 | await exec.run(); 38 | await debug.run(); 39 | displayResults(); 40 | } finally { 41 | await shutdownDevice(); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /types/exec/command.d.ts: -------------------------------------------------------------------------------- 1 | import { Node, CallExpr, Continue, Expression, Break } from "../wgsl_ast.js"; 2 | export declare class Command { 3 | get line(): number; 4 | } 5 | export declare class StatementCommand extends Command { 6 | node: Node; 7 | constructor(node: Node); 8 | get line(): number; 9 | } 10 | export declare class CallExprCommand extends Command { 11 | node: CallExpr; 12 | statement: Node; 13 | constructor(node: CallExpr, statement: Node); 14 | get line(): number; 15 | } 16 | export declare class ContinueTargetCommand extends Command { 17 | id: number; 18 | constructor(id: number); 19 | } 20 | export declare class BreakTargetCommand extends Command { 21 | id: number; 22 | constructor(id: number); 23 | } 24 | export declare class ContinueCommand extends Command { 25 | id: number; 26 | node: Continue; 27 | constructor(id: number, node: Continue); 28 | get line(): number; 29 | } 30 | export declare class BreakCommand extends Command { 31 | id: number; 32 | condition: Expression | null; 33 | node: Break; 34 | constructor(id: number, condition: Expression | null, node: Break); 35 | get line(): number; 36 | } 37 | export declare class GotoCommand extends Command { 38 | condition: Node | null; 39 | position: number; 40 | lineNo: number; 41 | constructor(condition: Node | null, position: number, line: number); 42 | get line(): number; 43 | } 44 | export declare class BlockCommand extends Command { 45 | statements: Array; 46 | constructor(statements: Array); 47 | get line(): number; 48 | } 49 | -------------------------------------------------------------------------------- /types/wgsl_debug.d.ts: -------------------------------------------------------------------------------- 1 | import * as AST from "./wgsl_ast.js"; 2 | import { WgslExec } from "./wgsl_exec.js"; 3 | import { ExecContext, FunctionRef } from "./exec/exec_context.js"; 4 | import { Command } from "./exec/command.js"; 5 | import { StackFrame } from "./exec/stack_frame.js"; 6 | import { ExecStack } from "./exec/exec_stack.js"; 7 | type RuntimeStateCallbackType = () => void; 8 | export declare class WgslDebug { 9 | _code: string; 10 | _exec: WgslExec; 11 | _execStack: ExecStack; 12 | _dispatchId: number[]; 13 | _runTimer: any; 14 | breakpoints: Set; 15 | runStateCallback: RuntimeStateCallbackType | null; 16 | constructor(code: string, runStateCallback?: RuntimeStateCallbackType); 17 | getVariableValue(name: string): number | number[] | null; 18 | reset(): void; 19 | startDebug(): void; 20 | get context(): ExecContext; 21 | get currentState(): StackFrame | null; 22 | get currentCommand(): Command | null; 23 | toggleBreakpoint(line: number): void; 24 | clearBreakpoints(): void; 25 | get isRunning(): boolean; 26 | run(): void; 27 | pause(): void; 28 | _setOverrides(constants: Object, context: ExecContext): void; 29 | debugWorkgroup(kernel: string, dispatchId: number[], dispatchCount: number | number[], bindGroups: Object, config?: Object): boolean; 30 | _shouldExecuteNextCommand(): boolean; 31 | stepInto(): void; 32 | stepOver(): void; 33 | stepOut(): void; 34 | stepNext(stepInto?: boolean): boolean; 35 | _dispatchWorkgroup(f: FunctionRef, workgroup_id: number[], context: ExecContext): boolean; 36 | _dispatchExec(f: FunctionRef, context: ExecContext): void; 37 | _createState(ast: AST.Node[], context: ExecContext, parent?: StackFrame): StackFrame; 38 | _collectFunctionCalls(node: AST.Expression, functionCalls: AST.CallExpr[]): void; 39 | } 40 | export {}; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wgsl_reflect", 3 | "version": "1.2.3", 4 | "description": "WGSL Parser and Reflection library", 5 | "author": "Brendan Duncan", 6 | "license": "MIT", 7 | "module": "wgsl_reflect.module.js", 8 | "main": "wgsl_reflect.node.js", 9 | "types": "types/index.d.ts", 10 | "type": "module", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/brendan-duncan/wgsl_reflect" 14 | }, 15 | "homepage": "https://github.com/brendan-duncan/wgsl_reflect", 16 | "bugs": { 17 | "url": "https://github.com/brendan-duncan/wgsl_reflect/issues" 18 | }, 19 | "sideEffects": false, 20 | "files": [ 21 | "wgsl_reflect.module.js", 22 | "wgsl_reflect.module.js.map", 23 | "wgsl_reflect.node.js", 24 | "wgsl_reflect.node.js.map", 25 | "LICENSE.md", 26 | "package.json", 27 | "README.md", 28 | "types" 29 | ], 30 | "directories": { 31 | "test": "test" 32 | }, 33 | "scripts": { 34 | "build": "rollup -c rollup.config.js", 35 | "watch": "npm run watch:js", 36 | "watch:js": "rollup -c rollup.config.js --watch", 37 | "test": "cd test; node test_node.js" 38 | }, 39 | "keywords": [ 40 | "wgsl_reflect", 41 | "webgpu", 42 | "wgsl", 43 | "javascript", 44 | "html5" 45 | ], 46 | 47 | "devDependencies": { 48 | "rollup": "^2.60.0", 49 | "tslib": "^2.6.1", 50 | "@rollup/plugin-typescript": "^11.1.2", 51 | "@rollup/plugin-terser": "^0.4.4", 52 | "webgpu": "^0.3.0", 53 | "jsdom": "^26.1.0" 54 | }, 55 | "jspm": { 56 | "files": [ 57 | "package.json", 58 | "LICENSE.md", 59 | "README.md", 60 | "wgsl_reflect.module.js", 61 | "wgsl_reflect.module.js.map", 62 | "wgsl_reflect.node.js", 63 | "wgsl_reflect.node.js.map", 64 | "types" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/cast.ts: -------------------------------------------------------------------------------- 1 | export function isArray(value: any): boolean { 2 | return Array.isArray(value) || value?.buffer instanceof ArrayBuffer; 3 | } 4 | 5 | export function isNumber(value: any): boolean { 6 | return typeof value === "number"; 7 | } 8 | 9 | const _f32 = new Float32Array(1); 10 | const _f32_i32 = new Uint32Array(_f32.buffer); 11 | const _f32_u32 = new Uint32Array(_f32.buffer); 12 | const _i32 = new Int32Array(1); 13 | const _i32_f32 = new Float32Array(_i32.buffer); 14 | const _i32_u32 = new Uint32Array(_i32.buffer); 15 | const _u32 = new Uint32Array(1); 16 | const _u32_f32 = new Float32Array(_u32.buffer); 17 | const _u32_i32 = new Int32Array(_u32.buffer); 18 | 19 | export function castScalar(v: number, from: string, to: string): number { 20 | if (from === to) { 21 | return v; 22 | } 23 | 24 | if (from === "f32") { 25 | if (to === "i32" || to === "x32") { 26 | _f32[0] = v; 27 | return _f32_i32[0]; 28 | } else if (to === "u32") { 29 | _f32[0] = v; 30 | return _f32_u32[0]; 31 | } 32 | } else if (from === "i32" || from === "x32") { 33 | if (to === "f32") { 34 | _i32[0] = v; 35 | return _i32_f32[0]; 36 | } else if (to === "u32") { 37 | _i32[0] = v; 38 | return _i32_u32[0]; 39 | } 40 | } else if (from === "u32") { 41 | if (to === "f32") { 42 | _u32[0] = v; 43 | return _u32_f32[0]; 44 | } else if (to === "i32" || to === "x32") { 45 | _u32[0] = v; 46 | return _u32_i32[0]; 47 | } 48 | } 49 | 50 | console.error(`Unsupported cast from ${from} to ${to}`); 51 | return v; 52 | } 53 | 54 | export function castVector(v: number[], from: string, to: string): number[] { 55 | if (from === to) { 56 | return v; 57 | } 58 | 59 | const cast = new Array(v.length); 60 | for (let i = 0; i < v.length; i++) { 61 | cast[i] = castScalar(v[i], from, to); 62 | } 63 | 64 | return cast; 65 | } 66 | -------------------------------------------------------------------------------- /test/tests/struct_layout.js: -------------------------------------------------------------------------------- 1 | import { test, group } from "../test.js"; 2 | import { WgslReflect } from "../../wgsl_reflect.module.js"; 3 | 4 | export async function run() { 5 | await group("struct_layout", async function () { 6 | const shader = ` 7 | struct A { // align(8) size(32) 8 | u: f32, // offset(0) align(4) size(4) 9 | v: f32, // offset(4) align(4) size(4) 10 | w: vec2, // offset(8) align(8) size(8) 11 | @size(16) x: f32 // offset(16) align(4) size(16) 12 | }; 13 | 14 | struct B { // align(16) size(208) 15 | a: vec2, // offset(0) align(8) size(8) 16 | // -- implicit member alignment padding -- // offset(8) size(8) 17 | b: vec3, // offset(16) align(16) size(12) 18 | c: f32, // offset(28) align(4) size(4) 19 | d: f32, // offset(32) align(4) size(4) 20 | // -- implicit member alignment padding -- // offset(36) size(12) 21 | @align(16) e: A, // offset(48) align(16) size(32) 22 | f: vec3, // offset(80) align(16) size(12) 23 | // -- implicit member alignment padding -- // offset(92) size(4) 24 | g: @stride(32) array, // offset(96) align(8) size(96) 25 | h: i32, // offset(192) align(4) size(4) 26 | // -- implicit struct size padding -- // offset(196) size(12) 27 | } 28 | 29 | @group(0) @binding(0) 30 | var uniform_buffer: B;`; 31 | 32 | let reflect = null; 33 | 34 | await test("reflect", function (test) { 35 | reflect = new WgslReflect(shader); 36 | test.equals(reflect.uniforms.length, 1); 37 | test.equals(reflect.structs.length, 2); 38 | }); 39 | 40 | await test("getMemberInfo(B)", function (test) { 41 | test.equals(reflect.uniforms[0].align, 16); 42 | test.equals(reflect.uniforms[0].size, 208); 43 | }); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /test/tests/struct.js: -------------------------------------------------------------------------------- 1 | import { test, group } from "../test.js"; 2 | import { WgslParser } from "../../wgsl_reflect.module.js"; 3 | 4 | export async function run() { 5 | await group("struct", async function () { 6 | const shader = ` 7 | struct S1 { 8 | a : i32, 9 | b : i32, 10 | c : i32, 11 | d : i32, // trailing comma 12 | } 13 | 14 | struct S2 { 15 | e : i32, 16 | f : S1 17 | } 18 | 19 | struct S3 { 20 | g : i32, 21 | h : S1, 22 | i : S2 23 | } 24 | 25 | struct T { 26 | a : array 27 | } 28 | 29 | @compute @workgroup_size(1) 30 | fn main() { 31 | let x : i32 = 42; 32 | 33 | // Test basic usage. 34 | let empty : S1 = S1(); 35 | let nonempty : S1 = S1(1, 2, 3, 4); 36 | let nonempty_with_expr : S1 = S1(1, x, x + 1, nonempty.d); 37 | 38 | // Test nested structs. 39 | let nested_empty : S3 = S3(); 40 | let nested_nonempty : S3 = S3(1, S1(2, 3, 4, 5), S2(6, S1(7, 8, 9, 10))); 41 | let nested_nonempty_with_expr : S3 = 42 | S3(1, S1(2, x, x + 1, nested_nonempty.i.f.d), S2(6, nonempty)); 43 | 44 | // Test use of constructors as sub-expressions. 45 | let subexpr_empty : i32 = S1().a; 46 | let subexpr_nonempty : i32 = S1(1, 2, 3, 4).b; 47 | let subexpr_nonempty_with_expr : i32 = S1(1, x, x + 1, nonempty.d).c; 48 | let subexpr_nested_empty : S1 = S2().f; 49 | let subexpr_nested_nonempty : S1 = S2(1, S1(2, 3, 4, 5)).f; 50 | let subexpr_nested_nonempty_with_expr : S1 = 51 | S2(1, S1(2, x, x + 1, nested_nonempty.i.f.d)).f; 52 | 53 | // Test arrays of structs containing arrays. 54 | let aosoa_empty : array = array(); 55 | let aosoa_nonempty : array = 56 | array( 57 | T(array(1, 2)), 58 | T(array(3, 4)), 59 | ); 60 | let aosoa_nonempty_with_expr : array = 61 | array( 62 | T(array(1, aosoa_nonempty[0].a[0] + 1)), 63 | aosoa_nonempty[1], 64 | ); 65 | }`; 66 | 67 | await test("type_constructor", function (test) { 68 | const parser = new WgslParser(); 69 | const t = parser.parse(shader); 70 | test.true(t.length > 0); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /types/utils/matrix.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeInfo } from '../reflect/info'; 2 | export declare const VectorTypeSize: { 3 | vec2: number; 4 | vec2f: number; 5 | vec2i: number; 6 | vec2u: number; 7 | vec2b: number; 8 | vec2h: number; 9 | vec3: number; 10 | vec3f: number; 11 | vec3i: number; 12 | vec3u: number; 13 | vec3b: number; 14 | vec3h: number; 15 | vec4: number; 16 | vec4f: number; 17 | vec4i: number; 18 | vec4u: number; 19 | vec4b: number; 20 | vec4h: number; 21 | }; 22 | export declare const MatrixTypeSize: { 23 | mat2x2: number[]; 24 | mat2x2f: number[]; 25 | mat2x2h: number[]; 26 | mat2x3: number[]; 27 | mat2x3f: number[]; 28 | mat2x3h: number[]; 29 | mat2x4: number[]; 30 | mat2x4f: number[]; 31 | mat2x4h: number[]; 32 | mat3x2: number[]; 33 | mat3x2f: number[]; 34 | mat3x2h: number[]; 35 | mat3x3: number[]; 36 | mat3x3f: number[]; 37 | mat3x3h: number[]; 38 | mat3x4: number[]; 39 | mat3x4f: number[]; 40 | mat3x4h: number[]; 41 | mat4x2: number[]; 42 | mat4x2f: number[]; 43 | mat4x2h: number[]; 44 | mat4x3: number[]; 45 | mat4x3f: number[]; 46 | mat4x3h: number[]; 47 | mat4x4: number[]; 48 | mat4x4f: number[]; 49 | mat4x4h: number[]; 50 | }; 51 | export declare const MatrixTransposeType: { 52 | mat2x2: string; 53 | mat2x2f: string; 54 | mat2x2h: string; 55 | mat2x3: string; 56 | mat2x3f: string; 57 | mat2x3h: string; 58 | mat2x4: string; 59 | mat2x4f: string; 60 | mat2x4h: string; 61 | mat3x2: string; 62 | mat3x2f: string; 63 | mat3x2h: string; 64 | mat3x3: string; 65 | mat3x3f: string; 66 | mat3x3h: string; 67 | mat3x4: string; 68 | mat3x4f: string; 69 | mat3x4h: string; 70 | mat4x2: string; 71 | mat4x2f: string; 72 | mat4x2h: string; 73 | mat4x3: string; 74 | mat4x3f: string; 75 | mat4x3h: string; 76 | mat4x4: string; 77 | mat4x4f: string; 78 | mat4x4h: string; 79 | }; 80 | export declare function matrixTranspose(matrix: number[], t: TypeInfo): number[]; 81 | export declare function matrixMultiply(matrixA: number[], t1: TypeInfo, matrixB: number[], t2: TypeInfo): number[] | null; 82 | export declare function matrixVectorMultiply(matrix: number[], t1: TypeInfo, vector: number[], t2: TypeInfo): number[] | null; 83 | export declare function vectorMatrixMultiply(vector: number[], t1: TypeInfo, matrix: number[], t2: TypeInfo): number[] | null; 84 | -------------------------------------------------------------------------------- /src/exec/command.ts: -------------------------------------------------------------------------------- 1 | import { Node, CallExpr, Continue, Expression, Break } from "../wgsl_ast.js"; 2 | 3 | export class Command { 4 | get line(): number { return -1; } 5 | } 6 | 7 | export class StatementCommand extends Command { 8 | node: Node; 9 | 10 | constructor(node: Node) { 11 | super(); 12 | this.node = node; 13 | } 14 | 15 | get line(): number { return this.node.line; } 16 | } 17 | 18 | export class CallExprCommand extends Command { 19 | node: CallExpr; 20 | statement: Node; 21 | 22 | constructor(node: CallExpr, statement: Node) { 23 | super(); 24 | this.node = node; 25 | this.statement = statement; 26 | } 27 | 28 | get line(): number { return this.statement.line; } 29 | } 30 | 31 | export class ContinueTargetCommand extends Command { 32 | id: number; 33 | 34 | constructor(id: number) { 35 | super(); 36 | this.id = id; 37 | } 38 | } 39 | 40 | export class BreakTargetCommand extends Command { 41 | id: number; 42 | 43 | constructor(id: number) { 44 | super(); 45 | this.id = id; 46 | } 47 | } 48 | 49 | export class ContinueCommand extends Command { 50 | id: number; 51 | node: Continue; 52 | 53 | constructor(id: number, node: Continue) { 54 | super(); 55 | this.id = id; 56 | this.node = node; 57 | } 58 | 59 | get line(): number { return this.node.line; } 60 | } 61 | 62 | export class BreakCommand extends Command { 63 | id: number; 64 | condition: Expression | null; 65 | node: Break; 66 | 67 | constructor(id: number, condition: Expression | null, node: Break) { 68 | super(); 69 | this.id = id; 70 | this.condition = condition; 71 | this.node = node; 72 | } 73 | 74 | get line(): number { return this.node.line; } 75 | } 76 | 77 | 78 | export class GotoCommand extends Command { 79 | condition: Node | null; 80 | position: number; 81 | lineNo: number = -1; 82 | 83 | constructor(condition: Node | null, position: number, line: number) { 84 | super(); 85 | this.condition = condition; 86 | this.position = position; 87 | this.lineNo = line; 88 | } 89 | 90 | get line(): number { 91 | return this.condition?.line ?? this.lineNo; 92 | } 93 | } 94 | 95 | export class BlockCommand extends Command { 96 | statements: Array = []; 97 | 98 | constructor(statements: Array) { 99 | super(); 100 | this.statements = statements; 101 | } 102 | 103 | get line(): number { 104 | return this.statements.length > 0 ? this.statements[0].line : -1; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/exec/exec_context.ts: -------------------------------------------------------------------------------- 1 | import { Let, Var, Argument, Function } from "../wgsl_ast.js"; 2 | import { Data } from "../wgsl_ast.js"; 3 | 4 | type ASTVarNode = Let | Var | Argument; 5 | 6 | let _id = 0; 7 | 8 | export class VarRef { 9 | name: string; 10 | value: Data; 11 | node: ASTVarNode | null; 12 | readonly id: number = _id++; 13 | 14 | constructor(n: string, v: Data, node: ASTVarNode | null) { 15 | this.name = n; 16 | this.value = v; 17 | this.node = node; 18 | } 19 | 20 | clone(): VarRef { 21 | return new VarRef(this.name, this.value, this.node); 22 | } 23 | }; 24 | 25 | export class FunctionRef { 26 | name: string; 27 | node: Function; 28 | readonly id: number = _id++; 29 | 30 | constructor(node: Function) { 31 | this.name = node.name; 32 | this.node = node; 33 | } 34 | 35 | clone(): FunctionRef { 36 | return new FunctionRef(this.node); 37 | } 38 | }; 39 | 40 | export class ExecContext { 41 | parent: ExecContext | null = null; 42 | variables = new Map(); 43 | functions = new Map(); 44 | currentFunctionName = ""; 45 | readonly id: number = _id++; 46 | 47 | constructor(parent?: ExecContext) { 48 | if (parent) { 49 | this.parent = parent; 50 | this.currentFunctionName = parent.currentFunctionName; 51 | } 52 | } 53 | 54 | getVariable(name: string): VarRef | null { 55 | if (this.variables.has(name)) { 56 | return this.variables.get(name) ?? null; 57 | } 58 | if (this.parent) { 59 | return this.parent.getVariable(name); 60 | } 61 | return null; 62 | } 63 | 64 | getFunction(name: string): FunctionRef | null { 65 | if (this.functions.has(name)) { 66 | return this.functions.get(name) ?? null; 67 | } 68 | if (this.parent) { 69 | return this.parent.getFunction(name); 70 | } 71 | return null 72 | } 73 | 74 | createVariable(name: string, value: Data, node?: ASTVarNode) { 75 | this.variables.set(name, new VarRef(name, value, node ?? null)); 76 | } 77 | 78 | setVariable(name: string, value: Data, node?: ASTVarNode) { 79 | const v = this.getVariable(name); 80 | if (v !== null) { 81 | v.value = value; 82 | } else { 83 | this.createVariable(name, value, node); 84 | } 85 | } 86 | 87 | getVariableValue(name: string): Data | null { 88 | const v = this.getVariable(name); 89 | return v?.value ?? null; 90 | } 91 | 92 | clone(): ExecContext { 93 | return new ExecContext(this); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/utils/float.ts: -------------------------------------------------------------------------------- 1 | // From https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript 2 | export function float16ToFloat32(float16: number): number { 3 | var s = (float16 & 0x8000) >> 15; 4 | var e = (float16 & 0x7C00) >> 10; 5 | var f = float16 & 0x03FF; 6 | 7 | if (e == 0) { 8 | return (s ? -1:1) * Math.pow(2, -14) * (f / Math.pow(2, 10)); 9 | } else if (e == 0x1F) { 10 | return f ? NaN : ((s ? -1 : 1) * Infinity); 11 | } 12 | 13 | return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10))); 14 | } 15 | 16 | const float32View = new Float32Array(1); 17 | const int32View = new Int32Array(float32View.buffer); 18 | const float16View = new Uint16Array(1); 19 | 20 | export function float32ToFloat16(float32: number): number { 21 | float32View[0] = float32; 22 | 23 | const f32 = int32View[0]; 24 | const sign = (f32 >> 31) & 1; 25 | let exponent = (f32 >> 23) & 0xff; 26 | let fraction = f32 & 0x7fffff; 27 | 28 | if (exponent === 0xff) { // Infinity or NaN 29 | float16View[0] = (sign << 15) | 0x7c00 | (fraction !== 0 ? 0x0200 : 0); 30 | return float16View[0]; 31 | } 32 | 33 | if (exponent === 0) { // Zero or subnormal 34 | if (fraction === 0) { // Zero 35 | float16View[0] = sign << 15; 36 | return float16View[0]; 37 | } 38 | // Subnormal 39 | fraction |= 0x800000; 40 | let shift = 113; 41 | while ((fraction & 0x800000) === 0) { 42 | fraction <<= 1; 43 | shift--; 44 | } 45 | exponent = 127 - shift; 46 | fraction &= 0x7fffff; 47 | if (exponent > 0) { 48 | fraction = (fraction >> (126 - exponent)) + ((fraction >> (127 - exponent)) & 1); 49 | float16View[0] = (sign << 15) | (exponent << 10) | (fraction >> 13); 50 | return float16View[0]; 51 | } else { 52 | float16View[0] = sign << 15; 53 | return float16View[0]; 54 | } 55 | } 56 | 57 | // Normalized 58 | exponent = exponent - 127 + 15; 59 | if (exponent >= 31) { // Overflow 60 | float16View[0] = (sign << 15) | 0x7c00; 61 | return float16View[0]; 62 | } 63 | if (exponent <= 0) { // Underflow 64 | if (exponent < -10) { 65 | float16View[0] = sign << 15; 66 | return float16View[0]; 67 | } 68 | fraction = (fraction | 0x800000) >> (1 - exponent); 69 | float16View[0] = (sign << 15) | (fraction >> 13); 70 | return float16View[0]; 71 | } 72 | 73 | fraction = fraction >> 13; 74 | float16View[0] = (sign << 15) | (exponent << 10) | fraction; 75 | return float16View[0]; 76 | } 77 | 78 | const uint32 = new Uint32Array(1); 79 | const uint32ToFloat32 = new Float32Array(uint32.buffer, 0, 1); 80 | 81 | export function float11ToFloat32(f11: number): number { 82 | const u32 = (((((f11) >> 6) & 0x1F) + (127 - 15)) << 23) | (((f11) & 0x3F) << 17); 83 | uint32[0] = u32; 84 | return uint32ToFloat32[0]; 85 | } 86 | 87 | export function float10ToFloat32(f10: number): number { 88 | const u32 = (((((f10) >> 5) & 0x1F) + (127 - 15)) << 23) | (((f10) & 0x1F) << 18); 89 | uint32[0] = u32; 90 | return uint32ToFloat32[0]; 91 | } 92 | -------------------------------------------------------------------------------- /types/wgsl_exec.d.ts: -------------------------------------------------------------------------------- 1 | import { Node, Type, Let, Var, Const, If, For, While, Loop, Assign, Increment, Override, Call, BinaryOperator, LiteralExpr, VariableExpr, CallExpr, CreateExpr, ConstExpr, BitcastExpr, UnaryOperator, Function, Switch } from "./wgsl_ast.js"; 2 | import { Data } from "./wgsl_ast.js"; 3 | import { Reflect } from "./reflect/reflect.js"; 4 | import { TypeInfo } from "./reflect/info.js"; 5 | import { ExecContext, FunctionRef } from "./exec/exec_context.js"; 6 | import { ExecInterface } from "./exec/exec_interface.js"; 7 | import { BuiltinFunctions } from "./exec/builtin_functions.js"; 8 | export declare class WgslExec extends ExecInterface { 9 | ast: Node[]; 10 | context: ExecContext; 11 | reflection: Reflect; 12 | builtins: BuiltinFunctions; 13 | typeInfo: Object; 14 | constructor(ast?: Node[], context?: ExecContext); 15 | getVariableValue(name: string): number | number[] | null; 16 | execute(config?: Object): void; 17 | dispatchWorkgroups(kernel: string, dispatchCount: number | number[], bindGroups: Object, config?: Object): void; 18 | static _breakObj: Data; 19 | static _continueObj: Data; 20 | execStatement(stmt: Node, context: ExecContext): Data | null; 21 | evalExpression(node: Node, context: ExecContext): Data | null; 22 | getTypeInfo(type: Type | string): TypeInfo | null; 23 | _setOverrides(constants: Object, context: ExecContext): void; 24 | _dispatchWorkgroup(f: FunctionRef, workgroup_id: number[], context: ExecContext): void; 25 | _dispatchExec(f: FunctionRef, context: ExecContext): void; 26 | getVariableName(node: Node, context: ExecContext): string | null; 27 | _execStatements(statements: Node[], context: ExecContext): Data | null; 28 | _call(node: Call, context: ExecContext): void; 29 | _increment(node: Increment, context: ExecContext): void; 30 | _getVariableData(node: Node, context: ExecContext): Data | null; 31 | _assign(node: Assign, context: ExecContext): void; 32 | _function(node: Function, context: ExecContext): void; 33 | _const(node: Const, context: ExecContext): void; 34 | _override(node: Override, context: ExecContext): void; 35 | _let(node: Let, context: ExecContext): void; 36 | _var(node: Var, context: ExecContext): void; 37 | _switch(node: Switch, context: ExecContext): Data | null; 38 | _if(node: If, context: ExecContext): Data | null; 39 | _getScalarValue(v: Data | null): number; 40 | _for(node: For, context: ExecContext): Data | null; 41 | _loop(node: Loop, context: ExecContext): Data | null; 42 | _while(node: While, context: ExecContext): Data | null; 43 | _evalBitcast(node: BitcastExpr, context: ExecContext): Data | null; 44 | _evalConst(node: ConstExpr, context: ExecContext): Data | null; 45 | _evalCreate(node: CreateExpr | CallExpr | Call, context: ExecContext): Data | null; 46 | _evalLiteral(node: LiteralExpr, context: ExecContext): Data | null; 47 | _evalVariable(node: VariableExpr, context: ExecContext): Data | null; 48 | static _priority: Map; 49 | _maxFormatTypeInfo(x: TypeInfo[]): TypeInfo | null; 50 | _evalUnaryOp(node: UnaryOperator, context: ExecContext): Data | null; 51 | _isMatrixType(data: Data): boolean; 52 | _isVectorType(data: Data): boolean; 53 | _evalBinaryOp(node: BinaryOperator, context: ExecContext): Data | null; 54 | _evalCall(node: CallExpr, context: ExecContext): Data | null; 55 | _callBuiltinFunction(node: CallExpr | Call, context: ExecContext): Data | null; 56 | _callConstructorValue(node: CreateExpr, context: ExecContext): Data | null; 57 | _callConstructorVec(node: CreateExpr | LiteralExpr, context: ExecContext): Data | null; 58 | _callConstructorMatrix(node: CreateExpr | LiteralExpr, context: ExecContext): Data | null; 59 | } 60 | -------------------------------------------------------------------------------- /types/exec/typed_data.d.ts: -------------------------------------------------------------------------------- 1 | import { Data } from "./data.js"; 2 | import { TypeInfo } from "../reflect/info.js"; 3 | import { ExecContext } from "./exec_context.js"; 4 | import { ExecInterface } from "./exec_interface.js"; 5 | import { Expression } from "../wgsl_ast.js"; 6 | export declare class VoidData extends Data { 7 | constructor(); 8 | static void: VoidData; 9 | toString(): string; 10 | } 11 | export declare class PointerData extends Data { 12 | reference: Data; 13 | constructor(reference: Data); 14 | clone(): Data; 15 | setDataValue(exec: ExecInterface, value: Data, postfix: Expression | null, context: ExecContext): void; 16 | getSubData(exec: ExecInterface, postfix: Expression | null, context: ExecContext): Data | null; 17 | } 18 | export declare class ScalarData extends Data { 19 | data: Int32Array | Uint32Array | Float32Array; 20 | constructor(value: number | Int32Array | Uint32Array | Float32Array, typeInfo: TypeInfo, parent?: Data | null); 21 | clone(): Data; 22 | get value(): number; 23 | set value(v: number); 24 | setDataValue(exec: ExecInterface, value: Data, postfix: Expression | null, context: ExecContext): void; 25 | getSubData(exec: ExecInterface, postfix: Expression | null, context: ExecContext): Data | null; 26 | toString(): string; 27 | } 28 | export declare class VectorData extends Data { 29 | data: Int32Array | Uint32Array | Float32Array; 30 | constructor(value: number[] | Float32Array | Uint32Array | Int32Array, typeInfo: TypeInfo, parent?: Data | null); 31 | clone(): Data; 32 | setDataValue(exec: ExecInterface, value: Data, postfix: Expression | null, context: ExecContext): void; 33 | getSubData(exec: ExecInterface, postfix: Expression | null, context: ExecContext): Data | null; 34 | toString(): string; 35 | } 36 | export declare class MatrixData extends Data { 37 | data: Float32Array; 38 | constructor(value: number[] | Float32Array, typeInfo: TypeInfo, parent?: Data | null); 39 | clone(): Data; 40 | setDataValue(exec: ExecInterface, value: Data, postfix: Expression | null, context: ExecContext): void; 41 | getSubData(exec: ExecInterface, postfix: Expression | null, context: ExecContext): Data | null; 42 | toString(): string; 43 | } 44 | export declare class TypedData extends Data { 45 | buffer: ArrayBuffer; 46 | offset: number; 47 | constructor(data: ArrayBuffer | Float32Array | Uint32Array | Int32Array | Uint8Array | Int8Array, typeInfo: TypeInfo, offset?: number, parent?: Data | null); 48 | clone(): Data; 49 | setDataValue(exec: ExecInterface, value: Data, postfix: Expression | null, context: ExecContext): void; 50 | setData(exec: ExecInterface, value: Data, typeInfo: TypeInfo, offset: number, context: ExecContext): void; 51 | getSubData(exec: ExecInterface, postfix: Expression | null, context: ExecContext): Data | null; 52 | toString(): string; 53 | } 54 | export declare class TextureData extends TypedData { 55 | descriptor: Object; 56 | view: Object | null; 57 | constructor(data: ArrayBuffer | Float32Array | Uint32Array | Int32Array | Uint8Array | Int8Array, typeInfo: TypeInfo, offset: number, descriptor: Object, view: Object | null); 58 | clone(): Data; 59 | get width(): number; 60 | get height(): number; 61 | get depthOrArrayLayers(): number; 62 | get format(): string; 63 | get sampleCount(): number; 64 | get mipLevelCount(): number; 65 | get dimension(): string; 66 | getMipLevelSize(level: number): number[]; 67 | get texelByteSize(): number; 68 | get bytesPerRow(): number; 69 | get isDepthStencil(): boolean; 70 | getGpuSize(): number; 71 | getPixel(x: number, y: number, z?: number, mipLevel?: number): number[] | null; 72 | setPixel(x: number, y: number, z: number, mipLevel: number, value: number[]): void; 73 | } 74 | -------------------------------------------------------------------------------- /types/wgsl_parser.d.ts: -------------------------------------------------------------------------------- 1 | import { Token, TokenType } from "./wgsl_scanner.js"; 2 | import * as AST from "./wgsl_ast.js"; 3 | import { WgslExec } from "./wgsl_exec.js"; 4 | import { ParseContext } from "./ast/parse_context.js"; 5 | export declare class WgslParser { 6 | _tokens: Token[]; 7 | _current: number; 8 | _currentLine: number; 9 | _deferArrayCountEval: Object[]; 10 | _currentLoop: AST.Statement[]; 11 | _context: ParseContext; 12 | _exec: WgslExec; 13 | _forwardTypeCount: number; 14 | parse(tokensOrCode: Token[] | string): AST.Statement[]; 15 | _forwardType(t: AST.Type | null): AST.Type | null; 16 | _initialize(tokensOrCode: Token[] | string): void; 17 | _updateNode(n: T, l?: number): T; 18 | _error(token: Token, message: string | null): Object; 19 | _isAtEnd(): boolean; 20 | _match(types: TokenType | TokenType[]): boolean; 21 | _consume(types: TokenType | TokenType[], message: string | null): Token; 22 | _check(types: TokenType | TokenType[]): boolean; 23 | _advance(): Token; 24 | _peek(): Token; 25 | _previous(): Token; 26 | _global_decl_or_directive(): AST.Statement | null; 27 | _function_decl(): AST.Function | null; 28 | _compound_statement(): AST.Statement[]; 29 | _statement(): AST.Statement | AST.Statement[] | null; 30 | _static_assert_statement(): AST.StaticAssert | null; 31 | _while_statement(): AST.While | null; 32 | _continuing_statement(): AST.Continuing | null; 33 | _for_statement(): AST.For | null; 34 | _for_init(): AST.Statement | null; 35 | _for_increment(): AST.Statement | null; 36 | _variable_statement(): AST.Var | AST.Let | AST.Const | null; 37 | _increment_decrement_statement(): AST.Statement | null; 38 | _assignment_statement(): AST.Assign | null; 39 | _func_call_statement(): AST.Call | null; 40 | _loop_statement(): AST.Loop | null; 41 | _switch_statement(): AST.Switch | null; 42 | _switch_body(): AST.SwitchCase[]; 43 | _case_selectors(): AST.Expression[]; 44 | _case_body(): AST.Statement[]; 45 | _if_statement(): AST.If | null; 46 | _match_elseif(): boolean; 47 | _elseif_statement(elseif?: AST.ElseIf[]): AST.ElseIf[]; 48 | _return_statement(): AST.Return | null; 49 | _short_circuit_or_expression(): AST.Expression; 50 | _short_circuit_and_expr(): AST.Expression; 51 | _inclusive_or_expression(): AST.Expression; 52 | _exclusive_or_expression(): AST.Expression; 53 | _and_expression(): AST.Expression; 54 | _equality_expression(): AST.Expression; 55 | _relational_expression(): AST.Expression; 56 | _shift_expression(): AST.Expression; 57 | _additive_expression(): AST.Expression; 58 | _multiplicative_expression(): AST.Expression; 59 | _unary_expression(): AST.Expression; 60 | _singular_expression(): AST.Expression; 61 | _postfix_expression(): AST.Expression | null; 62 | _getStruct(name: string): AST.Type | null; 63 | _getType(name: string): AST.Type; 64 | _validateTypeRange(value: number, type: AST.Type): void; 65 | _primary_expression(): AST.Expression; 66 | _argument_expression_list(): AST.Expression[] | null; 67 | _optional_paren_expression(): AST.Expression; 68 | _paren_expression(): AST.Expression; 69 | _struct_decl(): AST.Struct | null; 70 | _global_variable_decl(): AST.Var | null; 71 | _override_variable_decl(): AST.Override | null; 72 | _global_const_decl(): AST.Const | null; 73 | _global_let_decl(): AST.Let | null; 74 | _const_expression(): AST.Expression; 75 | _variable_decl(): AST.Var | null; 76 | _override_decl(): AST.Override | null; 77 | _diagnostic(): AST.Diagnostic | null; 78 | _enable_directive(): AST.Enable; 79 | _requires_directive(): AST.Requires; 80 | _type_alias(): AST.Alias; 81 | _type_decl(): AST.Type | null; 82 | _texture_sampler_types(): AST.SamplerType | null; 83 | _attribute(): AST.Attribute[] | null; 84 | } 85 | -------------------------------------------------------------------------------- /types/reflect/info.d.ts: -------------------------------------------------------------------------------- 1 | import { Attribute } from "../wgsl_ast.js"; 2 | export declare class TypeInfo { 3 | name: string; 4 | attributes: Attribute[] | null; 5 | size: number; 6 | constructor(name: string, attributes: Attribute[] | null); 7 | get isArray(): boolean; 8 | get isStruct(): boolean; 9 | get isTemplate(): boolean; 10 | get isPointer(): boolean; 11 | getTypeName(): string; 12 | } 13 | export declare class MemberInfo { 14 | name: string; 15 | type: TypeInfo; 16 | attributes: Attribute[] | null; 17 | offset: number; 18 | size: number; 19 | constructor(name: string, type: TypeInfo, attributes: Attribute[] | null); 20 | get isArray(): boolean; 21 | get isStruct(): boolean; 22 | get isTemplate(): boolean; 23 | get align(): number; 24 | get members(): MemberInfo[] | null; 25 | get format(): TypeInfo | null; 26 | get count(): number; 27 | get stride(): number; 28 | } 29 | export declare class StructInfo extends TypeInfo { 30 | members: MemberInfo[]; 31 | align: number; 32 | startLine: number; 33 | endLine: number; 34 | inUse: boolean; 35 | constructor(name: string, attributes: Attribute[] | null); 36 | get isStruct(): boolean; 37 | } 38 | export declare class ArrayInfo extends TypeInfo { 39 | format: TypeInfo; 40 | count: number; 41 | stride: number; 42 | constructor(name: string, attributes: Attribute[] | null); 43 | get isArray(): boolean; 44 | getTypeName(): string; 45 | } 46 | export declare class PointerInfo extends TypeInfo { 47 | format: TypeInfo; 48 | constructor(name: string, format: TypeInfo, attributes: Attribute[] | null); 49 | get isPointer(): boolean; 50 | getTypeName(): string; 51 | } 52 | export declare class TemplateInfo extends TypeInfo { 53 | format: TypeInfo | null; 54 | access: string; 55 | constructor(name: string, format: TypeInfo | null, attributes: Attribute[] | null, access: string); 56 | get isTemplate(): boolean; 57 | getTypeName(): string; 58 | } 59 | export declare enum ResourceType { 60 | Uniform = 0, 61 | Storage = 1, 62 | Texture = 2, 63 | Sampler = 3, 64 | StorageTexture = 4 65 | } 66 | export declare class VariableInfo { 67 | attributes: Attribute[] | null; 68 | name: string; 69 | type: TypeInfo; 70 | group: number; 71 | binding: number; 72 | resourceType: ResourceType; 73 | access: string; 74 | relations: Array | null; 75 | constructor(name: string, type: TypeInfo, group: number, binding: number, attributes: Attribute[] | null, resourceType: ResourceType, access: string); 76 | get isArray(): boolean; 77 | get isStruct(): boolean; 78 | get isTemplate(): boolean; 79 | get size(): number; 80 | get align(): number; 81 | get members(): MemberInfo[] | null; 82 | get format(): TypeInfo | null; 83 | get count(): number; 84 | get stride(): number; 85 | } 86 | export declare class AliasInfo { 87 | name: string; 88 | type: TypeInfo; 89 | constructor(name: string, type: TypeInfo); 90 | } 91 | export declare class InputInfo { 92 | name: string; 93 | type: TypeInfo | null; 94 | locationType: string; 95 | location: number | string; 96 | interpolation: string | null; 97 | constructor(name: string, type: TypeInfo | null, locationType: string, location: number | string); 98 | } 99 | export declare class OutputInfo { 100 | name: string; 101 | type: TypeInfo | null; 102 | locationType: string; 103 | location: number | string; 104 | constructor(name: string, type: TypeInfo | null, locationType: string, location: number | string); 105 | } 106 | export declare class OverrideInfo { 107 | name: string; 108 | type: TypeInfo | null; 109 | attributes: Attribute[] | null; 110 | id: number; 111 | constructor(name: string, type: TypeInfo | null, attributes: Attribute[] | null, id: number); 112 | } 113 | export declare class ArgumentInfo { 114 | name: string; 115 | type: TypeInfo; 116 | attributes: Attribute[] | null; 117 | constructor(name: string, type: TypeInfo, attributes: Attribute[] | null); 118 | } 119 | export declare class FunctionInfo { 120 | name: string; 121 | stage: string | null; 122 | inputs: InputInfo[]; 123 | outputs: OutputInfo[]; 124 | arguments: ArgumentInfo[]; 125 | returnType: TypeInfo | null; 126 | resources: VariableInfo[]; 127 | overrides: OverrideInfo[]; 128 | attributes: Attribute[] | null; 129 | startLine: number; 130 | endLine: number; 131 | inUse: boolean; 132 | calls: Set; 133 | constructor(name: string, stage: string | null, attributes: Attribute[] | null); 134 | } 135 | export declare class EntryFunctions { 136 | vertex: FunctionInfo[]; 137 | fragment: FunctionInfo[]; 138 | compute: FunctionInfo[]; 139 | } 140 | -------------------------------------------------------------------------------- /src/utils/matrix.ts: -------------------------------------------------------------------------------- 1 | import { TypeInfo } from '../reflect/info'; 2 | 3 | export const VectorTypeSize = { 4 | "vec2": 2, "vec2f": 2, "vec2i": 2, "vec2u": 2, "vec2b": 2, "vec2h": 2, 5 | "vec3": 3, "vec3f": 3, "vec3i": 3, "vec3u": 3, "vec3b": 3, "vec3h": 3, 6 | "vec4": 4, "vec4f": 4, "vec4i": 4, "vec4u": 4, "vec4b": 4, "vec4h": 4 7 | }; 8 | 9 | export const MatrixTypeSize = { 10 | "mat2x2": [2, 2, 4], "mat2x2f": [2, 2, 4], "mat2x2h": [2, 2, 4], 11 | "mat2x3": [2, 3, 6], "mat2x3f": [2, 3, 6], "mat2x3h": [2, 3, 6], 12 | "mat2x4": [2, 4, 8], "mat2x4f": [2, 4, 8], "mat2x4h": [2, 4, 8], 13 | "mat3x2": [3, 2, 6], "mat3x2f": [3, 2, 6], "mat3x2h": [3, 2, 6], 14 | "mat3x3": [3, 3, 9], "mat3x3f": [3, 3, 9], "mat3x3h": [3, 3, 9], 15 | "mat3x4": [3, 4, 12], "mat3x4f": [3, 4, 12], "mat3x4h": [3, 4, 12], 16 | "mat4x2": [4, 2, 8], "mat4x2f": [4, 2, 8], "mat4x2h": [4, 2, 8], 17 | "mat4x3": [4, 3, 12], "mat4x3f": [4, 3, 12], "mat4x3h": [4, 3, 12], 18 | "mat4x4": [4, 4, 16], "mat4x4f": [4, 4, 16], "mat4x4h": [4, 4, 16] 19 | }; 20 | 21 | export const MatrixTransposeType = { 22 | "mat2x2": "mat2x2", "mat2x2f": "mat2x2f", "mat2x2h": "mat2x2h", 23 | "mat2x3": "mat3x2", "mat2x3f": "mat3x2f", "mat2x3h": "mat3x2h", 24 | "mat2x4": "mat4x2", "mat2x4f": "mat4x2f", "mat2x4h": "mat4x2h", 25 | "mat3x2": "mat2x3", "mat3x2f": "mat2x3f", "mat3x2h": "mat2x3h", 26 | "mat3x3": "mat3x3", "mat3x3f": "mat3x3f", "mat3x3h": "mat3x3h", 27 | "mat3x4": "mat4x3", "mat3x4f": "mat4x3f", "mat3x4h": "mat4x3h", 28 | "mat4x2": "mat2x4", "mat4x2f": "mat2x4f", "mat4x2h": "mat2x4h", 29 | "mat4x3": "mat4x3", "mat4x3f": "mat4x3f", "mat4x3h": "mat4x3h", 30 | "mat4x4": "mat4x4", "mat4x4f": "mat4x4f", "mat4x4h": "mat4x4h" 31 | }; 32 | 33 | export function matrixTranspose(matrix: number[], t: TypeInfo) { 34 | if (MatrixTypeSize[t.name] === undefined) { 35 | return null; 36 | } 37 | 38 | const cols = MatrixTypeSize[t.name][0]; 39 | const rows = MatrixTypeSize[t.name][1]; 40 | const result: number[] = []; 41 | 42 | for (let i = 0; i < cols; i++) { 43 | for (let j = 0; j < rows; j++) { 44 | result[i * rows + j] = matrix[j * cols + i]; 45 | } 46 | } 47 | 48 | return result; 49 | } 50 | 51 | export function matrixMultiply(matrixA: number[], t1: TypeInfo, matrixB: number[], t2: TypeInfo): number[] | null { 52 | if (MatrixTypeSize[t1.name] === undefined || MatrixTypeSize[t2.name] === undefined) { 53 | return null; 54 | } 55 | 56 | const k = MatrixTypeSize[t1.name][0]; 57 | const r = MatrixTypeSize[t1.name][1]; 58 | const c = MatrixTypeSize[t2.name][0]; 59 | const k2 = MatrixTypeSize[t2.name][1]; 60 | 61 | if (k !== k2) { 62 | return null; 63 | } 64 | 65 | const result: number[] = new Array(c * r); 66 | 67 | for (let j = 0; j < r; j++) { // Iterate through columns of result 68 | for (let i = 0; i < c; i++) { // Iterate through rows of result 69 | let sum = 0; 70 | for (let l = 0; l < k; l++) { 71 | sum += matrixA[l * r + j] * matrixB[i * k + l]; // Access column-major elements 72 | } 73 | result[j * c + i] = sum; // Store in column-major order 74 | } 75 | } 76 | 77 | return result; 78 | } 79 | 80 | export function matrixVectorMultiply(matrix: number[], t1: TypeInfo, vector: number[], t2: TypeInfo): number[] | null { 81 | if (MatrixTypeSize[t1.name] === undefined || VectorTypeSize[t2.name] === undefined) { 82 | return null; 83 | } 84 | 85 | const cols = MatrixTypeSize[t1.name][0]; 86 | const rows = MatrixTypeSize[t1.name][1]; 87 | 88 | if (cols !== vector.length) { 89 | return null; 90 | } 91 | 92 | const resultVec = new Array(rows); 93 | // Perform matrix-vector multiplication (column-major) 94 | for (let i = 0; i < rows; i++) { 95 | let sum = 0; 96 | for (let j = 0; j < cols; j++) { 97 | sum += matrix[j * rows + i] * vector[j]; // Access column-major element 98 | } 99 | resultVec[i] = sum; 100 | } 101 | 102 | return resultVec; 103 | } 104 | 105 | export function vectorMatrixMultiply(vector: number[], t1: TypeInfo, matrix: number[], t2: TypeInfo): number[] | null { 106 | if (VectorTypeSize[t1.name] === undefined || MatrixTypeSize[t2.name] === undefined) { 107 | return null; 108 | } 109 | 110 | const cols = MatrixTypeSize[t2.name][0]; 111 | const rows = MatrixTypeSize[t2.name][1]; 112 | 113 | if (rows !== vector.length) { 114 | return null; 115 | } 116 | 117 | const result: number[] = []; 118 | for (let j = 0; j < cols; j++) { 119 | let sum = 0; 120 | for (let i = 0; i < rows; i++) { 121 | sum += vector[i] * matrix[i * cols + j]; 122 | } 123 | result[j] = sum; 124 | } 125 | 126 | return result; 127 | } 128 | -------------------------------------------------------------------------------- /types/reflect/reflect.d.ts: -------------------------------------------------------------------------------- 1 | import { Type, Struct, Alias, Node, Function, Argument, Member, Attribute } from "../wgsl_ast.js"; 2 | import { FunctionInfo, VariableInfo, AliasInfo, OverrideInfo, StructInfo, TypeInfo, MemberInfo, OutputInfo, InputInfo, EntryFunctions } from "./info.js"; 3 | declare class _FunctionResources { 4 | node: Function; 5 | resources: VariableInfo[] | null; 6 | inUse: boolean; 7 | info: FunctionInfo | null; 8 | constructor(node: Function); 9 | } 10 | declare class _TypeSize { 11 | align: number; 12 | size: number; 13 | constructor(align: number, size: number); 14 | } 15 | export declare class Reflect { 16 | uniforms: VariableInfo[]; 17 | storage: VariableInfo[]; 18 | textures: VariableInfo[]; 19 | samplers: VariableInfo[]; 20 | aliases: AliasInfo[]; 21 | overrides: OverrideInfo[]; 22 | structs: StructInfo[]; 23 | entry: EntryFunctions; 24 | functions: FunctionInfo[]; 25 | _types: Map; 26 | _functions: Map; 27 | _isStorageTexture(type: TypeInfo): boolean; 28 | updateAST(ast: Node[]): void; 29 | getFunctionInfo(name: string): FunctionInfo | null; 30 | getStructInfo(name: string): StructInfo | null; 31 | getOverrideInfo(name: string): OverrideInfo | null; 32 | _markStructsInUse(type: TypeInfo): void; 33 | _addCalls(fn: Function, calls: Set): void; 34 | findResource(group: number, binding: number, entry?: string): VariableInfo; 35 | _findResource(name: string): VariableInfo | null; 36 | _markStructsFromAST(type: Type): void; 37 | _findResources(fn: Node, isEntry: boolean): VariableInfo[]; 38 | getBindGroups(): Array; 39 | _getOutputs(type: Type, outputs?: OutputInfo[] | undefined): OutputInfo[]; 40 | _getStructOutputs(struct: Struct, outputs: OutputInfo[]): void; 41 | _getOutputInfo(type: Type): OutputInfo | null; 42 | _getInputs(args: Argument[], inputs?: InputInfo[] | undefined): InputInfo[]; 43 | _getStructInputs(struct: Struct, inputs: InputInfo[]): void; 44 | _getInputInfo(node: Member | Argument): InputInfo | null; 45 | _parseString(s: string | string[]): string; 46 | _parseInt(s: string | string[]): number | string; 47 | _getAlias(name: string): TypeInfo | null; 48 | _getAliasInfo(node: Alias): AliasInfo; 49 | getTypeInfoByName(name: string): TypeInfo | null; 50 | getTypeInfo(type: Type, attributes?: Attribute[] | null): TypeInfo; 51 | _updateTypeInfo(type: TypeInfo): void; 52 | _updateStructInfo(struct: StructInfo): void; 53 | _getTypeSize(type: TypeInfo | MemberInfo | null | undefined): _TypeSize | null; 54 | _isUniformVar(node: Node): boolean; 55 | _isStorageVar(node: Node): boolean; 56 | _isTextureVar(node: Node): boolean; 57 | _isSamplerVar(node: Node): boolean; 58 | _getAttribute(node: Node, name: string): Attribute | null; 59 | _getAttributeNum(attributes: Attribute[] | null, name: string, defaultValue: number): number; 60 | _roundUp(k: number, n: number): number; 61 | static readonly _typeInfo: { 62 | f16: { 63 | align: number; 64 | size: number; 65 | }; 66 | i32: { 67 | align: number; 68 | size: number; 69 | }; 70 | u32: { 71 | align: number; 72 | size: number; 73 | }; 74 | f32: { 75 | align: number; 76 | size: number; 77 | }; 78 | atomic: { 79 | align: number; 80 | size: number; 81 | }; 82 | vec2: { 83 | align: number; 84 | size: number; 85 | }; 86 | vec3: { 87 | align: number; 88 | size: number; 89 | }; 90 | vec4: { 91 | align: number; 92 | size: number; 93 | }; 94 | mat2x2: { 95 | align: number; 96 | size: number; 97 | }; 98 | mat3x2: { 99 | align: number; 100 | size: number; 101 | }; 102 | mat4x2: { 103 | align: number; 104 | size: number; 105 | }; 106 | mat2x3: { 107 | align: number; 108 | size: number; 109 | }; 110 | mat3x3: { 111 | align: number; 112 | size: number; 113 | }; 114 | mat4x3: { 115 | align: number; 116 | size: number; 117 | }; 118 | mat2x4: { 119 | align: number; 120 | size: number; 121 | }; 122 | mat3x4: { 123 | align: number; 124 | size: number; 125 | }; 126 | mat4x4: { 127 | align: number; 128 | size: number; 129 | }; 130 | }; 131 | static readonly _textureTypes: string[]; 132 | static readonly _samplerTypes: string[]; 133 | } 134 | export {}; 135 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

WGSL Reflection Library

4 |

Shader

5 | 6 | 7 |

Reflection Info

8 |
11 | 12 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /src/reflect/info.ts: -------------------------------------------------------------------------------- 1 | import { Attribute } from "../wgsl_ast.js"; 2 | 3 | export class TypeInfo { 4 | name: string; 5 | attributes: Attribute[] | null; 6 | size: number; 7 | 8 | constructor(name: string, attributes: Attribute[] | null) { 9 | this.name = name; 10 | this.attributes = attributes; 11 | this.size = 0; 12 | } 13 | 14 | get isArray(): boolean { 15 | return false; 16 | } 17 | 18 | get isStruct(): boolean { 19 | return false; 20 | } 21 | 22 | get isTemplate(): boolean { 23 | return false; 24 | } 25 | 26 | get isPointer(): boolean { 27 | return false; 28 | } 29 | 30 | getTypeName(): string { 31 | return this.name; 32 | } 33 | } 34 | 35 | export class MemberInfo { 36 | name: string; 37 | type: TypeInfo; 38 | attributes: Attribute[] | null; 39 | offset: number; 40 | size: number; 41 | 42 | constructor( 43 | name: string, 44 | type: TypeInfo, 45 | attributes: Attribute[] | null 46 | ) { 47 | this.name = name; 48 | this.type = type; 49 | this.attributes = attributes; 50 | this.offset = 0; 51 | this.size = 0; 52 | } 53 | 54 | get isArray(): boolean { 55 | return this.type.isArray; 56 | } 57 | 58 | get isStruct(): boolean { 59 | return this.type.isStruct; 60 | } 61 | 62 | get isTemplate(): boolean { 63 | return this.type.isTemplate; 64 | } 65 | 66 | get align(): number { 67 | return this.type.isStruct ? (this.type as StructInfo).align : 0; 68 | } 69 | 70 | get members(): MemberInfo[] | null { 71 | return this.type.isStruct ? (this.type as StructInfo).members : null; 72 | } 73 | 74 | get format(): TypeInfo | null { 75 | return this.type.isArray 76 | ? (this.type as ArrayInfo).format 77 | : this.type.isTemplate 78 | ? (this.type as TemplateInfo).format 79 | : null; 80 | } 81 | 82 | get count(): number { 83 | return this.type.isArray ? (this.type as ArrayInfo).count : 0; 84 | } 85 | 86 | get stride(): number { 87 | return this.type.isArray ? (this.type as ArrayInfo).stride : this.size; 88 | } 89 | } 90 | 91 | export class StructInfo extends TypeInfo { 92 | members: MemberInfo[] = []; 93 | align: number = 0; 94 | startLine: number = -1; 95 | endLine: number = -1; 96 | inUse: boolean = false; 97 | 98 | constructor(name: string, attributes: Attribute[] | null) { 99 | super(name, attributes); 100 | } 101 | 102 | get isStruct(): boolean { 103 | return true; 104 | } 105 | } 106 | 107 | export class ArrayInfo extends TypeInfo { 108 | format: TypeInfo; 109 | count: number; 110 | stride: number; 111 | 112 | constructor(name: string, attributes: Attribute[] | null) { 113 | super(name, attributes); 114 | this.count = 0; 115 | this.stride = 0; 116 | } 117 | 118 | get isArray(): boolean { 119 | return true; 120 | } 121 | 122 | getTypeName(): string { 123 | return `array<${this.format.getTypeName()}, ${this.count}>`; 124 | } 125 | } 126 | 127 | export class PointerInfo extends TypeInfo { 128 | format: TypeInfo; 129 | constructor(name: string, format: TypeInfo, attributes: Attribute[] | null) { 130 | super(name, attributes); 131 | this.format = format; 132 | } 133 | 134 | get isPointer(): boolean { 135 | return true; 136 | } 137 | 138 | getTypeName(): string { 139 | return `&${this.format.getTypeName()}`; 140 | } 141 | } 142 | 143 | export class TemplateInfo extends TypeInfo { 144 | format: TypeInfo | null; 145 | access: string; 146 | constructor( 147 | name: string, 148 | format: TypeInfo | null, 149 | attributes: Attribute[] | null, 150 | access: string 151 | ) { 152 | super(name, attributes); 153 | this.format = format; 154 | this.access = access; 155 | } 156 | 157 | get isTemplate(): boolean { 158 | return true; 159 | } 160 | 161 | getTypeName(): string { 162 | let name = this.name; 163 | if (this.format !== null) { 164 | if (name === "vec2" || name === "vec3" || name === "vec4" || 165 | name === "mat2x2" || name === "mat2x3" || name === "mat2x4" || 166 | name === "mat3x2" || name === "mat3x3" || name === "mat3x4" || 167 | name === "mat4x2" || name === "mat4x3" || name === "mat4x4") { 168 | if (this.format.name === "f32") { 169 | name += "f"; 170 | return name; 171 | } else if (this.format.name === "i32") { 172 | name += "i"; 173 | return name; 174 | } else if (this.format.name === "u32") { 175 | name += "u"; 176 | return name; 177 | } else if (this.format.name === "bool") { 178 | name += "b"; 179 | return name; 180 | } else if (this.format.name === "f16") { 181 | name += "h"; 182 | return name; 183 | } 184 | } 185 | name += `<${this.format.name}>`; 186 | } else { 187 | if (name === "vec2" || name === "vec3" || name === "vec4") { 188 | return name; 189 | } 190 | //console.error("Template format is null."); 191 | } 192 | return name; 193 | } 194 | } 195 | 196 | export enum ResourceType { 197 | Uniform, 198 | Storage, 199 | Texture, 200 | Sampler, 201 | StorageTexture, 202 | } 203 | 204 | export class VariableInfo { 205 | attributes: Attribute[] | null; 206 | name: string; 207 | type: TypeInfo; 208 | group: number; 209 | binding: number; 210 | resourceType: ResourceType; 211 | access: string; 212 | relations: Array | null = null; 213 | 214 | constructor( 215 | name: string, 216 | type: TypeInfo, 217 | group: number, 218 | binding: number, 219 | attributes: Attribute[] | null, 220 | resourceType: ResourceType, 221 | access: string 222 | ) { 223 | this.name = name; 224 | this.type = type; 225 | this.group = group; 226 | this.binding = binding; 227 | this.attributes = attributes; 228 | this.resourceType = resourceType; 229 | this.access = access; 230 | } 231 | 232 | get isArray(): boolean { 233 | return this.type.isArray; 234 | } 235 | 236 | get isStruct(): boolean { 237 | return this.type.isStruct; 238 | } 239 | 240 | get isTemplate(): boolean { 241 | return this.type.isTemplate; 242 | } 243 | 244 | get size(): number { 245 | return this.type.size; 246 | } 247 | 248 | get align(): number { 249 | return this.type.isStruct ? (this.type as StructInfo).align : 0; 250 | } 251 | 252 | get members(): MemberInfo[] | null { 253 | return this.type.isStruct ? (this.type as StructInfo).members : null; 254 | } 255 | 256 | get format(): TypeInfo | null { 257 | return this.type.isArray 258 | ? (this.type as ArrayInfo).format 259 | : this.type.isTemplate 260 | ? (this.type as TemplateInfo).format 261 | : null; 262 | } 263 | 264 | get count(): number { 265 | return this.type.isArray ? (this.type as ArrayInfo).count : 0; 266 | } 267 | 268 | get stride(): number { 269 | return this.type.isArray ? (this.type as ArrayInfo).stride : this.size; 270 | } 271 | } 272 | 273 | export class AliasInfo { 274 | name: string; 275 | type: TypeInfo; 276 | 277 | constructor(name: string, type: TypeInfo) { 278 | this.name = name; 279 | this.type = type; 280 | } 281 | } 282 | 283 | export class InputInfo { 284 | name: string; 285 | type: TypeInfo | null; 286 | locationType: string; 287 | location: number | string; 288 | interpolation: string | null; 289 | 290 | constructor( 291 | name: string, 292 | type: TypeInfo | null, 293 | locationType: string, 294 | location: number | string 295 | ) { 296 | this.name = name; 297 | this.type = type; 298 | this.locationType = locationType; 299 | this.location = location; 300 | this.interpolation = null; 301 | } 302 | } 303 | 304 | export class OutputInfo { 305 | name: string; 306 | type: TypeInfo | null; 307 | locationType: string; 308 | location: number | string; 309 | 310 | constructor( 311 | name: string, 312 | type: TypeInfo | null, 313 | locationType: string, 314 | location: number | string 315 | ) { 316 | this.name = name; 317 | this.type = type; 318 | this.locationType = locationType; 319 | this.location = location; 320 | } 321 | } 322 | 323 | export class OverrideInfo { 324 | name: string; 325 | type: TypeInfo | null; 326 | attributes: Attribute[] | null; 327 | id: number; 328 | 329 | constructor( 330 | name: string, 331 | type: TypeInfo | null, 332 | attributes: Attribute[] | null, 333 | id: number 334 | ) { 335 | this.name = name; 336 | this.type = type; 337 | this.attributes = attributes; 338 | this.id = id; 339 | } 340 | } 341 | 342 | export class ArgumentInfo { 343 | name: string; 344 | type: TypeInfo; 345 | attributes: Attribute[] | null; 346 | 347 | constructor( 348 | name: string, 349 | type: TypeInfo, 350 | attributes: Attribute[] | null 351 | ) { 352 | this.name = name; 353 | this.type = type; 354 | this.attributes = attributes; 355 | } 356 | } 357 | 358 | export class FunctionInfo { 359 | name: string; 360 | stage: string | null = null; 361 | inputs: InputInfo[] = []; 362 | outputs: OutputInfo[] = []; 363 | arguments: ArgumentInfo[] = []; 364 | returnType: TypeInfo | null = null; 365 | resources: VariableInfo[] = []; 366 | overrides: OverrideInfo[] = []; 367 | attributes: Attribute[] | null; 368 | startLine: number = -1; 369 | endLine: number = -1; 370 | inUse: boolean = false; 371 | calls: Set = new Set(); 372 | 373 | constructor(name: string, stage: string | null = null, attributes: Attribute[] | null) { 374 | this.name = name; 375 | this.stage = stage; 376 | this.attributes = attributes; 377 | } 378 | } 379 | 380 | export class EntryFunctions { 381 | vertex: FunctionInfo[] = []; 382 | fragment: FunctionInfo[] = []; 383 | compute: FunctionInfo[] = []; 384 | } 385 | -------------------------------------------------------------------------------- /types/wgsl_scanner.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum TokenClass { 2 | token = 0, 3 | keyword = 1, 4 | reserved = 2 5 | } 6 | export declare class TokenType { 7 | name: string; 8 | type: TokenClass; 9 | rule: RegExp | string; 10 | constructor(name: string, type: TokenClass, rule: RegExp | string); 11 | toString(): string; 12 | } 13 | export declare class TokenTypes { 14 | static readonly none: TokenType; 15 | static readonly eof: TokenType; 16 | static readonly reserved: { 17 | asm: TokenType; 18 | bf16: TokenType; 19 | do: TokenType; 20 | enum: TokenType; 21 | f16: TokenType; 22 | f64: TokenType; 23 | handle: TokenType; 24 | i8: TokenType; 25 | i16: TokenType; 26 | i64: TokenType; 27 | mat: TokenType; 28 | premerge: TokenType; 29 | regardless: TokenType; 30 | typedef: TokenType; 31 | u8: TokenType; 32 | u16: TokenType; 33 | u64: TokenType; 34 | unless: TokenType; 35 | using: TokenType; 36 | vec: TokenType; 37 | void: TokenType; 38 | }; 39 | static readonly keywords: { 40 | array: TokenType; 41 | atomic: TokenType; 42 | bool: TokenType; 43 | f32: TokenType; 44 | i32: TokenType; 45 | mat2x2: TokenType; 46 | mat2x3: TokenType; 47 | mat2x4: TokenType; 48 | mat3x2: TokenType; 49 | mat3x3: TokenType; 50 | mat3x4: TokenType; 51 | mat4x2: TokenType; 52 | mat4x3: TokenType; 53 | mat4x4: TokenType; 54 | ptr: TokenType; 55 | sampler: TokenType; 56 | sampler_comparison: TokenType; 57 | struct: TokenType; 58 | texture_1d: TokenType; 59 | texture_2d: TokenType; 60 | texture_2d_array: TokenType; 61 | texture_3d: TokenType; 62 | texture_cube: TokenType; 63 | texture_cube_array: TokenType; 64 | texture_multisampled_2d: TokenType; 65 | texture_storage_1d: TokenType; 66 | texture_storage_2d: TokenType; 67 | texture_storage_2d_array: TokenType; 68 | texture_storage_3d: TokenType; 69 | texture_depth_2d: TokenType; 70 | texture_depth_2d_array: TokenType; 71 | texture_depth_cube: TokenType; 72 | texture_depth_cube_array: TokenType; 73 | texture_depth_multisampled_2d: TokenType; 74 | texture_external: TokenType; 75 | u32: TokenType; 76 | vec2: TokenType; 77 | vec3: TokenType; 78 | vec4: TokenType; 79 | bitcast: TokenType; 80 | block: TokenType; 81 | break: TokenType; 82 | case: TokenType; 83 | continue: TokenType; 84 | continuing: TokenType; 85 | default: TokenType; 86 | diagnostic: TokenType; 87 | discard: TokenType; 88 | else: TokenType; 89 | enable: TokenType; 90 | fallthrough: TokenType; 91 | false: TokenType; 92 | fn: TokenType; 93 | for: TokenType; 94 | function: TokenType; 95 | if: TokenType; 96 | let: TokenType; 97 | const: TokenType; 98 | loop: TokenType; 99 | while: TokenType; 100 | private: TokenType; 101 | read: TokenType; 102 | read_write: TokenType; 103 | return: TokenType; 104 | requires: TokenType; 105 | storage: TokenType; 106 | switch: TokenType; 107 | true: TokenType; 108 | alias: TokenType; 109 | type: TokenType; 110 | uniform: TokenType; 111 | var: TokenType; 112 | override: TokenType; 113 | workgroup: TokenType; 114 | write: TokenType; 115 | r8unorm: TokenType; 116 | r8snorm: TokenType; 117 | r8uint: TokenType; 118 | r8sint: TokenType; 119 | r16uint: TokenType; 120 | r16sint: TokenType; 121 | r16float: TokenType; 122 | rg8unorm: TokenType; 123 | rg8snorm: TokenType; 124 | rg8uint: TokenType; 125 | rg8sint: TokenType; 126 | r32uint: TokenType; 127 | r32sint: TokenType; 128 | r32float: TokenType; 129 | rg16uint: TokenType; 130 | rg16sint: TokenType; 131 | rg16float: TokenType; 132 | rgba8unorm: TokenType; 133 | rgba8unorm_srgb: TokenType; 134 | rgba8snorm: TokenType; 135 | rgba8uint: TokenType; 136 | rgba8sint: TokenType; 137 | bgra8unorm: TokenType; 138 | bgra8unorm_srgb: TokenType; 139 | rgb10a2unorm: TokenType; 140 | rg11b10float: TokenType; 141 | rg32uint: TokenType; 142 | rg32sint: TokenType; 143 | rg32float: TokenType; 144 | rgba16uint: TokenType; 145 | rgba16sint: TokenType; 146 | rgba16float: TokenType; 147 | rgba32uint: TokenType; 148 | rgba32sint: TokenType; 149 | rgba32float: TokenType; 150 | static_assert: TokenType; 151 | }; 152 | static readonly tokens: { 153 | decimal_float_literal: TokenType; 154 | hex_float_literal: TokenType; 155 | int_literal: TokenType; 156 | uint_literal: TokenType; 157 | name: TokenType; 158 | ident: TokenType; 159 | and: TokenType; 160 | and_and: TokenType; 161 | arrow: TokenType; 162 | attr: TokenType; 163 | forward_slash: TokenType; 164 | bang: TokenType; 165 | bracket_left: TokenType; 166 | bracket_right: TokenType; 167 | brace_left: TokenType; 168 | brace_right: TokenType; 169 | colon: TokenType; 170 | comma: TokenType; 171 | equal: TokenType; 172 | equal_equal: TokenType; 173 | not_equal: TokenType; 174 | greater_than: TokenType; 175 | greater_than_equal: TokenType; 176 | shift_right: TokenType; 177 | less_than: TokenType; 178 | less_than_equal: TokenType; 179 | shift_left: TokenType; 180 | modulo: TokenType; 181 | minus: TokenType; 182 | minus_minus: TokenType; 183 | period: TokenType; 184 | plus: TokenType; 185 | plus_plus: TokenType; 186 | or: TokenType; 187 | or_or: TokenType; 188 | paren_left: TokenType; 189 | paren_right: TokenType; 190 | semicolon: TokenType; 191 | star: TokenType; 192 | tilde: TokenType; 193 | underscore: TokenType; 194 | xor: TokenType; 195 | plus_equal: TokenType; 196 | minus_equal: TokenType; 197 | times_equal: TokenType; 198 | division_equal: TokenType; 199 | modulo_equal: TokenType; 200 | and_equal: TokenType; 201 | or_equal: TokenType; 202 | xor_equal: TokenType; 203 | shift_right_equal: TokenType; 204 | shift_left_equal: TokenType; 205 | }; 206 | static readonly simpleTokens: { 207 | "@": TokenType; 208 | "{": TokenType; 209 | "}": TokenType; 210 | ":": TokenType; 211 | ",": TokenType; 212 | "(": TokenType; 213 | ")": TokenType; 214 | ";": TokenType; 215 | }; 216 | static readonly literalTokens: { 217 | "&": TokenType; 218 | "&&": TokenType; 219 | "->": TokenType; 220 | "/": TokenType; 221 | "!": TokenType; 222 | "[": TokenType; 223 | "]": TokenType; 224 | "=": TokenType; 225 | "==": TokenType; 226 | "!=": TokenType; 227 | ">": TokenType; 228 | ">=": TokenType; 229 | ">>": TokenType; 230 | "<": TokenType; 231 | "<=": TokenType; 232 | "<<": TokenType; 233 | "%": TokenType; 234 | "-": TokenType; 235 | "--": TokenType; 236 | ".": TokenType; 237 | "+": TokenType; 238 | "++": TokenType; 239 | "|": TokenType; 240 | "||": TokenType; 241 | "*": TokenType; 242 | "~": TokenType; 243 | _: TokenType; 244 | "^": TokenType; 245 | "+=": TokenType; 246 | "-=": TokenType; 247 | "*=": TokenType; 248 | "/=": TokenType; 249 | "%=": TokenType; 250 | "&=": TokenType; 251 | "|=": TokenType; 252 | "^=": TokenType; 253 | ">>=": TokenType; 254 | "<<=": TokenType; 255 | }; 256 | static readonly regexTokens: { 257 | decimal_float_literal: TokenType; 258 | hex_float_literal: TokenType; 259 | int_literal: TokenType; 260 | uint_literal: TokenType; 261 | ident: TokenType; 262 | }; 263 | static readonly storage_class: TokenType[]; 264 | static readonly access_mode: TokenType[]; 265 | static readonly sampler_type: TokenType[]; 266 | static readonly sampled_texture_type: TokenType[]; 267 | static readonly multisampled_texture_type: TokenType[]; 268 | static readonly storage_texture_type: TokenType[]; 269 | static readonly depth_texture_type: TokenType[]; 270 | static readonly texture_external_type: TokenType[]; 271 | static readonly any_texture_type: TokenType[]; 272 | static readonly texel_format: TokenType[]; 273 | static readonly const_literal: TokenType[]; 274 | static readonly literal_or_ident: TokenType[]; 275 | static readonly element_count_expression: TokenType[]; 276 | static readonly template_types: TokenType[]; 277 | static readonly attribute_name: TokenType[]; 278 | static readonly assignment_operators: TokenType[]; 279 | static readonly increment_operators: TokenType[]; 280 | } 281 | export declare class Token { 282 | readonly type: TokenType; 283 | readonly lexeme: string; 284 | readonly line: number; 285 | readonly start: number; 286 | readonly end: number; 287 | constructor(type: TokenType, lexeme: string, line: number, start: number, end: number); 288 | toString(): string; 289 | isTemplateType(): boolean; 290 | isArrayType(): boolean; 291 | isArrayOrTemplateType(): boolean; 292 | } 293 | export declare class WgslScanner { 294 | private _source; 295 | private _tokens; 296 | private _start; 297 | private _current; 298 | private _line; 299 | constructor(source?: string); 300 | scanTokens(): Token[]; 301 | scanToken(): boolean; 302 | _findType(lexeme: string): TokenType; 303 | _match(lexeme: string, rule: RegExp): boolean; 304 | _isAtEnd(): boolean; 305 | _isAlpha(c: string): boolean; 306 | _isNumeric(c: string): boolean; 307 | _isAlphaNumeric(c: string): boolean; 308 | _isWhitespace(c: string): boolean; 309 | _advance(amount?: number): string; 310 | _peekAhead(offset?: number): string; 311 | _addToken(type: TokenType): void; 312 | } 313 | -------------------------------------------------------------------------------- /test/tests/test_debug.js: -------------------------------------------------------------------------------- 1 | import { test, group } from "../test.js"; 2 | import { WgslDebug } from "../../wgsl_reflect.module.js"; 3 | 4 | export async function run() { 5 | await group("Debug", async function () { 6 | await test("mat4x4 uniform multiply", async function (test) { 7 | const shader = ` 8 | @group(0) @binding(0) var mat: mat4x4; 9 | @group(0) @binding(1) var vec: vec4; 10 | fn foo(m: mat4x4, v: vec4) -> vec4 { 11 | return m * v; 12 | } 13 | @compute @workgroup_size(1) 14 | fn main(@builtin(global_invocation_id) id: vec3) { 15 | vec = foo(mat, vec); 16 | }`; 17 | 18 | // Verify the emulated dispatch has the same results as the WebGPU dispatch. 19 | const mat = new Float32Array([2.0, 0.0, 0.0, 0.0, 20 | 0.0, 2.0, 0.0, 0.0, 21 | 0.0, 0.0, 2.0, 0.0, 22 | 0.0, 0.0, 0.0, 1.0]); 23 | 24 | const vec = new Float32Array([1.0, 2.0, 3.0, 1.0]); 25 | const bg = {0: {0: mat, 1: vec}}; 26 | const dbg = new WgslDebug(shader); 27 | dbg.debugWorkgroup("main", [1, 0, 0], 4, bg); 28 | while (dbg.stepNext()); 29 | test.equals(vec, [2.0, 4.0, 6.0, 1.0]); 30 | }); 31 | 32 | await test("mat4x4 multiply", async function (test) { 33 | const shader = ` 34 | let a = mat4x4( 35 | 2.0, 0.0, 0.0, 0.0, 36 | 0.0, 2.0, 0.0, 0.0, 37 | 0.0, 0.0, 2.0, 0.0, 38 | 0.0, 0.0, 0.0, 1.0); 39 | let b = mat4x4( 40 | 2.0, 0.0, 0.0, 0.0, 41 | 0.0, 2.0, 0.0, 0.0, 42 | 0.0, 0.0, 2.0, 0.0, 43 | 0.0, 0.0, 0.0, 1.0 ); 44 | let m = foo(a * b); 45 | fn foo(m: mat4x4) -> mat4x4 { 46 | return m; 47 | } 48 | `; 49 | const dbg = new WgslDebug(shader); 50 | dbg.startDebug() 51 | dbg.stepNext(); // LET a 52 | dbg.stepNext(); // LET b 53 | dbg.stepNext(); // LET m 54 | const m = dbg.context.getVariable("m")?.value; 55 | const mStr = m?.toString(); 56 | console.log(mStr); 57 | test.equals(mStr, "4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1"); 58 | }); 59 | 60 | await test("default value override", async function (test) { 61 | const shader = ` 62 | override test_val = 64; 63 | let bar = test_val;`; 64 | const dbg = new WgslDebug(shader); 65 | dbg.startDebug() 66 | while (dbg.stepNext()); 67 | const v = dbg.getVariableValue("bar"); 68 | test.equals(v, 64); 69 | }); 70 | 71 | await test("default value override actually overridden", async function (test) { 72 | const shader = ` 73 | override test_val = 64; 74 | let bar = test_val;`; 75 | const dbg = new WgslDebug(shader); 76 | dbg.startDebug() 77 | dbg._setOverrides({test_val: 65}, dbg.context); 78 | while (dbg.stepNext()); 79 | const v = dbg.getVariableValue("bar"); 80 | test.equals(v, 65); 81 | }); 82 | 83 | await test("callexpr", function (test) { 84 | const shader = ` 85 | let foo = photon(); 86 | fn photon() -> vec3f { 87 | var ray = new_light_ray(); 88 | return ray.dir; 89 | } 90 | fn new_light_ray() -> Ray { 91 | let center = vec3f(0.0, 0.0, 0.0); 92 | let pos = center + vec3f(0.0, 0.1, 0.0); 93 | var rc = vec3f(1.0, 2.0, 3.0); 94 | var dir = rc.xzy; 95 | dir.y = -dir.y; 96 | return Ray(pos, dir); 97 | } 98 | struct Ray { 99 | start : vec3f, 100 | dir : vec3f, 101 | };`; 102 | const dbg = new WgslDebug(shader); 103 | while (dbg.stepNext()); 104 | test.equals(dbg.getVariableValue("foo"), [1, -3, 2]); 105 | }); 106 | 107 | await test("switch default selector", function (test) { 108 | const shader = ` 109 | const c = 2; 110 | fn foo(x: i32) -> i32 { 111 | var a : i32; 112 | switch x { 113 | case 0: { // colon is optional 114 | a = 1; 115 | } 116 | case 3, default { // The default keyword can be used with other clauses 117 | a = 4; 118 | } 119 | case 1, c { // Const-expression can be used in case selectors 120 | a = 3; 121 | } 122 | } 123 | return a; 124 | } 125 | let x = foo(2); 126 | let y = foo(5);`; 127 | const dbg = new WgslDebug(shader); 128 | while (dbg.stepNext()); 129 | test.equals(dbg.getVariableValue("x"), 3); 130 | test.equals(dbg.getVariableValue("y"), 4); 131 | }); 132 | 133 | await test("nested loops", async function (test) { 134 | const shader = ` 135 | fn foo() -> i32 { 136 | let j = 0; 137 | loop { 138 | if j >= 2 { break; } 139 | loop { 140 | j++; 141 | if j >= 3 { break; } 142 | } 143 | } 144 | return j; 145 | } 146 | let bar = foo();`; 147 | const dbg = new WgslDebug(shader); 148 | while (dbg.stepNext()); 149 | const v = dbg.getVariableValue("bar"); 150 | test.equals(v, 3); 151 | }); 152 | 153 | await test("vec2 operators", async function (test) { 154 | var shader = `let i = 2; 155 | var a = vec2(1.0, 2.0); 156 | var b = vec2(3.0, 4.0); 157 | var c = (a / vec2(f32(i))) - b;`; 158 | const dbg = new WgslDebug(shader); 159 | while (dbg.stepNext()); 160 | const v = dbg.getVariableValue("c"); 161 | test.equals(v, [-2.5, -3]); 162 | }); 163 | 164 | await test("call statement", async function (test) { 165 | const shader = `var j: i32; 166 | fn foo() -> i32 { 167 | var a: i32 = 2; 168 | var i: i32 = 0; 169 | loop { 170 | let step: i32 = 1; 171 | if i % 2 == 0 { continue; } 172 | a = a * 2; 173 | continuing { 174 | i = i + step; 175 | break if i >= 4; 176 | } 177 | } 178 | j = a; 179 | return j; 180 | } 181 | fn bar() -> i32 { 182 | foo(); 183 | return j; 184 | } 185 | let k = bar();`; 186 | const dbg = new WgslDebug(shader); 187 | while (dbg.stepNext()); 188 | test.equals(dbg.getVariableValue("j"), 8); 189 | }); 190 | 191 | await test("break", async function (test) { 192 | const shader = `fn foo() -> i32 { 193 | let j = 0; 194 | for (var i = 0; i < 5; i++) { 195 | if i == 0 { break; } 196 | j++; 197 | } 198 | return j; 199 | } 200 | let j = foo();`; 201 | const dbg = new WgslDebug(shader); 202 | while (dbg.stepNext()); 203 | test.equals(dbg.getVariableValue("j"), 0); 204 | }); 205 | 206 | await test("continue", async function (test) { 207 | const shader = `fn foo() -> i32 { 208 | let j = 0; 209 | for (var i = 0; i < 5; i++) { 210 | if i == 0 { continue; } 211 | j++; 212 | } 213 | return j; 214 | } 215 | let j = foo();`; 216 | const dbg = new WgslDebug(shader); 217 | while (dbg.stepNext()); 218 | test.equals(dbg.getVariableValue("j"), 4); 219 | }); 220 | 221 | await test("set variable", async function (test) { 222 | const shader = `let foo = 1 + 2;`; 223 | const dbg = new WgslDebug(shader); 224 | let res = dbg.stepNext(); 225 | test.equals(res, false); 226 | test.equals(dbg.getVariableValue("foo"), 3); 227 | }); 228 | 229 | await test("multiple variables", function (test) { 230 | const shader = `let foo = 1 + 2; 231 | let bar = foo * 4;`; 232 | 233 | const dbg = new WgslDebug(shader); 234 | dbg.stepNext(); 235 | dbg.stepNext(); 236 | // Ensure as the top-level instructions are executed, variables are correctly evaluated. 237 | test.equals(dbg.getVariableValue("foo"), 3); 238 | test.equals(dbg.getVariableValue("bar"), 12); 239 | }); 240 | 241 | await test("call function", function (test) { 242 | const shader = ` 243 | fn foo(a: i32, b: i32) -> i32 { 244 | if b > 0 { 245 | return a / b; 246 | } else { 247 | return a * b; 248 | } 249 | } 250 | let bar = foo(3, 4); 251 | let bar2 = foo(5, -2);`; 252 | const dbg = new WgslDebug(shader); 253 | while (dbg.stepNext()); 254 | // Ensure calling a function works as expected. 255 | test.equals(dbg.getVariableValue("bar"), 0); 256 | test.equals(dbg.getVariableValue("bar2"), -10); 257 | }); 258 | 259 | await test("data", async function (test) { 260 | const shader = ` 261 | @group(0) @binding(0) var buffer: array; 262 | @compute @workgroup_size(1) 263 | fn main(@builtin(global_invocation_id) id: vec3) { 264 | let i = id.x; 265 | buffer[i] = buffer[i] * 2.0; 266 | }`; 267 | 268 | // Verify the emulated dispatch has the same results as the WebGPU dispatch. 269 | const buffer = new Float32Array([1, 2, 6, 0]); 270 | const bg = {0: {0: buffer}}; 271 | 272 | const dbg = new WgslDebug(shader); 273 | dbg.debugWorkgroup("main", [1, 0, 0], 4, bg); 274 | while (dbg.stepNext()); 275 | 276 | // Test that we only executed the [1, 0, 0] global_invocation_id. 277 | test.equals(buffer, [1, 4, 6, 0]); 278 | }); 279 | 280 | await test("scalar binding", async function (test) { 281 | const shader = ` 282 | @group(0) @binding(0) var buffer: f32; 283 | @compute @workgroup_size(1) 284 | fn main(@builtin(global_invocation_id) id: vec3) { 285 | buffer = 42 + buffer; 286 | }`; 287 | 288 | const buffer = new Float32Array([6]); 289 | const bg = {0: {0: buffer}}; 290 | const dbg = new WgslDebug(shader); 291 | dbg.debugWorkgroup("main", [1, 0, 0], 4, bg); 292 | while (dbg.stepNext()); 293 | test.equals(buffer, [48]); 294 | }); 295 | 296 | await test("dispatch function call", async function (test) { 297 | const shader = ` 298 | fn scale(x: f32, y: f32) -> f32 { 299 | return x * y; 300 | } 301 | @group(0) @binding(0) var buffer: array; 302 | @compute @workgroup_size(1) 303 | fn main(@builtin(global_invocation_id) id: vec3) { 304 | let i = id.x; 305 | buffer[i] = scale(buffer[i], 2.0); 306 | }`; 307 | 308 | // Verify the emulated dispatch has the same results as the WebGPU dispatch. 309 | const buffer = new Float32Array([1, 2, 6, 0]); 310 | const bg = {0: {0: buffer}}; 311 | 312 | const dbg = new WgslDebug(shader); 313 | dbg.debugWorkgroup("main", [1, 0, 0], 4, bg); 314 | dbg.stepNext(); // LET: i = id.x; 315 | dbg.stepNext(); // CALL: scale(buffer[i], 2.0) 316 | dbg.stepNext(); // RETURN: x * y 317 | dbg.stepNext(); // ASSIGN: buffer[i] = 318 | 319 | // Test that we only executed the [1, 0, 0] global_invocation_id. 320 | test.equals(buffer, [1, 4, 6, 0]); 321 | }); 322 | }, true); 323 | } 324 | 325 | -------------------------------------------------------------------------------- /types/exec/builtin_functions.d.ts: -------------------------------------------------------------------------------- 1 | import { CallExpr, Call } from "../wgsl_ast.js"; 2 | import { Data } from "../wgsl_ast.js"; 3 | import { ExecContext } from "./exec_context.js"; 4 | import { ExecInterface } from "./exec_interface.js"; 5 | import { TypeInfo } from "../reflect/info.js"; 6 | export declare class BuiltinFunctions { 7 | exec: ExecInterface; 8 | constructor(exec: ExecInterface); 9 | getTypeInfo(type: string): TypeInfo | null; 10 | All(node: CallExpr | Call, context: ExecContext): Data | null; 11 | Any(node: CallExpr | Call, context: ExecContext): Data | null; 12 | Select(node: CallExpr | Call, context: ExecContext): Data | null; 13 | ArrayLength(node: CallExpr | Call, context: ExecContext): Data | null; 14 | Abs(node: CallExpr | Call, context: ExecContext): Data | null; 15 | Acos(node: CallExpr | Call, context: ExecContext): Data | null; 16 | Acosh(node: CallExpr | Call, context: ExecContext): Data | null; 17 | Asin(node: CallExpr | Call, context: ExecContext): Data | null; 18 | Asinh(node: CallExpr | Call, context: ExecContext): Data | null; 19 | Atan(node: CallExpr | Call, context: ExecContext): Data | null; 20 | Atanh(node: CallExpr | Call, context: ExecContext): Data | null; 21 | Atan2(node: CallExpr | Call, context: ExecContext): Data | null; 22 | Ceil(node: CallExpr | Call, context: ExecContext): Data | null; 23 | _clamp(value: number, min: number, max: number): number; 24 | Clamp(node: CallExpr | Call, context: ExecContext): Data | null; 25 | Cos(node: CallExpr | Call, context: ExecContext): Data | null; 26 | Cosh(node: CallExpr | Call, context: ExecContext): Data | null; 27 | CountLeadingZeros(node: CallExpr | Call, context: ExecContext): Data | null; 28 | _countOneBits(value: number): number; 29 | CountOneBits(node: CallExpr | Call, context: ExecContext): Data | null; 30 | _countTrailingZeros(value: number): number; 31 | CountTrailingZeros(node: CallExpr | Call, context: ExecContext): Data | null; 32 | Cross(node: CallExpr | Call, context: ExecContext): Data | null; 33 | Degrees(node: CallExpr | Call, context: ExecContext): Data | null; 34 | Determinant(node: CallExpr | Call, context: ExecContext): Data | null; 35 | Distance(node: CallExpr | Call, context: ExecContext): Data | null; 36 | _dot(e1: Int32Array | Uint32Array | Float32Array, e2: Int32Array | Uint32Array | Float32Array): number; 37 | Dot(node: CallExpr | Call, context: ExecContext): Data | null; 38 | Dot4U8Packed(node: CallExpr | Call, context: ExecContext): Data | null; 39 | Dot4I8Packed(node: CallExpr | Call, context: ExecContext): Data | null; 40 | Exp(node: CallExpr | Call, context: ExecContext): Data | null; 41 | Exp2(node: CallExpr | Call, context: ExecContext): Data | null; 42 | ExtractBits(node: CallExpr | Call, context: ExecContext): Data | null; 43 | FaceForward(node: CallExpr | Call, context: ExecContext): Data | null; 44 | _firstLeadingBit(s: number): number; 45 | FirstLeadingBit(node: CallExpr | Call, context: ExecContext): Data | null; 46 | _firstTrailingBit(s: number): number; 47 | FirstTrailingBit(node: CallExpr | Call, context: ExecContext): Data | null; 48 | Floor(node: CallExpr | Call, context: ExecContext): Data | null; 49 | Fma(node: CallExpr | Call, context: ExecContext): Data | null; 50 | Fract(node: CallExpr | Call, context: ExecContext): Data | null; 51 | Frexp(node: CallExpr | Call, context: ExecContext): Data | null; 52 | InsertBits(node: CallExpr | Call, context: ExecContext): Data | null; 53 | InverseSqrt(node: CallExpr | Call, context: ExecContext): Data | null; 54 | Ldexp(node: CallExpr | Call, context: ExecContext): Data | null; 55 | Length(node: CallExpr | Call, context: ExecContext): Data | null; 56 | Log(node: CallExpr | Call, context: ExecContext): Data | null; 57 | Log2(node: CallExpr | Call, context: ExecContext): Data | null; 58 | Max(node: CallExpr | Call, context: ExecContext): Data | null; 59 | Min(node: CallExpr | Call, context: ExecContext): Data | null; 60 | Mix(node: CallExpr | Call, context: ExecContext): Data | null; 61 | Modf(node: CallExpr | Call, context: ExecContext): Data | null; 62 | Normalize(node: CallExpr | Call, context: ExecContext): Data | null; 63 | Pow(node: CallExpr | Call, context: ExecContext): Data | null; 64 | QuantizeToF16(node: CallExpr | Call, context: ExecContext): Data | null; 65 | Radians(node: CallExpr | Call, context: ExecContext): Data | null; 66 | Reflect(node: CallExpr | Call, context: ExecContext): Data | null; 67 | Refract(node: CallExpr | Call, context: ExecContext): Data | null; 68 | ReverseBits(node: CallExpr | Call, context: ExecContext): Data | null; 69 | Round(node: CallExpr | Call, context: ExecContext): Data | null; 70 | Saturate(node: CallExpr | Call, context: ExecContext): Data | null; 71 | Sign(node: CallExpr | Call, context: ExecContext): Data | null; 72 | Sin(node: CallExpr | Call, context: ExecContext): Data | null; 73 | Sinh(node: CallExpr | Call, context: ExecContext): Data | null; 74 | _smoothstep(edge0: number, edge1: number, x: number): number; 75 | SmoothStep(node: CallExpr | Call, context: ExecContext): Data | null; 76 | Sqrt(node: CallExpr | Call, context: ExecContext): Data | null; 77 | Step(node: CallExpr | Call, context: ExecContext): Data | null; 78 | Tan(node: CallExpr | Call, context: ExecContext): Data | null; 79 | Tanh(node: CallExpr | Call, context: ExecContext): Data | null; 80 | _getTransposeType(t: TypeInfo): TypeInfo; 81 | Transpose(node: CallExpr | Call, context: ExecContext): Data | null; 82 | Trunc(node: CallExpr | Call, context: ExecContext): Data | null; 83 | Dpdx(node: CallExpr | Call, context: ExecContext): Data | null; 84 | DpdxCoarse(node: CallExpr | Call, context: ExecContext): Data | null; 85 | DpdxFine(node: CallExpr | Call, context: ExecContext): Data | null; 86 | Dpdy(node: CallExpr | Call, context: ExecContext): Data | null; 87 | DpdyCoarse(node: CallExpr | Call, context: ExecContext): Data | null; 88 | DpdyFine(node: CallExpr | Call, context: ExecContext): Data | null; 89 | Fwidth(node: CallExpr | Call, context: ExecContext): Data | null; 90 | FwidthCoarse(node: CallExpr | Call, context: ExecContext): Data | null; 91 | FwidthFine(node: CallExpr | Call, context: ExecContext): Data | null; 92 | TextureDimensions(node: CallExpr | Call, context: ExecContext): Data | null; 93 | TextureGather(node: CallExpr | Call, context: ExecContext): Data | null; 94 | TextureGatherCompare(node: CallExpr | Call, context: ExecContext): Data | null; 95 | TextureLoad(node: CallExpr | Call, context: ExecContext): Data | null; 96 | TextureNumLayers(node: CallExpr | Call, context: ExecContext): Data | null; 97 | TextureNumLevels(node: CallExpr | Call, context: ExecContext): Data | null; 98 | TextureNumSamples(node: CallExpr | Call, context: ExecContext): Data | null; 99 | TextureSample(node: CallExpr | Call, context: ExecContext): Data | null; 100 | TextureSampleBias(node: CallExpr | Call, context: ExecContext): Data | null; 101 | TextureSampleCompare(node: CallExpr | Call, context: ExecContext): Data | null; 102 | TextureSampleCompareLevel(node: CallExpr | Call, context: ExecContext): Data | null; 103 | TextureSampleGrad(node: CallExpr | Call, context: ExecContext): Data | null; 104 | TextureSampleLevel(node: CallExpr | Call, context: ExecContext): Data | null; 105 | TextureSampleBaseClampToEdge(node: CallExpr | Call, context: ExecContext): Data | null; 106 | TextureStore(node: CallExpr | Call, context: ExecContext): Data | null; 107 | AtomicLoad(node: CallExpr | Call, context: ExecContext): Data | null; 108 | AtomicStore(node: CallExpr | Call, context: ExecContext): Data | null; 109 | AtomicAdd(node: CallExpr | Call, context: ExecContext): Data | null; 110 | AtomicSub(node: CallExpr | Call, context: ExecContext): Data | null; 111 | AtomicMax(node: CallExpr | Call, context: ExecContext): Data | null; 112 | AtomicMin(node: CallExpr | Call, context: ExecContext): Data | null; 113 | AtomicAnd(node: CallExpr | Call, context: ExecContext): Data | null; 114 | AtomicOr(node: CallExpr | Call, context: ExecContext): Data | null; 115 | AtomicXor(node: CallExpr | Call, context: ExecContext): Data | null; 116 | AtomicExchange(node: CallExpr | Call, context: ExecContext): Data | null; 117 | AtomicCompareExchangeWeak(node: CallExpr | Call, context: ExecContext): Data | null; 118 | Pack4x8snorm(node: CallExpr | Call, context: ExecContext): Data | null; 119 | Pack4x8unorm(node: CallExpr | Call, context: ExecContext): Data | null; 120 | Pack4xI8(node: CallExpr | Call, context: ExecContext): Data | null; 121 | Pack4xU8(node: CallExpr | Call, context: ExecContext): Data | null; 122 | Pack4x8Clamp(node: CallExpr | Call, context: ExecContext): Data | null; 123 | Pack4xU8Clamp(node: CallExpr | Call, context: ExecContext): Data | null; 124 | Pack2x16snorm(node: CallExpr | Call, context: ExecContext): Data | null; 125 | Pack2x16unorm(node: CallExpr | Call, context: ExecContext): Data | null; 126 | Pack2x16float(node: CallExpr | Call, context: ExecContext): Data | null; 127 | Unpack4x8snorm(node: CallExpr | Call, context: ExecContext): Data | null; 128 | Unpack4x8unorm(node: CallExpr | Call, context: ExecContext): Data | null; 129 | Unpack4xI8(node: CallExpr | Call, context: ExecContext): Data | null; 130 | Unpack4xU8(node: CallExpr | Call, context: ExecContext): Data | null; 131 | Unpack2x16snorm(node: CallExpr | Call, context: ExecContext): Data | null; 132 | Unpack2x16unorm(node: CallExpr | Call, context: ExecContext): Data | null; 133 | Unpack2x16float(node: CallExpr | Call, context: ExecContext): Data | null; 134 | StorageBarrier(node: CallExpr | Call, context: ExecContext): Data | null; 135 | TextureBarrier(node: CallExpr | Call, context: ExecContext): Data | null; 136 | WorkgroupBarrier(node: CallExpr | Call, context: ExecContext): Data | null; 137 | WorkgroupUniformLoad(node: CallExpr | Call, context: ExecContext): Data | null; 138 | SubgroupAdd(node: CallExpr | Call, context: ExecContext): Data | null; 139 | SubgroupExclusiveAdd(node: CallExpr | Call, context: ExecContext): Data | null; 140 | SubgroupInclusiveAdd(node: CallExpr | Call, context: ExecContext): Data | null; 141 | SubgroupAll(node: CallExpr | Call, context: ExecContext): Data | null; 142 | SubgroupAnd(node: CallExpr | Call, context: ExecContext): Data | null; 143 | SubgroupAny(node: CallExpr | Call, context: ExecContext): Data | null; 144 | SubgroupBallot(node: CallExpr | Call, context: ExecContext): Data | null; 145 | SubgroupBroadcast(node: CallExpr | Call, context: ExecContext): Data | null; 146 | SubgroupBroadcastFirst(node: CallExpr | Call, context: ExecContext): Data | null; 147 | SubgroupElect(node: CallExpr | Call, context: ExecContext): Data | null; 148 | SubgroupMax(node: CallExpr | Call, context: ExecContext): Data | null; 149 | SubgroupMin(node: CallExpr | Call, context: ExecContext): Data | null; 150 | SubgroupMul(node: CallExpr | Call, context: ExecContext): Data | null; 151 | SubgroupExclusiveMul(node: CallExpr | Call, context: ExecContext): Data | null; 152 | SubgroupInclusiveMul(node: CallExpr | Call, context: ExecContext): Data | null; 153 | SubgroupOr(node: CallExpr | Call, context: ExecContext): Data | null; 154 | SubgroupShuffle(node: CallExpr | Call, context: ExecContext): Data | null; 155 | SubgroupShuffleDown(node: CallExpr | Call, context: ExecContext): Data | null; 156 | SubgroupShuffleUp(node: CallExpr | Call, context: ExecContext): Data | null; 157 | SubgroupShuffleXor(node: CallExpr | Call, context: ExecContext): Data | null; 158 | SubgroupXor(node: CallExpr | Call, context: ExecContext): Data | null; 159 | QuadBroadcast(node: CallExpr | Call, context: ExecContext): Data | null; 160 | QuadSwapDiagonal(node: CallExpr | Call, context: ExecContext): Data | null; 161 | QuadSwapX(node: CallExpr | Call, context: ExecContext): Data | null; 162 | QuadSwapY(node: CallExpr | Call, context: ExecContext): Data | null; 163 | } 164 | -------------------------------------------------------------------------------- /src/utils/texture_format_info.ts: -------------------------------------------------------------------------------- 1 | 2 | export const TextureFormatInfo = { 3 | "r8unorm": { "bytesPerBlock": 1, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 4 | "r8snorm": { "bytesPerBlock": 1, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 5 | "r8uint": { "bytesPerBlock": 1, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 6 | "r8sint": { "bytesPerBlock": 1, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 7 | "rg8unorm": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 8 | "rg8snorm": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 9 | "rg8uint": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 10 | "rg8sint": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 11 | 12 | "rgba8unorm": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 13 | "rgba8unorm-srgb": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 14 | "rgba8snorm": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 15 | "rgba8uint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 16 | "rgba8sint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 17 | "bgra8unorm": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 18 | "bgra8unorm-srgb": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 19 | 20 | "r16uint": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 21 | "r16sint": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 22 | "r16float": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 23 | 24 | "rg16uint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 25 | "rg16sint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 26 | "rg16float": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 27 | 28 | "rgba16uint": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 29 | "rgba16sint": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 30 | "rgba16float": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 31 | 32 | "r32uint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 33 | "r32sint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 34 | "r32float": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 1 }, 35 | 36 | "rg32uint": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 37 | "rg32sint": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 38 | "rg32float": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 2 }, 39 | 40 | "rgba32uint": { "bytesPerBlock": 16, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 41 | "rgba32sint": { "bytesPerBlock": 16, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 42 | "rgba32float": { "bytesPerBlock": 16, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 43 | "rgb10a2uint": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 44 | "rgb10a2unorm": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 45 | "rg11b10ufloat": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 46 | 47 | // Depth Stencil Formats 48 | "stencil8": { "bytesPerBlock": 1, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": false, "hasStencil": true, "channels": 1 }, // bytesPerBlock is actually 1-4 49 | "depth16unorm": { "bytesPerBlock": 2, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": true, "hasStencil": false, "channels": 1 }, 50 | "depth24plus": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": true, "hasStencil": false, "depthOnlyFormat": "depth32float", "channels": 1 }, 51 | "depth24plus-stencil8": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": true, "hasStencil": true, "depthOnlyFormat": "depth32float", "channels": 1 }, // bytesPerBlock is actually 4-8 52 | "depth32float": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": true, "hasStencil": false, "channels": 1 }, 53 | "depth32float-stencil8": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "isDepthStencil": true, "hasDepth": true, "hasStencil": true, "stencilOnlyFormat": "depth32float", "channels": 1 }, // bytesPerBlock is actually 5-8 54 | 55 | // Packed Formats 56 | "rgb9e5ufloat": { "bytesPerBlock": 4, "blockWidth": 1, "blockHeight": 1, "isCompressed": false, "channels": 4 }, 57 | 58 | // Compressed Formats 59 | "bc1-rgba-unorm": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 60 | "bc1-rgba-unorm-srgb": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 61 | "bc2-rgba-unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 62 | "bc2-rgba-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 63 | "bc3-rgba-unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 64 | "bc3-rgba-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 65 | 66 | "bc4-r-unorm": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 1 }, 67 | "bc4-r-snorm": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 1 }, 68 | 69 | "bc5-rg-unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 2 }, 70 | "bc5-rg-snorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 2 }, 71 | 72 | "bc6h-rgb-ufloat": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 73 | "bc6h-rgb-float": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 74 | "bc7-rgba-unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 75 | "bc7-rgba-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 76 | 77 | "etc2-rgb8unorm": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 78 | "etc2-rgb8unorm-srgb": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 79 | "etc2-rgb8a1unorm": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 80 | "etc2-rgb8a1unorm-srgb": { "bytesPerBlock": 8, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 81 | "etc2-rgba8unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 82 | "etc2-rgba8unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 83 | 84 | "eac-r11unorm": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": true, "channels": 1 }, 85 | "eac-r11snorm": { "bytesPerBlock": 8, "blockWidth": 1, "blockHeight": 1, "isCompressed": true, "channels": 1 }, 86 | 87 | "eac-rg11unorm": { "bytesPerBlock": 16, "blockWidth": 1, "blockHeight": 1, "isCompressed": true, "channels": 2 }, 88 | "eac-rg11snorm": { "bytesPerBlock": 16, "blockWidth": 1, "blockHeight": 1, "isCompressed": true, "channels": 2 }, 89 | 90 | "astc-4x4-unorm": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 91 | "astc-4x4-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 4, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 92 | "astc-5x4-unorm": { "bytesPerBlock": 16, "blockWidth": 5, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 93 | "astc-5x4-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 5, "blockHeight": 4, "isCompressed": true, "channels": 4 }, 94 | "astc-5x5-unorm": { "bytesPerBlock": 16, "blockWidth": 5, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 95 | "astc-5x5-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 5, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 96 | "astc-6x5-unorm": { "bytesPerBlock": 16, "blockWidth": 6, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 97 | "astc-6x5-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 6, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 98 | "astc-6x6-unorm": { "bytesPerBlock": 16, "blockWidth": 6, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 99 | "astc-6x6-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 6, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 100 | "astc-8x5-unorm": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 101 | "astc-8x5-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 102 | "astc-8x6-unorm": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 103 | "astc-8x6-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 104 | "astc-8x8-unorm": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 8, "isCompressed": true, "channels": 4 }, 105 | "astc-8x8-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 8, "blockHeight": 8, "isCompressed": true, "channels": 4 }, 106 | "astc-10x5-unorm": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 107 | "astc-10x5-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 5, "isCompressed": true, "channels": 4 }, 108 | "astc-10x6-unorm": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 109 | "astc-10x6-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 6, "isCompressed": true, "channels": 4 }, 110 | "astc-10x8-unorm": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 8, "isCompressed": true, "channels": 4 }, 111 | "astc-10x8-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 8, "isCompressed": true, "channels": 4 }, 112 | "astc-10x10-unorm": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 10, "isCompressed": true, "channels": 4 }, 113 | "astc-10x10-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 10, "blockHeight": 10, "isCompressed": true, "channels": 4 }, 114 | "astc-12x10-unorm": { "bytesPerBlock": 16, "blockWidth": 12, "blockHeight": 10, "isCompressed": true, "channels": 4 }, 115 | "astc-12x10-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 12, "blockHeight": 10, "isCompressed": true, "channels": 4 }, 116 | "astc-12x12-unorm": { "bytesPerBlock": 16, "blockWidth": 12, "blockHeight": 12, "isCompressed": true, "channels": 4 }, 117 | "astc-12x12-unorm-srgb": { "bytesPerBlock": 16, "blockWidth": 12, "blockHeight": 12, "isCompressed": true, "channels": 4 }, 118 | }; 119 | -------------------------------------------------------------------------------- /test/tests/test_scanner.js: -------------------------------------------------------------------------------- 1 | import { test, group } from "../test.js"; 2 | import { WgslScanner, TokenTypes } from "../../wgsl_reflect.module.js"; 3 | 4 | export async function run() { 5 | await group("Scanner", async function () { 6 | await test("(1+2)-3;", function (test) { 7 | const scanner = new WgslScanner("(1+2)-3;"); 8 | const tokens = scanner.scanTokens(); 9 | test.equals(tokens.length, 9); 10 | test.equals(tokens[5].type, TokenTypes.tokens.minus); 11 | }); 12 | await test("bar--;", function (test) { 13 | const scanner = new WgslScanner("bar--;"); 14 | const tokens = scanner.scanTokens(); 15 | test.equals(tokens.length, 4); 16 | test.equals(tokens[1].type, TokenTypes.tokens.minus_minus); 17 | }); 18 | await test("b-=1;", function (test) { 19 | const scanner = new WgslScanner("b-=1;"); 20 | const tokens = scanner.scanTokens(); 21 | test.equals(tokens.length, 5); 22 | test.equals(tokens[1].type, TokenTypes.tokens.minus_equal); 23 | }); 24 | await test("foo=bar--", function (test) { 25 | const scanner = new WgslScanner("foo=bar--"); 26 | const tokens = scanner.scanTokens(); 27 | test.equals(tokens.length, 5); 28 | test.equals(tokens[3].type, TokenTypes.tokens.minus_minus); 29 | }); 30 | await test("foo=-2", function (test) { 31 | const scanner = new WgslScanner("foo=-1;"); 32 | const tokens = scanner.scanTokens(); 33 | test.equals(tokens.length, 5); 34 | test.equals(tokens[2].type, TokenTypes.tokens.int_literal); 35 | }); 36 | 37 | await test("1-2", function (test) { 38 | const scanner = new WgslScanner("let foo=bar*2-1;"); 39 | const tokens = scanner.scanTokens(); 40 | test.equals(tokens.length, 10); 41 | test.equals(tokens[6].type, TokenTypes.tokens.minus); 42 | }); 43 | 44 | await test("default", function (test) { 45 | const scanner = new WgslScanner(); 46 | const tokens = scanner.scanTokens(); 47 | test.equals(tokens.length, 1); 48 | test.equals(tokens[0].type, TokenTypes.eof); 49 | }); 50 | 51 | await test("empty", function (test) { 52 | const scanner = new WgslScanner(""); 53 | const tokens = scanner.scanTokens(); 54 | test.equals(tokens.length, 1); 55 | test.equals(tokens[0].type, TokenTypes.eof); 56 | }); 57 | 58 | await test("newline", function (test) { 59 | const scanner = new WgslScanner("\n"); 60 | const tokens = scanner.scanTokens(); 61 | test.equals(tokens.length, 1); 62 | test.equals(tokens[0].type, TokenTypes.eof); 63 | }); 64 | 65 | await test("comment", function (test) { 66 | const scanner = new WgslScanner("\n// this is a comment\n"); 67 | const tokens = scanner.scanTokens(); 68 | test.equals(tokens.length, 1); 69 | test.equals(tokens[0].type, TokenTypes.eof); 70 | }); 71 | 72 | await test("block comment", function (test) { 73 | const scanner = new WgslScanner("\n/* this is\n a comment*/\n"); 74 | const tokens = scanner.scanTokens(); 75 | test.equals(tokens.length, 1); 76 | test.equals(tokens[0].type, TokenTypes.eof); 77 | }); 78 | 79 | await test("nested block comment", function (test) { 80 | const scanner = new WgslScanner("\nfoo/* this /*is\n a*/ comment*/\n"); 81 | const tokens = scanner.scanTokens(); 82 | test.equals(tokens.length, 2); 83 | test.equals(tokens[0].type, TokenTypes.tokens.ident); 84 | test.equals(tokens[1].type, TokenTypes.eof); 85 | }); 86 | 87 | await test("identifier", function (test) { 88 | const scanner = new WgslScanner("abc123"); 89 | const tokens = scanner.scanTokens(); 90 | test.equals(tokens.length, 2); 91 | test.equals(tokens[0].type, TokenTypes.tokens.ident); 92 | }); 93 | 94 | await test("123", function (test) { 95 | const scanner = new WgslScanner("123"); 96 | const tokens = scanner.scanTokens(); 97 | test.equals(tokens.length, 2); 98 | test.equals(tokens[0].type, TokenTypes.tokens.int_literal); 99 | }); 100 | 101 | await test("123.456", function (test) { 102 | const scanner = new WgslScanner("123.456"); 103 | const tokens = scanner.scanTokens(); 104 | test.equals(tokens.length, 2); 105 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 106 | }); 107 | 108 | await test(".456f", function (test) { 109 | const scanner = new WgslScanner(".456f"); 110 | const tokens = scanner.scanTokens(); 111 | test.equals(tokens.length, 2); 112 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 113 | }); 114 | 115 | await test("123.", function (test) { 116 | const scanner = new WgslScanner("123."); 117 | const tokens = scanner.scanTokens(); 118 | test.equals(tokens.length, 2); 119 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 120 | }); 121 | 122 | await test("-123", function (test) { 123 | const scanner = new WgslScanner("-123"); 124 | const tokens = scanner.scanTokens(); 125 | test.equals(tokens.length, 2); 126 | test.equals(tokens[0].type, TokenTypes.tokens.int_literal); 127 | test.equals(tokens[0].lexeme, "-123"); 128 | }); 129 | 130 | await test("-.123", function (test) { 131 | const scanner = new WgslScanner("-.123"); 132 | const tokens = scanner.scanTokens(); 133 | test.equals(tokens.length, 2); 134 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 135 | test.equals(tokens[0].lexeme, "-.123"); 136 | }); 137 | 138 | await test("123u", function (test) { 139 | const scanner = new WgslScanner("123u"); 140 | const tokens = scanner.scanTokens(); 141 | test.equals(tokens.length, 2); 142 | test.equals(tokens[0].type, TokenTypes.tokens.uint_literal); 143 | test.equals(tokens[0].lexeme, "123u"); 144 | }); 145 | 146 | await test("0i", function (test) { 147 | const scanner = new WgslScanner("0i"); 148 | const tokens = scanner.scanTokens(); 149 | test.equals(tokens.length, 2); 150 | test.equals(tokens[0].type, TokenTypes.tokens.int_literal); 151 | test.equals(tokens[0].lexeme, "0i"); 152 | }); 153 | 154 | await test("0u", function (test) { 155 | const scanner = new WgslScanner("0u"); 156 | const tokens = scanner.scanTokens(); 157 | test.equals(tokens.length, 2); 158 | test.equals(tokens[0].type, TokenTypes.tokens.uint_literal); 159 | test.equals(tokens[0].lexeme, "0u"); 160 | }); 161 | 162 | await test("0f", function (test) { 163 | const scanner = new WgslScanner("0f"); 164 | const tokens = scanner.scanTokens(); 165 | test.equals(tokens.length, 2); 166 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 167 | test.equals(tokens[0].lexeme, "0f"); 168 | }); 169 | 170 | await test("123i", function (test) { 171 | const scanner = new WgslScanner("123i"); 172 | const tokens = scanner.scanTokens(); 173 | test.equals(tokens.length, 2); 174 | test.equals(tokens[0].type, TokenTypes.tokens.int_literal); 175 | test.equals(tokens[0].lexeme, "123i"); 176 | }); 177 | 178 | await test("123f", function (test) { 179 | const scanner = new WgslScanner("123f"); 180 | const tokens = scanner.scanTokens(); 181 | test.equals(tokens.length, 2); 182 | test.equals(tokens[0].type, TokenTypes.tokens.decimal_float_literal); 183 | test.equals(tokens[0].lexeme, "123f"); 184 | }); 185 | 186 | await test("0x123u", function (test) { 187 | const scanner = new WgslScanner("0x123u"); 188 | const tokens = scanner.scanTokens(); 189 | test.equals(tokens.length, 2); 190 | test.equals(tokens[0].type, TokenTypes.tokens.uint_literal); 191 | test.equals(tokens[0].lexeme, "0x123u"); 192 | }); 193 | 194 | await test("0x.5", function (test) { 195 | const scanner = new WgslScanner("0x.5"); 196 | const tokens = scanner.scanTokens(); 197 | test.equals(tokens.length, 2); 198 | test.equals(tokens[0].type, TokenTypes.tokens.hex_float_literal); 199 | test.equals(tokens[0].lexeme, "0x.5"); 200 | }); 201 | 202 | await test("a.b", function (test) { 203 | const scanner = new WgslScanner("a.b"); 204 | const tokens = scanner.scanTokens(); 205 | test.equals(tokens.length, 4); 206 | test.equals(tokens[0].type, TokenTypes.tokens.ident); 207 | test.equals(tokens[0].lexeme, "a"); 208 | test.equals(tokens[1].type, TokenTypes.tokens.period); 209 | test.equals(tokens[2].type, TokenTypes.tokens.ident); 210 | test.equals(tokens[2].lexeme, "b"); 211 | }); 212 | 213 | await test("1>>2", function (test) { 214 | const scanner = new WgslScanner("1>>2"); 215 | const tokens = scanner.scanTokens(); 216 | test.equals(tokens.length, 4); 217 | test.equals(tokens[1].type, TokenTypes.tokens.shift_right); 218 | }); 219 | 220 | await test("1<2||a>>2==0", function (test) { 221 | // Syntatical ambiguity case for > vs >>. Here, >> should be a shift_right. 222 | const scanner = new WgslScanner("a<2||a>>2==0"); 223 | const tokens = scanner.scanTokens(); 224 | test.equals(tokens.length, 10); 225 | test.equals(tokens[5].type, TokenTypes.tokens.shift_right); 226 | }); 227 | 228 | await test("array", function (test) { 229 | const scanner = new WgslScanner("array"); 230 | const tokens = scanner.scanTokens(); 231 | test.equals(tokens.length, 7); 232 | test.equals(tokens[5].type, TokenTypes.tokens.greater_than); 233 | }); 234 | 235 | await test("array>", function (test) { 236 | // Syntatical ambiguity case for > vs >>. Here, >> should be two greater_than tokens. 237 | const scanner = new WgslScanner("array>"); 238 | const tokens = scanner.scanTokens(); 239 | test.equals(tokens.length, 8); 240 | test.equals(tokens[6].type, TokenTypes.tokens.greater_than); 241 | }); 242 | 243 | await test("array>", function (test) { 244 | // Syntatical ambiguity case for > vs >>. Here, >> should be two greater_than tokens. 245 | const scanner = new WgslScanner("array>"); 246 | const tokens = scanner.scanTokens(); 247 | test.equals(tokens.length, 10); 248 | test.equals(tokens[7].type, TokenTypes.tokens.greater_than); 249 | }); 250 | 251 | await test("let v: vec2= vec2(1,2);", function (test) { 252 | // Syntatical ambiguity case for =. Here, >= should be a greater_than and an equal. 253 | const scanner = new WgslScanner("let v: vec2= vec2(1,2);"); 254 | const tokens = scanner.scanTokens(); 255 | test.equals(tokens.length, 16); 256 | test.equals(tokens[6].type, TokenTypes.tokens.greater_than); 257 | test.equals(tokens[7].type, TokenTypes.tokens.equal); 258 | }); 259 | 260 | await test("nested array", function (test) { 261 | const scanner = new WgslScanner("foo[bar[0]]"); 262 | const tokens = scanner.scanTokens(); 263 | test.equals(tokens.length, 8); 264 | test.equals(tokens[6].type, TokenTypes.tokens.bracket_right); 265 | }); 266 | 267 | await test("fn foo(a, b) -> d { return; }", function (test) { 268 | const scanner = new WgslScanner(`fn foo(a, b) -> d { 269 | // skip this comment 270 | return; 271 | }`); 272 | const tokens = scanner.scanTokens(); 273 | const expected = [ 274 | TokenTypes.keywords.fn, 275 | TokenTypes.tokens.ident, 276 | TokenTypes.tokens.paren_left, 277 | TokenTypes.tokens.ident, 278 | TokenTypes.tokens.comma, 279 | TokenTypes.tokens.ident, 280 | TokenTypes.tokens.paren_right, 281 | TokenTypes.tokens.arrow, 282 | TokenTypes.tokens.ident, 283 | TokenTypes.tokens.brace_left, 284 | TokenTypes.keywords.return, 285 | TokenTypes.tokens.semicolon, 286 | TokenTypes.tokens.brace_right, 287 | TokenTypes.eof, 288 | ]; 289 | test.equals(tokens.length, expected.length); 290 | for (let i = 0; i < tokens.length; ++i) 291 | test.equals(tokens[i].type, expected[i]); 292 | }); 293 | 294 | await test("operators", function (test) { 295 | const scanner = new WgslScanner(`fn foo() { 296 | var b = 1; 297 | b+=1; 298 | b++; 299 | }`); 300 | const tokens = scanner.scanTokens(); 301 | test.equals(tokens.length, 19); 302 | }); 303 | 304 | await test('fn foo(value_1 : ptr>) {}', (test) => 305 | { 306 | const scanner = new WgslScanner(`fn foo(value_1 : ptr>) {}`); 307 | const tokens = scanner.scanTokens(); 308 | test.equals(tokens.length, 18); 309 | }); 310 | 311 | await test('fn foo(p : ptr, 4u>>) {}', (test) => 312 | { 313 | const scanner = new WgslScanner(`fn foo(p : ptr, 4u>>) {}`); 314 | const tokens = scanner.scanTokens(); 315 | test.equals(tokens.length, 23); 316 | test.equals(tokens[17].lexeme, '>'); 317 | test.equals(tokens[18].lexeme, '>'); 318 | }); 319 | 320 | await test(">=", function (test) { 321 | const scanner = new WgslScanner("vec3=a>=b"); 322 | const tokens = scanner.scanTokens(); 323 | test.equals(tokens.length, 9); 324 | test.equals(tokens[6].type, TokenTypes.tokens.greater_than_equal); 325 | }); 326 | }); 327 | } 328 | -------------------------------------------------------------------------------- /src/utils/texture_sample.ts: -------------------------------------------------------------------------------- 1 | import { float16ToFloat32, float32ToFloat16, float10ToFloat32, float11ToFloat32 } from "./float.js"; 2 | 3 | export function setTexturePixel(imageData: Uint8Array, x: number, y: number, z: number, mipLevel: number, 4 | height: number, bytesPerRow: number, texelByteSize: number, format: string, value: number[]): void { 5 | bytesPerRow = bytesPerRow >> mipLevel; 6 | height = height >> mipLevel; 7 | 8 | const offset = (z * bytesPerRow * height) + y * bytesPerRow + x * texelByteSize; 9 | 10 | switch (format) { 11 | case "r8unorm": { 12 | setPixelValue(imageData, offset, "8unorm", 1, value); 13 | return; 14 | } 15 | case "r8snorm": { 16 | setPixelValue(imageData, offset, "8snorm", 1, value); 17 | return; 18 | } 19 | case "r8uint": { 20 | setPixelValue(imageData, offset, "8uint", 1, value); 21 | return; 22 | } 23 | case "r8sint": { 24 | setPixelValue(imageData, offset, "8sint", 1, value); 25 | return; 26 | } 27 | 28 | case "rg8unorm": { 29 | setPixelValue(imageData, offset, "8unorm", 2, value); 30 | return; 31 | } 32 | case "rg8snorm": { 33 | setPixelValue(imageData, offset, "8snorm", 2, value); 34 | return; 35 | } 36 | case "rg8uint": { 37 | setPixelValue(imageData, offset, "8uint", 2, value); 38 | return; 39 | } 40 | case "rg8sint": { 41 | setPixelValue(imageData, offset, "8sint", 2, value); 42 | return; 43 | } 44 | 45 | case "rgba8unorm-srgb": 46 | case "rgba8unorm": { 47 | setPixelValue(imageData, offset, "8unorm", 4, value); 48 | return; 49 | } 50 | case "rgba8snorm": { 51 | setPixelValue(imageData, offset, "8snorm", 4, value); 52 | return; 53 | } 54 | case "rgba8uint": { 55 | setPixelValue(imageData, offset, "8uint", 4, value); 56 | return; 57 | } 58 | case "rgba8sint": { 59 | setPixelValue(imageData, offset, "8sint", 4, value); 60 | return; 61 | } 62 | 63 | case "bgra8unorm-srgb": 64 | case "bgra8unorm": { 65 | setPixelValue(imageData, offset, "8unorm", 4, value); 66 | return; 67 | } 68 | 69 | case "r16uint": { 70 | setPixelValue(imageData, offset, "16uint", 1, value); 71 | return; 72 | } 73 | case "r16sint": { 74 | setPixelValue(imageData, offset, "16sint", 1, value); 75 | return; 76 | } 77 | case "r16float": { 78 | setPixelValue(imageData, offset, "16float", 1, value); 79 | return; 80 | } 81 | 82 | case "rg16uint": { 83 | setPixelValue(imageData, offset, "16uint", 2, value); 84 | return; 85 | } 86 | case "rg16sint": { 87 | setPixelValue(imageData, offset, "16sint", 2, value); 88 | return; 89 | } 90 | case "rg16float": { 91 | setPixelValue(imageData, offset, "16float", 2, value); 92 | return; 93 | } 94 | 95 | case "rgba16uint": { 96 | setPixelValue(imageData, offset, "16uint", 4, value); 97 | return; 98 | } 99 | case "rgba16sint": { 100 | setPixelValue(imageData, offset, "16sint", 4, value); 101 | return; 102 | } 103 | case "rgba16float": { 104 | setPixelValue(imageData, offset, "16float", 4, value); 105 | return; 106 | } 107 | 108 | case "r32uint": { 109 | setPixelValue(imageData, offset, "32uint", 1, value); 110 | return; 111 | } 112 | case "r32sint": { 113 | setPixelValue(imageData, offset, "32sint", 1, value); 114 | return; 115 | } 116 | case "depth16unorm": // depth formats get conerted to r32float 117 | case "depth24plus": 118 | case "depth24plus-stencil8": 119 | case "depth32float": 120 | case "depth32float-stencil8": 121 | case "r32float": { 122 | setPixelValue(imageData, offset, "32float", 1, value); 123 | return; 124 | } 125 | case "rg32uint": { 126 | setPixelValue(imageData, offset, "32uint", 2, value); 127 | return; 128 | } 129 | case "rg32sint": { 130 | setPixelValue(imageData, offset, "32sint", 2, value); 131 | return; 132 | } 133 | case "rg32float": { 134 | setPixelValue(imageData, offset, "32float", 2, value); 135 | return; 136 | } 137 | case "rgba32uint": { 138 | setPixelValue(imageData, offset, "32uint", 4, value); 139 | return; 140 | } 141 | case "rgba32sint": { 142 | setPixelValue(imageData, offset, "32sint", 4, value); 143 | return; 144 | } 145 | case "rgba32float": { 146 | setPixelValue(imageData, offset, "32float", 4, value); 147 | return; 148 | } 149 | 150 | case "rg11b10ufloat": { 151 | console.error("TODO: rg11b10ufloat not supported for writing"); 152 | /*const uintValue = new Uint32Array(imageData.buffer, offset, 1)[0]; 153 | const ri = uintValue & 0x7FF; 154 | const gi = (uintValue & 0x3FF800) >> 11; 155 | const bi = (uintValue & 0xFFC00000) >> 22; 156 | const rf = float11ToFloat32(ri); 157 | const gf = float11ToFloat32(gi); 158 | const bf = float10ToFloat32(bi); 159 | return [rf, gf, bf, 1.0];*/ 160 | return; 161 | } 162 | } 163 | } 164 | 165 | export function getTexturePixel(imageData: Uint8Array, x: number, y: number, z: number, mipLevel: number, 166 | height: number, bytesPerRow: number, texelByteSize: number, format: string): number[] | null { 167 | bytesPerRow = bytesPerRow >> mipLevel; 168 | height = height >> mipLevel; 169 | 170 | const offset = (z * bytesPerRow * height) + y * bytesPerRow + x * texelByteSize; 171 | 172 | switch (format) { 173 | case "r8unorm": { 174 | const value = pixelValue(imageData, offset, "8unorm", 1); 175 | return [value[0]]; 176 | } 177 | case "r8snorm": { 178 | const value = pixelValue(imageData, offset, "8snorm", 1); 179 | return [value[0]]; 180 | } 181 | case "r8uint": { 182 | const value = pixelValue(imageData, offset, "8uint", 1); 183 | return [value[0]]; 184 | } 185 | case "r8sint": { 186 | const value = pixelValue(imageData, offset, "8sint", 1); 187 | return [value[0]]; 188 | } 189 | 190 | case "rg8unorm": { 191 | const value = pixelValue(imageData, offset, "8unorm", 2); 192 | return [value[0], value[1]]; 193 | } 194 | case "rg8snorm": { 195 | const value = pixelValue(imageData, offset, "8snorm", 2); 196 | return [value[0], value[1]]; 197 | } 198 | case "rg8uint": { 199 | const value = pixelValue(imageData, offset, "8uint", 2); 200 | return [value[0], value[1]]; 201 | } 202 | case "rg8sint": { 203 | const value = pixelValue(imageData, offset, "8sint", 2); 204 | return [value[0], value[1]]; 205 | } 206 | 207 | case "rgba8unorm-srgb": 208 | case "rgba8unorm": { 209 | const value = pixelValue(imageData, offset, "8unorm", 4); 210 | return [value[0], value[1], value[2], value[3]]; 211 | } 212 | case "rgba8snorm": { 213 | const value = pixelValue(imageData, offset, "8snorm", 4); 214 | return [value[0], value[1], value[2], value[3]]; 215 | } 216 | case "rgba8uint": { 217 | const value = pixelValue(imageData, offset, "8uint", 4); 218 | return [value[0], value[1], value[2], value[3]]; 219 | } 220 | case "rgba8sint": { 221 | const value = pixelValue(imageData, offset, "8sint", 4); 222 | return [value[0], value[1], value[2], value[3]]; 223 | } 224 | 225 | case "bgra8unorm-srgb": 226 | case "bgra8unorm": { 227 | const value = pixelValue(imageData, offset, "8unorm", 4); 228 | return [value[2], value[1], value[0], value[3]]; 229 | } 230 | 231 | case "r16uint": { 232 | const value = pixelValue(imageData, offset, "16uint", 1); 233 | return [value[0]]; 234 | } 235 | case "r16sint": { 236 | const value = pixelValue(imageData, offset, "16sint", 1); 237 | return [value[0]]; 238 | } 239 | case "r16float": { 240 | const value = pixelValue(imageData, offset, "16float", 1); 241 | return [value[0]]; 242 | } 243 | 244 | case "rg16uint": { 245 | const value = pixelValue(imageData, offset, "16uint", 2); 246 | return [value[0], value[1]]; 247 | } 248 | case "rg16sint": { 249 | const value = pixelValue(imageData, offset, "16sint", 2); 250 | return [value[0], value[1]]; 251 | } 252 | case "rg16float": { 253 | const value = pixelValue(imageData, offset, "16float", 2); 254 | return [value[0], value[1]]; 255 | } 256 | 257 | case "rgba16uint": { 258 | const value = pixelValue(imageData, offset, "16uint", 4); 259 | return [value[0], value[1], value[2], value[3]]; 260 | } 261 | case "rgba16sint": { 262 | const value = pixelValue(imageData, offset, "16sint", 4); 263 | return [value[0], value[1], value[2], value[3]]; 264 | } 265 | case "rgba16float": { 266 | const value = pixelValue(imageData, offset, "16float", 4); 267 | return [value[0], value[1], value[2], value[3]]; 268 | } 269 | 270 | case "r32uint": { 271 | const value = pixelValue(imageData, offset, "32uint", 1); 272 | return [value[0]]; 273 | } 274 | case "r32sint": { 275 | const value = pixelValue(imageData, offset, "32sint", 1); 276 | return [value[0]]; 277 | } 278 | case "depth16unorm": // depth formats get conerted to r32float 279 | case "depth24plus": 280 | case "depth24plus-stencil8": 281 | case "depth32float": 282 | case "depth32float-stencil8": 283 | case "r32float": { 284 | const value = pixelValue(imageData, offset, "32float", 1); 285 | return [value[0]]; 286 | } 287 | case "rg32uint": { 288 | const value = pixelValue(imageData, offset, "32uint", 2); 289 | return [value[0], value[1]]; 290 | } 291 | case "rg32sint": { 292 | const value = pixelValue(imageData, offset, "32sint", 2); 293 | return [value[0], value[1]]; 294 | } 295 | case "rg32float": { 296 | const value = pixelValue(imageData, offset, "32float", 2); 297 | return [value[0], value[1]]; 298 | } 299 | case "rgba32uint": { 300 | const value = pixelValue(imageData, offset, "32uint", 4); 301 | return [value[0], value[1], value[2], value[3]]; 302 | } 303 | case "rgba32sint": { 304 | const value = pixelValue(imageData, offset, "32sint", 4); 305 | return [value[0], value[1], value[2], value[3]]; 306 | } 307 | case "rgba32float": { 308 | const value = pixelValue(imageData, offset, "32float", 4); 309 | return [value[0], value[1], value[2], value[3]]; 310 | } 311 | 312 | case "rg11b10ufloat": { 313 | const uintValue = new Uint32Array(imageData.buffer, offset, 1)[0]; 314 | const ri = uintValue & 0x7FF; 315 | const gi = (uintValue & 0x3FF800) >> 11; 316 | const bi = (uintValue & 0xFFC00000) >> 22; 317 | const rf = float11ToFloat32(ri); 318 | const gf = float11ToFloat32(gi); 319 | const bf = float10ToFloat32(bi); 320 | return [rf, gf, bf, 1.0]; 321 | } 322 | } 323 | 324 | return null; 325 | } 326 | 327 | function pixelValue(imageData: Uint8Array, offset: number, format: string, numChannels: number) { 328 | const value = [0, 0, 0, 0]; 329 | for (let i = 0; i < numChannels; ++i) { 330 | switch (format) { 331 | case "8unorm": 332 | value[i] = imageData[offset] / 255; 333 | offset++; 334 | break; 335 | case "8snorm": 336 | value[i] = (imageData[offset] / 255) * 2 - 1; 337 | offset++; 338 | break; 339 | case "8uint": 340 | value[i] = imageData[offset]; 341 | offset++; 342 | break; 343 | case "8sint": 344 | value[i] = imageData[offset] - 127; 345 | offset++; 346 | break; 347 | case "16uint": 348 | value[i] = imageData[offset] | (imageData[offset + 1] << 8); 349 | offset += 2; 350 | break; 351 | case "16sint": 352 | value[i] = (imageData[offset] | (imageData[offset + 1] << 8)) - 32768; 353 | offset += 2; 354 | break; 355 | case "16float": 356 | value[i] = float16ToFloat32(imageData[offset] | (imageData[offset + 1] << 8)); 357 | offset += 2; 358 | break; 359 | case "32uint": 360 | value[i] = imageData[offset] | (imageData[offset + 1] << 8) | (imageData[offset + 2] << 16) | (imageData[offset + 3] << 24); 361 | offset += 4; 362 | break; 363 | case "32sint": 364 | value[i] = (imageData[offset] | (imageData[offset + 1] << 8) | (imageData[offset + 2] << 16) | (imageData[offset + 3] << 24)) | 0; 365 | offset += 4; 366 | break; 367 | case "32float": 368 | value[i] = new Float32Array(imageData.buffer, offset, 1)[0]; 369 | offset += 4; 370 | break; 371 | } 372 | } 373 | return value; 374 | } 375 | 376 | function setPixelValue(imageData: Uint8Array, offset: number, format: string, numChannels: number, value: number[]) { 377 | for (let i = 0; i < numChannels; ++i) { 378 | switch (format) { 379 | case "8unorm": 380 | imageData[offset] = value[i] * 255; 381 | offset++; 382 | break; 383 | case "8snorm": 384 | imageData[offset] = ((value[i] + 1.0) * 0.5) * 255; 385 | offset++; 386 | break; 387 | case "8uint": 388 | imageData[offset] = value[i]; 389 | offset++; 390 | break; 391 | case "8sint": 392 | imageData[offset] = value[i] + 127; 393 | offset++; 394 | break; 395 | case "16uint": 396 | new Uint16Array(imageData.buffer, offset, 1)[0] = value[i]; 397 | offset += 2; 398 | break; 399 | case "16sint": 400 | new Int16Array(imageData.buffer, offset, 1)[0] = value[i]; 401 | offset += 2; 402 | break; 403 | case "16float": { 404 | const f16 = float32ToFloat16(value[i]); 405 | new Uint16Array(imageData.buffer, offset, 1)[0] = f16; 406 | offset += 2; 407 | break; 408 | } 409 | case "32uint": 410 | new Uint32Array(imageData.buffer, offset, 1)[0] = value[i]; 411 | offset += 4; 412 | break; 413 | case "32sint": 414 | new Int32Array(imageData.buffer, offset, 1)[0] = value[i]; 415 | offset += 4; 416 | break; 417 | case "32float": 418 | new Float32Array(imageData.buffer, offset, 1)[0] = value[i]; 419 | offset += 4; 420 | break; 421 | } 422 | } 423 | return value; 424 | } 425 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGPU Shading Language Reflection Library 2 | 3 | A WebGPU Shading Language parser and reflection library for Typescript and Javascript. 4 | 5 | **wgsl_reflect** can parse a WGSL shader and analyze its contents, providing information about the shader. It can determine the bind group layout of the shader, resource bindings, uniform buffers, the members of a uniform buffer, their names, types, sizes, offsets into the buffer. 6 | 7 | ## Usage 8 | 9 | From NPM 10 | ``` 11 | npm install wgsl_reflect 12 | ``` 13 | 14 | The _wgsl_reflect.module.js_ file is a self-contained roll-up of the library that can be included in your project and imported with: 15 | 16 | ```javascript 17 | import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js"; 18 | const reflect = new WgslReflect(shader_code); 19 | ``` 20 | 21 | ## Example 22 | 23 | [WGSL Reflect Example](https://brendan-duncan.github.io/wgsl_reflect/example.html) 24 | 25 | ## Documentation 26 | 27 | ```javascript 28 | // A collection of gathered reflection information about the shader. 29 | class WgslReflect { 30 | // All top-level uniform vars in the shader. 31 | uniforms: Array; 32 | // All top-level storage vars in the shader, including storage buffers and textures. 33 | storage: Array; 34 | // All top-level texture vars in the shader; 35 | textures: Array; 36 | // All top-level sampler vars in the shader. 37 | samplers: Array; 38 | // All top-level type aliases in the shader. 39 | aliases: Array; 40 | // All top-level overrides in the shader. 41 | overrides: Array = []; 42 | // All top-level structs in the shader. 43 | structs: Array; 44 | // All entry functions in the shader: vertex, fragment, and/or compute. 45 | entry: EntryFunctions; 46 | // All functions in the shader, including entry functions. 47 | functions: Array; 48 | 49 | // Parse the given WGSL shader code, populating the info properties of this class. 50 | constructor(shader?: string); 51 | 52 | // Parse the given WGSL shader code, adding to the info properties of this class. 53 | update(shader: string); 54 | 55 | // Find a function or entry point with the given name. 56 | getFunctionInfo(name: string): FunctionInfo | null; 57 | 58 | // Find a struct with the given tname. 59 | getStructInfo(name: string): StructInfo | null; 60 | 61 | // Find an override with the given name. 62 | getOverrideInfo(name: string): OverrideInfo | null; 63 | 64 | // Find a resource by its group and binding. 65 | findResource(group: number, binding: number): VariableInfo | null; 66 | 67 | // Get the bind groups used by the shader, bindGroups[group][binding]. 68 | getBindGroups(): Array>; 69 | } 70 | 71 | // A variable can be a resource passed to the shader, of this type. 72 | enum ResourceType { 73 | Uniform, // Uniform buffer 74 | Storage, // Storage buffer 75 | Texture, // Texture 76 | Sampler, // Sampler to sample a Texture 77 | StorageTexture // StorageTexture 78 | } 79 | 80 | // Information about a resource variable. This will be a uniform buffer, 81 | // storage buffer, texture, sampler, or storageTexture. 82 | class VariableInfo { 83 | // The name of the variable. 84 | name: string; 85 | // The type of the variable. 86 | type: TypeInfo; 87 | // The binding group of the variable. 88 | group: number; 89 | // The binding index of the variable. 90 | binding: number; 91 | // The resource type of the variable. 92 | resourceType: ResourceType; 93 | // The access mode of the variable, can be: "", "read", "write", or "read_write". 94 | access: string; 95 | // Associated objects, for relating samplers to textures. 96 | // For a call to textureSample(texture, sampler), texture.relations will include sampler, and sampler.relations will include texture. 97 | relations: Array | null; 98 | 99 | // True if the type of the variable is an array. 100 | get isArray(): boolean; 101 | // True if the type of the variable is a struct. 102 | get isStruct(): boolean; 103 | // True if the type of the variable is a template. 104 | get isTemplate(): boolean; 105 | // Size of the data pointed to by the variable, in bytes. 106 | get size(): number; 107 | // The alignment size if the variable type is a struct, otherwise 0. 108 | get align(): number; 109 | // The list of members of the variable type if it's a struct, otherwise null. 110 | get members(): Array | null; 111 | // The format if the type is a template or array, otherwise null. 112 | get format(): TypeInfo | null; 113 | // The array size if it's an array, otherwise 0. 114 | get count(): number; 115 | // The array stride if it's an array, otherwise 0. 116 | get stride(): number; 117 | } 118 | 119 | // Base class for variable types. 120 | class TypeInfo { 121 | // The name of the type declaration. 122 | name: string; 123 | // Size of the data used by this type, in bytes 124 | size: number; 125 | 126 | // True if this is an array type, can be cast to ArrayInfo. 127 | get isArray(): boolean; 128 | // True if this is a struct type, can be cast to StructInfo. 129 | get isStruct(): boolean; 130 | // True if this is a template type, can be cast to TemplateInfo. 131 | get isTemplate(): boolean; 132 | } 133 | 134 | // Information about struct type declarations 135 | class StructInfo extends TypeInfo { 136 | // The list of members of the struct. 137 | members: Array; 138 | // The alignment, in bytes, for the structs data. 139 | align: number; 140 | // The line in the shader code the type declaration starts at. 141 | startLine: number; 142 | // The line in the shader code the type declaration ends at. 143 | endLine: number; 144 | // True if the struct is used by a uniform, storage, or directly or indirectly by an entry function. 145 | inUse: boolean; 146 | } 147 | 148 | // Information about array type declarations 149 | class ArrayInfo extends TypeInfo { 150 | // The format for the data in the array 151 | format: TypeInfo; 152 | // The number of elements in the array 153 | count: number; 154 | // The stride, in bytes, of the array. This is the alignment of elements in the array data, including padding. 155 | stride: number; 156 | } 157 | 158 | // Information about template type declarations 159 | class TemplateInfo extends TypeInfo { 160 | // The format type of the template 161 | format: TypeInfo; 162 | // Access mode of the template, which can be: "", "read", "write", or "read_write" 163 | access: string; 164 | } 165 | 166 | // Information about a struct member declaration. 167 | class MemberInfo { 168 | // The name of the struct member. 169 | name: string; 170 | // The type of the struct member. 171 | type: TypeInfo; 172 | // The offset, in bytes, of the member from the start of the struct data. 173 | offset: number; 174 | // The size of the members data, in bytes. 175 | size: number; 176 | 177 | // True if the member type is an array and can be cast to ArrayInfo 178 | get isArray(): boolean; 179 | // True if the member type is a struct and can be cast to StructInfo. 180 | get isStruct(): boolean; 181 | // True if the member type is a template and can be cast to TemplateInfo. 182 | get isTemplate(): boolean; 183 | // If the member type is a struct, the alignment of the struct in bytes, otherwise 0. 184 | get align(): number; 185 | // If the member type is a struct, the members of the struct, otherwise null. 186 | get members(): Array | null; 187 | // If the member type is an array or template, the format of the type, otherwise null. 188 | get format(): TypeInfo | null; 189 | // If the member type is an array, the number of elements in the array, otherwise 0. 190 | get count(): number; 191 | // If the member type is an array, the stride of the array elements in bytes, otherwise 0. 192 | get stride(): number; 193 | } 194 | 195 | // Information about type aliases declared in the shader. 196 | class AliasInfo { 197 | // The name of the alias type. 198 | name: string; 199 | // The information of the type being aliased. 200 | type: TypeInfo; 201 | } 202 | 203 | // The lists of shader vertex, fragment, and/or compute entry functions. 204 | class EntryFunctions { 205 | // Any vertex entry points in the shader. 206 | vertex: Array; 207 | // Any fragment entry points in the shader. 208 | fragment: Array; 209 | // Any compute entry points in the shader. 210 | compute: Array; 211 | } 212 | 213 | // Information about a function in the shader. 214 | class FunctionInfo { 215 | // The name of the function. 216 | name: string; 217 | // If the function is an entry function, which stage is it for, either "vertex", "fragment", "compute", or null if none. 218 | stage: string | null; 219 | // The list of shader inputs used by the function, which includes vertex and index buffers. 220 | inputs: Array; 221 | // The list of shader outputs updated by the function, such as inter-stage buffers. 222 | outputs: Array; 223 | // The arguments of the function. 224 | arguments: Array; 225 | // The return type of the function, or null if the function returns void. 226 | returnType: TypeInfo | null; 227 | // The resources used by the function, including uniform buffers, storage buffers, textures, 228 | // samplers, and storage textures. 229 | resources: Array; 230 | // The line in the shader the function definition starts at. 231 | startLine: number; 232 | // The line in the shader the function definition ends at. 233 | endLine: number; 234 | // True if called directly or indirectly by an entry function. 235 | inUse: boolean; 236 | // All custom functions called directly by this function. 237 | calls: Set; 238 | } 239 | 240 | // Information about a shader inputs. 241 | class InputInfo { 242 | // The name of the input variable 243 | name: string; 244 | // The type of the input variable. 245 | type: TypeInfo | null; 246 | // The location type of the input. 247 | locationType: string; 248 | // The location index or built-in location name. 249 | location: number | string; 250 | // The interpolation mode of the binding. 251 | interpolation: string | null; 252 | } 253 | 254 | // Information about a shader output. 255 | class OutputInfo { 256 | // The name of the output variable. 257 | name: string; 258 | // The type of the output variable. 259 | type: TypeInfo | null; 260 | // The location type of the output. 261 | locationType: string; 262 | // The location index or built-in location name. 263 | location: number | string; 264 | } 265 | 266 | // Information about override constants in the shader. 267 | class OverrideInfo { 268 | // The name of the override constant. 269 | name: string; 270 | // The type of the override constant. 271 | type: TypeInfo | null; 272 | // A unique ID given to the override constant. 273 | id: number; 274 | } 275 | 276 | // Information about a function argument. 277 | class ArgumentInfo { 278 | // Then ame of the argument variable. 279 | name: string; 280 | // The type of the argument variable. 281 | type: TypeInfo; 282 | } 283 | ``` 284 | 285 | ## Examples 286 | 287 | Calculate the bind group information in the shader: 288 | 289 | ```javascript 290 | import { WgslReflect } from "./wgsl_reflect.module.js"; 291 | 292 | const shader = ` 293 | struct ViewUniforms { 294 | viewProjection: mat4x4 295 | } 296 | 297 | struct ModelUniforms { 298 | model: mat4x4, 299 | color: vec4, 300 | intensity: f32 301 | } 302 | 303 | @binding(0) @group(0) var viewUniforms: ViewUniforms; 304 | @binding(1) @group(0) var modelUniforms: ModelUniforms; 305 | @binding(2) @group(0) var u_sampler: sampler; 306 | @binding(3) @group(0) var u_texture: texture_2d; 307 | 308 | struct VertexInput { 309 | @location(0) a_position: vec3, 310 | @location(1) a_normal: vec3, 311 | @location(2) a_color: vec4, 312 | @location(3) a_uv: vec2 313 | } 314 | 315 | struct VertexOutput { 316 | @builtin(position) Position: vec4, 317 | @location(0) v_position: vec4, 318 | @location(1) v_normal: vec3, 319 | @location(2) v_color: vec4, 320 | @location(3) v_uv: vec2 321 | } 322 | 323 | @vertex 324 | fn main(input: VertexInput) -> VertexOutput { 325 | var output: VertexOutput; 326 | output.Position = viewUniforms.viewProjection * modelUniforms.model * vec4(input.a_position, 1.0); 327 | output.v_position = output.Position; 328 | output.v_normal = input.a_normal; 329 | output.v_color = input.a_color * modelUniforms.color * modelUniforms.intensity; 330 | output.v_uv = input.a_uv; 331 | return output; 332 | }`; 333 | 334 | const reflect = new WgslReflect(shader); 335 | 336 | console.log(reflect.functions.length); // 1 337 | console.log(reflect.structs.length); // 4 338 | console.log(reflect.uniforms.length); // 2 339 | 340 | // Shader entry points 341 | console.log(reflect.entry.vertex.length); // 1, there is 1 vertex entry function. 342 | console.log(reflect.entry.fragment.length); // 0, there are no fragment entry functions. 343 | console.log(reflect.entry.compute.length); // 0, there are no compute entry functions. 344 | 345 | console.log(reflect.entry.vertex[0].name); // "main", the name of the vertex entry function. 346 | 347 | console.log(reflect.entry.vertex[0].resources.length); // 2, main uses modelUniforms and viewUniforms resource bindings. 348 | console.log(reflect.entry.vertex[0].resources[0].name); // viewUniforms 349 | console.log(reflect.entry.vertex[0].resources[1].name); // modelUniforms 350 | 351 | // Vertex shader inputs 352 | console.log(reflect.entry.vertex[0].inputs.length); // 4, inputs to "main" 353 | console.log(reflect.entry.vertex[0].inputs[0].name); // "a_position" 354 | console.log(reflect.entry.vertex[0].inputs[0].location); // 0 355 | console.log(reflect.entry.vertex[0].inputs[0].locationType); // "location" (can be "builtin") 356 | console.log(reflect.entry.vertex[0].inputs[0].type.name); // "vec3" 357 | console.log(reflect.entry.vertex[0].inputs[0].type.format.name); // "f32" 358 | 359 | // Gather the bind groups used by the shader. 360 | const groups = reflect.getBindGroups(); 361 | console.log(groups.length); // 1 362 | console.log(groups[0].length); // 4, bindings in group(0) 363 | 364 | console.log(groups[0][1].resourceType); // ResourceType.Uniform, the type of resource at group(0) binding(1) 365 | console.log(groups[0][1].size); // 96, the size of the uniform buffer. 366 | console.log(groups[0][1].members.length); // 3, members in ModelUniforms. 367 | console.log(groups[0][1].members[0].name); // "model", the name of the first member in the uniform buffer. 368 | console.log(groups[0][1].members[0].offset); // 0, the offset of 'model' in the uniform buffer. 369 | console.log(groups[0][1].members[0].size); // 64, the size of 'model'. 370 | console.log(groups[0][1].members[0].type.name); // "mat4x4", the type of 'model'. 371 | console.log(groups[0][1].members[0].type.format.name); // "f32", the format of the mat4x4. 372 | 373 | console.log(groups[0][2].resourceType); // ResourceType.Sampler 374 | 375 | console.log(groups[0][3].resourceType); // ResourceType.Texture 376 | console.log(groups[0][3].type.name); // "texture_2d" 377 | console.log(groups[0][3].type.format.name); // "f32" 378 | ``` 379 | 380 | --- 381 | 382 | Calculate the member information for a uniform buffer block: 383 | 384 | ```javascript 385 | import { WgslReflect } from "./wgsl_reflect.module.js"; 386 | 387 | // WgslReflect can calculate the size and offset for members of a uniform buffer block. 388 | 389 | const shader = ` 390 | struct A { // align(8) size(32) 391 | u: f32, // offset(0) align(4) size(4) 392 | v: f32, // offset(4) align(4) size(4) 393 | w: vec2, // offset(8) align(8) size(8) 394 | @size(16) x: f32 // offset(16) align(4) size(16) 395 | } 396 | 397 | struct B { // align(16) size(208) 398 | a: vec2, // offset(0) align(8) size(8) 399 | // -- implicit member alignment padding -- // offset(8) size(8) 400 | b: vec3, // offset(16) align(16) size(12) 401 | c: f32, // offset(28) align(4) size(4) 402 | d: f32, // offset(32) align(4) size(4) 403 | // -- implicit member alignment padding -- // offset(36) size(12) 404 | @align(16) e: A, // offset(48) align(16) size(32) 405 | f: vec3, // offset(80) align(16) size(12) 406 | // -- implicit member alignment padding -- // offset(92) size(4) 407 | g: @stride(32) array, // offset(96) align(8) size(96) 408 | h: i32, // offset(192) align(4) size(4) 409 | // -- implicit struct size padding -- // offset(196) size(12) 410 | } 411 | 412 | @group(0) @binding(0) 413 | var uniform_buffer: B;`; 414 | 415 | const reflect = new WgslReflect(shader); 416 | 417 | const u = reflect.uniforms[0]; 418 | console.log(u.size); // 208, the size of the uniform buffer in bytes 419 | console.log(u.group); // 0 420 | console.log(u.binding); // 0 421 | console.log(u.members.length); // 8, members in B 422 | console.log(u.members[0].name); // "a" 423 | console.log(u.members[0].offset); // 0, the offset of 'a' in the buffer 424 | console.log(u.members[0].size); // 8, the size of 'a' in bytes 425 | console.log(u.members[0].type.name); // "vec2", the type of 'a' 426 | console.log(u.members[0].type.format.name); // "f32", the format of the vec2. 427 | 428 | console.log(u.members[4].name); // "e" 429 | console.log(u.members[4].offset); // 48, the offset of 'e' in the buffer 430 | console.log(u.members[4].size); // 32, the size of 'e' in the buffer 431 | ``` 432 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | export class Test { 3 | static isArray(obj) { 4 | return obj && (obj.constructor === Array || 5 | (obj.buffer && obj.buffer.constructor === ArrayBuffer)); 6 | } 7 | 8 | static isObject(obj) { 9 | return obj && obj.constructor === Object; 10 | } 11 | 12 | constructor() { 13 | this.state = true; 14 | this.messages = []; 15 | this._log = []; 16 | } 17 | 18 | log() { 19 | console.log(...arguments); 20 | this._log.push(Array.prototype.slice.call(arguments)); 21 | } 22 | 23 | fail(message) { 24 | this.state = false; 25 | this.messages.push(message || "failed"); 26 | } 27 | 28 | true(b, message) { 29 | if (!b) { 30 | this.state = false; 31 | this.messages.push(message || "!true"); 32 | } 33 | } 34 | 35 | false(b, message) { 36 | if (b) { 37 | this.state = false; 38 | this.messages.push(message || "!false"); 39 | } 40 | } 41 | 42 | _closeTo(a, b, e = 1.0e-6) { 43 | return Math.abs(b - a) <= e; 44 | } 45 | 46 | closeTo(a, b, e = 1.0e-6, message) { 47 | if (e.constructor === String) { 48 | message = e; 49 | e = 1.0e-6; 50 | } 51 | 52 | if (Test.isArray(a) && Test.isArray(b)) { 53 | let al = a.length; 54 | let bl = b.length; 55 | if (al != bl) { 56 | this.state = false; 57 | if (message) { 58 | this.messages.push(message); 59 | } else { 60 | this.messages.push(a, "!=", b); 61 | } 62 | return; 63 | } 64 | for (let i = 0, l = a.length; i < l; ++i) { 65 | if (!this._closeTo(a[i], b[i], e)) { 66 | this.state = false; 67 | if (message) { 68 | this.messages.push(message); 69 | } else { 70 | this.messages.push(a, "!=", b); 71 | } 72 | return; 73 | } 74 | } 75 | return; 76 | } 77 | 78 | if (!this._closeTo(a, b, e)) { 79 | this.state = false; 80 | if (message) { 81 | this.messages.push(message); 82 | } else { 83 | this.messages.push(a, "!=", b); 84 | } 85 | } 86 | } 87 | 88 | objectEquals(a, b, message) { 89 | if (a !== b) { 90 | this.state = false; 91 | if (message) { 92 | this.messages.push(message); 93 | } else { 94 | this.messages.push(a, "!=", b); 95 | } 96 | return; 97 | } 98 | } 99 | 100 | objectNotEquals(a, b, message) { 101 | if (a === b) { 102 | this.state = false; 103 | if (message) { 104 | this.messages.push(message); 105 | } else { 106 | this.messages.push(a, "!=", b); 107 | } 108 | return; 109 | } 110 | } 111 | 112 | _error(message) { 113 | this.state = false; 114 | this.messages.push(message); 115 | return false; 116 | } 117 | 118 | validateObject(object, validator, message) { 119 | if (object === undefined || typeof(object) != typeof(validator)) { 120 | if (typeof(validator) == "string") { 121 | if (object) { 122 | if (object.toString() == validator) { 123 | return true; 124 | } 125 | } 126 | } 127 | return this._error(message || `type mismatch ${typeof(object)} != ${typeof(validator)} : ${object} ${validator}`); 128 | } 129 | 130 | if (Test.isArray(object)) { 131 | if (!Test.isArray(validator)) { 132 | return this._error(message || `array mismatch`); 133 | } 134 | if (validator.length != object.length) { 135 | return this._error(message || `array length mismatch: ${validator.length} != ${object.length}`); 136 | } 137 | for (let i = 0, l = validator.length; i < l; ++i) { 138 | if (!this.validateObject(object[i], validator[i])) 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | if (typeof(object) != "object") { 145 | if (object !== validator) { 146 | return this._error(message || `value mismatch: ${object} != ${validator}`); 147 | } 148 | return true; 149 | } 150 | 151 | for (let p in validator) { 152 | let gp = object[p]; 153 | let vp = validator[p]; 154 | if (!this.validateObject(gp, vp)) { 155 | return false; 156 | } 157 | } 158 | 159 | return true; 160 | } 161 | 162 | equals(a, b, epsilon_message, message) { 163 | if (a === b) { 164 | return; 165 | } 166 | 167 | let epsilon = typeof(epsilon_message) === "number" ? epsilon_message : undefined; 168 | message = typeof(epsilon_message) === "string" ? epsilon_message : message; 169 | 170 | if (Test.isArray(a) && Test.isArray(b)) { 171 | let al = a.length; 172 | let bl = b.length; 173 | if (al != bl) { 174 | this.state = false; 175 | if (message) { 176 | this.messages.push(message); 177 | } else { 178 | this.messages.push(a.toString(), "!=", b.toString()); 179 | } 180 | return; 181 | } 182 | for (let i = 0, l = a.length; i < l; ++i) { 183 | if (epsilon !== undefined) { 184 | if (Math.abs(a[i] - b[i]) > epsilon) { 185 | this.state = false; 186 | if (message) { 187 | this.messages.push(message); 188 | } else { 189 | this.messages.push(a.toString(), "!=", b.toString()); 190 | } 191 | return; 192 | } 193 | } else if (a[i] != b[i]) { 194 | this.state = false; 195 | if (message) { 196 | this.messages.push(message); 197 | } else { 198 | this.messages.push(a, "!=", b); 199 | } 200 | return; 201 | } 202 | } 203 | return; 204 | } 205 | 206 | if (epsilon !== undefined) { 207 | if (Math.abs(a - b) > epsilon) { 208 | this.state = false; 209 | if (message) { 210 | this.messages.push(message); 211 | } else { 212 | this.messages.push(a, "!=", b); 213 | } 214 | } 215 | } else if (a != b) { 216 | this.state = false; 217 | if (message) { 218 | this.messages.push(message); 219 | } else { 220 | this.messages.push(a, "!=", b); 221 | } 222 | } 223 | } 224 | 225 | notEquals(a, b, message) { 226 | if (Test.isArray(a) && Test.isArray(b)) { 227 | if (a.length != b.length) { 228 | return; 229 | } 230 | let found = false; 231 | for (let i = 0, l = a.length; i < l; ++i) { 232 | if (a[i] != b[i]) { 233 | found = true; 234 | } 235 | } 236 | if (!found) { 237 | this.state = false; 238 | if (message) { 239 | this.messages.push(message); 240 | } else { 241 | this.messages.push(a, "==", b); 242 | } 243 | return; 244 | } 245 | return; 246 | } 247 | if (a == b) { 248 | this.state = false; 249 | if (message) { 250 | this.messages.push(message); 251 | } else { 252 | this.messages.push(a, "==", b); 253 | } 254 | } 255 | } 256 | 257 | defined(a, message) { 258 | if (a === undefined) { 259 | this.state = false; 260 | this.messages.push(message || (a + " undefined")); 261 | } 262 | } 263 | 264 | undefined(a, message) { 265 | if (a !== undefined) { 266 | this.state = false; 267 | this.messages.push(message || (a + " defined")); 268 | } 269 | } 270 | 271 | isNull(a, message) { 272 | if (a !== undefined && a !== null) { 273 | this.state = false; 274 | this.messages.push(message || "expected null"); 275 | } 276 | } 277 | 278 | notNull(a, message) { 279 | if (a === undefined || a === null) { 280 | this.state = false; 281 | this.messages.push(message || "expected not null"); 282 | } 283 | } 284 | } 285 | 286 | export const __test = { 287 | totalTests: 0, 288 | totalFailed: 0, 289 | }; 290 | 291 | let __group = { 292 | group: undefined, 293 | numTests: 0, 294 | testsFailed: 0, 295 | skipCatchError: false 296 | }; 297 | 298 | function _copy(src) { 299 | const dst = new Uint8Array(src.byteLength); 300 | dst.set(new Uint8Array(src)); 301 | return dst.buffer; 302 | } 303 | 304 | let __device = null; 305 | async function getWebGPUDevice() { 306 | if (__device !== null) { 307 | return __device; 308 | } 309 | const adapter = await navigator.gpu.requestAdapter(); 310 | __device = await adapter.requestDevice(); 311 | 312 | __device.addEventListener('uncapturederror', (event) => { 313 | console.error(event.error.message); 314 | }); 315 | 316 | return __device; 317 | } 318 | 319 | export async function shutdownDevice() { 320 | const dev = __device; 321 | if (dev !== null) { 322 | dev.destroy(); 323 | __device = null; 324 | } 325 | } 326 | 327 | export async function webgpuDispatch(shader, module, dispatchCount, bindgroupData, options) { 328 | const device = await getWebGPUDevice(); 329 | 330 | if (dispatchCount.length === undefined) { 331 | dispatchCount = [dispatchCount, 1, 1]; 332 | } 333 | 334 | const readbackBuffers = []; 335 | const bindGroups = {}; 336 | 337 | for (const group in bindgroupData) { 338 | for (const _binding in bindgroupData[group]) { 339 | const binding = parseInt(_binding); 340 | const data = bindgroupData[group][_binding]; 341 | if (data.buffer instanceof ArrayBuffer) { 342 | const bufferSize = data.byteLength; 343 | 344 | const storageBuffer = device.createBuffer({ 345 | size: bufferSize, 346 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, 347 | }); 348 | device.queue.writeBuffer(storageBuffer, 0, data); 349 | 350 | if (bindGroups[group] === undefined) { 351 | bindGroups[group] = []; 352 | } 353 | bindGroups[group].push({ binding, resource: { buffer: storageBuffer } }); 354 | 355 | const readbackBuffer = device.createBuffer({ 356 | size: bufferSize, 357 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, 358 | }); 359 | readbackBuffers.push([storageBuffer, readbackBuffer, bufferSize]); 360 | } else if (data.uniform !== undefined) { 361 | const bufferSize = data.uniform.byteLength; 362 | 363 | const uniformBuffer = device.createBuffer({ 364 | size: bufferSize, 365 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 366 | }); 367 | device.queue.writeBuffer(uniformBuffer, 0, data.uniform); 368 | 369 | if (bindGroups[group] === undefined) { 370 | bindGroups[group] = []; 371 | } 372 | bindGroups[group].push({ binding, resource: { buffer: uniformBuffer } }); 373 | } else if (data.texture !== undefined && data.descriptor !== undefined) { 374 | const texture = device.createTexture(data.descriptor); 375 | 376 | // TODO: this is hard-coded to assume 4 bytes per pixel and no mips 377 | device.queue.writeTexture({ texture }, data.texture[0], {bytesPerRow: data.descriptor.size[0] * 4, 378 | rowsPerImage:data.descriptor.size[1]}, 379 | {width: data.descriptor.size[0], height: data.descriptor.size[1], depthOrArrayLayers:data.descriptor.size[2]}); 380 | if (bindGroups[group] === undefined) { 381 | bindGroups[group] = []; 382 | } 383 | 384 | bindGroups[group].push({ binding, resource: texture.createView() }); 385 | } 386 | } 387 | } 388 | 389 | const shaderModule = device.createShaderModule({code: shader}); 390 | const info = await shaderModule.getCompilationInfo(); 391 | if (info.messages.length) { 392 | for (const m of info.messages) { 393 | console.log(`${m.lineNum}:${m.linePos}: ${m.message}`); 394 | } 395 | throw new Error("Shader compilation failed"); 396 | } 397 | 398 | let constants = {}; 399 | if (options !== undefined) { 400 | if (options.constants !== undefined) { 401 | constants = options.constants 402 | } 403 | } 404 | 405 | const computePipeline = device.createComputePipeline({ 406 | layout: "auto", 407 | compute: { module: shaderModule, entryPoint: module, constants } 408 | }); 409 | 410 | const commandEncoder = device.createCommandEncoder(); 411 | const computePass = commandEncoder.beginComputePass(); 412 | computePass.setPipeline(computePipeline); 413 | 414 | for (const group in bindGroups) { 415 | const groupIndex = parseInt(group); 416 | const bindings = bindGroups[group]; 417 | const bindGroup = device.createBindGroup({ 418 | layout: computePipeline.getBindGroupLayout(groupIndex), 419 | entries: bindings 420 | }); 421 | computePass.setBindGroup(groupIndex, bindGroup); 422 | } 423 | 424 | computePass.dispatchWorkgroups(...dispatchCount); 425 | computePass.end(); 426 | 427 | device.queue.submit([commandEncoder.finish()]); 428 | 429 | const copyEncoder = device.createCommandEncoder(); 430 | for (const b of readbackBuffers) { 431 | copyEncoder.copyBufferToBuffer(b[0], 0, b[1], 0, b[2]); 432 | } 433 | device.queue.submit([copyEncoder.finish()]); 434 | 435 | const results = []; 436 | for (const b of readbackBuffers) { 437 | await b[1].mapAsync(GPUMapMode.READ, 0, b[2]); 438 | const mappedArray = _copy(b[1].getMappedRange(0, b[2])); 439 | b[1].unmap(); 440 | b[0].destroy(); 441 | b[1].destroy(); 442 | results.push(mappedArray); 443 | } 444 | 445 | if (results.length === 1) { 446 | return results[0]; 447 | } 448 | return results; 449 | } 450 | 451 | export async function group(name, f, skipCatchError) { 452 | let div = document.createElement("div"); 453 | div.className = "test_group"; 454 | div.textContent = name; 455 | document.body.append(div); 456 | 457 | const group = { 458 | group: div, 459 | numTests: 0, 460 | testsFailed: 0, 461 | skipCatchError: !!skipCatchError 462 | }; 463 | 464 | __group = group; 465 | 466 | if (skipCatchError) { 467 | await f() 468 | } else { 469 | try { 470 | await f(); 471 | } catch (error) { 472 | div = document.createElement("div"); 473 | div.className = "test_status_fail"; 474 | div.textContent = `${error}`; 475 | document.body.appendChild(div); 476 | } 477 | } 478 | 479 | div = document.createElement("div"); 480 | document.body.appendChild(div); 481 | 482 | const numPassed = group.numTests - group.testsFailed; 483 | div.className = (group.testsFailed > 0) ? "test_status_fail" : "test_status_pass"; 484 | div.textContent = `Tests: ${numPassed} / ${group.numTests}`; 485 | 486 | __test.totalTests += group.numTests; 487 | __test.totalFailed += group.testsFailed; 488 | } 489 | 490 | function _printLog(log) { 491 | const space = ""; 492 | for (const l of log) { 493 | const div = document.createElement("div"); 494 | div.className = "test_log"; 495 | div.innerHTML = l.join(space); 496 | if (group.group !== undefined) { 497 | group.group.appendChild(div); 498 | } else { 499 | document.body.appendChild(div); 500 | } 501 | } 502 | } 503 | 504 | export async function test(name, func, skipCatchError) { 505 | const t = new Test(); 506 | const group = __group; 507 | group.numTests++; 508 | 509 | skipCatchError = !!skipCatchError || group.skipCatchError; 510 | 511 | if (skipCatchError) { 512 | await func(t); 513 | } else { 514 | try { 515 | await func(t); 516 | } catch (error) { 517 | group.testsFailed++; 518 | const div = document.createElement("div"); 519 | div.className = "test_fail"; 520 | let stack = ""; 521 | if (error.stack !== undefined) { 522 | stack = ` ${error.stack}`; 523 | } 524 | if (error.fileName !== undefined) { 525 | div.textContent = `${name} FAILED: ${error.fileName}:${error.lineNumber}: ${error} ${stack}`; 526 | } else { 527 | div.textContent = `${name} FAILED: ${error} ${stack}`; 528 | } 529 | 530 | if (group.group !== undefined) { 531 | group.group.appendChild(div); 532 | } else { 533 | document.body.append(div); 534 | } 535 | 536 | _printLog(t._log); 537 | 538 | return; 539 | } 540 | } 541 | 542 | let msg = ""; 543 | if (!t.state) { 544 | group.testsFailed++; 545 | for (let m of t.messages) { 546 | msg += " : " + m; 547 | } 548 | } 549 | 550 | const div = document.createElement("div"); 551 | div.className = t.state ? "test_pass" : "test_fail"; 552 | div.textContent = `${name} ${t.state ? "PASSED" : "FAILED"}: ${msg}`; 553 | 554 | if (group.group != undefined) { 555 | group.group.appendChild(div); 556 | } else { 557 | document.body.append(div); 558 | } 559 | 560 | _printLog(t._log); 561 | } 562 | 563 | -------------------------------------------------------------------------------- /types/utils/texture_format_info.d.ts: -------------------------------------------------------------------------------- 1 | export declare const TextureFormatInfo: { 2 | r8unorm: { 3 | bytesPerBlock: number; 4 | blockWidth: number; 5 | blockHeight: number; 6 | isCompressed: boolean; 7 | channels: number; 8 | }; 9 | r8snorm: { 10 | bytesPerBlock: number; 11 | blockWidth: number; 12 | blockHeight: number; 13 | isCompressed: boolean; 14 | channels: number; 15 | }; 16 | r8uint: { 17 | bytesPerBlock: number; 18 | blockWidth: number; 19 | blockHeight: number; 20 | isCompressed: boolean; 21 | channels: number; 22 | }; 23 | r8sint: { 24 | bytesPerBlock: number; 25 | blockWidth: number; 26 | blockHeight: number; 27 | isCompressed: boolean; 28 | channels: number; 29 | }; 30 | rg8unorm: { 31 | bytesPerBlock: number; 32 | blockWidth: number; 33 | blockHeight: number; 34 | isCompressed: boolean; 35 | channels: number; 36 | }; 37 | rg8snorm: { 38 | bytesPerBlock: number; 39 | blockWidth: number; 40 | blockHeight: number; 41 | isCompressed: boolean; 42 | channels: number; 43 | }; 44 | rg8uint: { 45 | bytesPerBlock: number; 46 | blockWidth: number; 47 | blockHeight: number; 48 | isCompressed: boolean; 49 | channels: number; 50 | }; 51 | rg8sint: { 52 | bytesPerBlock: number; 53 | blockWidth: number; 54 | blockHeight: number; 55 | isCompressed: boolean; 56 | channels: number; 57 | }; 58 | rgba8unorm: { 59 | bytesPerBlock: number; 60 | blockWidth: number; 61 | blockHeight: number; 62 | isCompressed: boolean; 63 | channels: number; 64 | }; 65 | "rgba8unorm-srgb": { 66 | bytesPerBlock: number; 67 | blockWidth: number; 68 | blockHeight: number; 69 | isCompressed: boolean; 70 | channels: number; 71 | }; 72 | rgba8snorm: { 73 | bytesPerBlock: number; 74 | blockWidth: number; 75 | blockHeight: number; 76 | isCompressed: boolean; 77 | channels: number; 78 | }; 79 | rgba8uint: { 80 | bytesPerBlock: number; 81 | blockWidth: number; 82 | blockHeight: number; 83 | isCompressed: boolean; 84 | channels: number; 85 | }; 86 | rgba8sint: { 87 | bytesPerBlock: number; 88 | blockWidth: number; 89 | blockHeight: number; 90 | isCompressed: boolean; 91 | channels: number; 92 | }; 93 | bgra8unorm: { 94 | bytesPerBlock: number; 95 | blockWidth: number; 96 | blockHeight: number; 97 | isCompressed: boolean; 98 | channels: number; 99 | }; 100 | "bgra8unorm-srgb": { 101 | bytesPerBlock: number; 102 | blockWidth: number; 103 | blockHeight: number; 104 | isCompressed: boolean; 105 | channels: number; 106 | }; 107 | r16uint: { 108 | bytesPerBlock: number; 109 | blockWidth: number; 110 | blockHeight: number; 111 | isCompressed: boolean; 112 | channels: number; 113 | }; 114 | r16sint: { 115 | bytesPerBlock: number; 116 | blockWidth: number; 117 | blockHeight: number; 118 | isCompressed: boolean; 119 | channels: number; 120 | }; 121 | r16float: { 122 | bytesPerBlock: number; 123 | blockWidth: number; 124 | blockHeight: number; 125 | isCompressed: boolean; 126 | channels: number; 127 | }; 128 | rg16uint: { 129 | bytesPerBlock: number; 130 | blockWidth: number; 131 | blockHeight: number; 132 | isCompressed: boolean; 133 | channels: number; 134 | }; 135 | rg16sint: { 136 | bytesPerBlock: number; 137 | blockWidth: number; 138 | blockHeight: number; 139 | isCompressed: boolean; 140 | channels: number; 141 | }; 142 | rg16float: { 143 | bytesPerBlock: number; 144 | blockWidth: number; 145 | blockHeight: number; 146 | isCompressed: boolean; 147 | channels: number; 148 | }; 149 | rgba16uint: { 150 | bytesPerBlock: number; 151 | blockWidth: number; 152 | blockHeight: number; 153 | isCompressed: boolean; 154 | channels: number; 155 | }; 156 | rgba16sint: { 157 | bytesPerBlock: number; 158 | blockWidth: number; 159 | blockHeight: number; 160 | isCompressed: boolean; 161 | channels: number; 162 | }; 163 | rgba16float: { 164 | bytesPerBlock: number; 165 | blockWidth: number; 166 | blockHeight: number; 167 | isCompressed: boolean; 168 | channels: number; 169 | }; 170 | r32uint: { 171 | bytesPerBlock: number; 172 | blockWidth: number; 173 | blockHeight: number; 174 | isCompressed: boolean; 175 | channels: number; 176 | }; 177 | r32sint: { 178 | bytesPerBlock: number; 179 | blockWidth: number; 180 | blockHeight: number; 181 | isCompressed: boolean; 182 | channels: number; 183 | }; 184 | r32float: { 185 | bytesPerBlock: number; 186 | blockWidth: number; 187 | blockHeight: number; 188 | isCompressed: boolean; 189 | channels: number; 190 | }; 191 | rg32uint: { 192 | bytesPerBlock: number; 193 | blockWidth: number; 194 | blockHeight: number; 195 | isCompressed: boolean; 196 | channels: number; 197 | }; 198 | rg32sint: { 199 | bytesPerBlock: number; 200 | blockWidth: number; 201 | blockHeight: number; 202 | isCompressed: boolean; 203 | channels: number; 204 | }; 205 | rg32float: { 206 | bytesPerBlock: number; 207 | blockWidth: number; 208 | blockHeight: number; 209 | isCompressed: boolean; 210 | channels: number; 211 | }; 212 | rgba32uint: { 213 | bytesPerBlock: number; 214 | blockWidth: number; 215 | blockHeight: number; 216 | isCompressed: boolean; 217 | channels: number; 218 | }; 219 | rgba32sint: { 220 | bytesPerBlock: number; 221 | blockWidth: number; 222 | blockHeight: number; 223 | isCompressed: boolean; 224 | channels: number; 225 | }; 226 | rgba32float: { 227 | bytesPerBlock: number; 228 | blockWidth: number; 229 | blockHeight: number; 230 | isCompressed: boolean; 231 | channels: number; 232 | }; 233 | rgb10a2uint: { 234 | bytesPerBlock: number; 235 | blockWidth: number; 236 | blockHeight: number; 237 | isCompressed: boolean; 238 | channels: number; 239 | }; 240 | rgb10a2unorm: { 241 | bytesPerBlock: number; 242 | blockWidth: number; 243 | blockHeight: number; 244 | isCompressed: boolean; 245 | channels: number; 246 | }; 247 | rg11b10ufloat: { 248 | bytesPerBlock: number; 249 | blockWidth: number; 250 | blockHeight: number; 251 | isCompressed: boolean; 252 | channels: number; 253 | }; 254 | stencil8: { 255 | bytesPerBlock: number; 256 | blockWidth: number; 257 | blockHeight: number; 258 | isCompressed: boolean; 259 | isDepthStencil: boolean; 260 | hasDepth: boolean; 261 | hasStencil: boolean; 262 | channels: number; 263 | }; 264 | depth16unorm: { 265 | bytesPerBlock: number; 266 | blockWidth: number; 267 | blockHeight: number; 268 | isCompressed: boolean; 269 | isDepthStencil: boolean; 270 | hasDepth: boolean; 271 | hasStencil: boolean; 272 | channels: number; 273 | }; 274 | depth24plus: { 275 | bytesPerBlock: number; 276 | blockWidth: number; 277 | blockHeight: number; 278 | isCompressed: boolean; 279 | isDepthStencil: boolean; 280 | hasDepth: boolean; 281 | hasStencil: boolean; 282 | depthOnlyFormat: string; 283 | channels: number; 284 | }; 285 | "depth24plus-stencil8": { 286 | bytesPerBlock: number; 287 | blockWidth: number; 288 | blockHeight: number; 289 | isCompressed: boolean; 290 | isDepthStencil: boolean; 291 | hasDepth: boolean; 292 | hasStencil: boolean; 293 | depthOnlyFormat: string; 294 | channels: number; 295 | }; 296 | depth32float: { 297 | bytesPerBlock: number; 298 | blockWidth: number; 299 | blockHeight: number; 300 | isCompressed: boolean; 301 | isDepthStencil: boolean; 302 | hasDepth: boolean; 303 | hasStencil: boolean; 304 | channels: number; 305 | }; 306 | "depth32float-stencil8": { 307 | bytesPerBlock: number; 308 | blockWidth: number; 309 | blockHeight: number; 310 | isCompressed: boolean; 311 | isDepthStencil: boolean; 312 | hasDepth: boolean; 313 | hasStencil: boolean; 314 | stencilOnlyFormat: string; 315 | channels: number; 316 | }; 317 | rgb9e5ufloat: { 318 | bytesPerBlock: number; 319 | blockWidth: number; 320 | blockHeight: number; 321 | isCompressed: boolean; 322 | channels: number; 323 | }; 324 | "bc1-rgba-unorm": { 325 | bytesPerBlock: number; 326 | blockWidth: number; 327 | blockHeight: number; 328 | isCompressed: boolean; 329 | channels: number; 330 | }; 331 | "bc1-rgba-unorm-srgb": { 332 | bytesPerBlock: number; 333 | blockWidth: number; 334 | blockHeight: number; 335 | isCompressed: boolean; 336 | channels: number; 337 | }; 338 | "bc2-rgba-unorm": { 339 | bytesPerBlock: number; 340 | blockWidth: number; 341 | blockHeight: number; 342 | isCompressed: boolean; 343 | channels: number; 344 | }; 345 | "bc2-rgba-unorm-srgb": { 346 | bytesPerBlock: number; 347 | blockWidth: number; 348 | blockHeight: number; 349 | isCompressed: boolean; 350 | channels: number; 351 | }; 352 | "bc3-rgba-unorm": { 353 | bytesPerBlock: number; 354 | blockWidth: number; 355 | blockHeight: number; 356 | isCompressed: boolean; 357 | channels: number; 358 | }; 359 | "bc3-rgba-unorm-srgb": { 360 | bytesPerBlock: number; 361 | blockWidth: number; 362 | blockHeight: number; 363 | isCompressed: boolean; 364 | channels: number; 365 | }; 366 | "bc4-r-unorm": { 367 | bytesPerBlock: number; 368 | blockWidth: number; 369 | blockHeight: number; 370 | isCompressed: boolean; 371 | channels: number; 372 | }; 373 | "bc4-r-snorm": { 374 | bytesPerBlock: number; 375 | blockWidth: number; 376 | blockHeight: number; 377 | isCompressed: boolean; 378 | channels: number; 379 | }; 380 | "bc5-rg-unorm": { 381 | bytesPerBlock: number; 382 | blockWidth: number; 383 | blockHeight: number; 384 | isCompressed: boolean; 385 | channels: number; 386 | }; 387 | "bc5-rg-snorm": { 388 | bytesPerBlock: number; 389 | blockWidth: number; 390 | blockHeight: number; 391 | isCompressed: boolean; 392 | channels: number; 393 | }; 394 | "bc6h-rgb-ufloat": { 395 | bytesPerBlock: number; 396 | blockWidth: number; 397 | blockHeight: number; 398 | isCompressed: boolean; 399 | channels: number; 400 | }; 401 | "bc6h-rgb-float": { 402 | bytesPerBlock: number; 403 | blockWidth: number; 404 | blockHeight: number; 405 | isCompressed: boolean; 406 | channels: number; 407 | }; 408 | "bc7-rgba-unorm": { 409 | bytesPerBlock: number; 410 | blockWidth: number; 411 | blockHeight: number; 412 | isCompressed: boolean; 413 | channels: number; 414 | }; 415 | "bc7-rgba-unorm-srgb": { 416 | bytesPerBlock: number; 417 | blockWidth: number; 418 | blockHeight: number; 419 | isCompressed: boolean; 420 | channels: number; 421 | }; 422 | "etc2-rgb8unorm": { 423 | bytesPerBlock: number; 424 | blockWidth: number; 425 | blockHeight: number; 426 | isCompressed: boolean; 427 | channels: number; 428 | }; 429 | "etc2-rgb8unorm-srgb": { 430 | bytesPerBlock: number; 431 | blockWidth: number; 432 | blockHeight: number; 433 | isCompressed: boolean; 434 | channels: number; 435 | }; 436 | "etc2-rgb8a1unorm": { 437 | bytesPerBlock: number; 438 | blockWidth: number; 439 | blockHeight: number; 440 | isCompressed: boolean; 441 | channels: number; 442 | }; 443 | "etc2-rgb8a1unorm-srgb": { 444 | bytesPerBlock: number; 445 | blockWidth: number; 446 | blockHeight: number; 447 | isCompressed: boolean; 448 | channels: number; 449 | }; 450 | "etc2-rgba8unorm": { 451 | bytesPerBlock: number; 452 | blockWidth: number; 453 | blockHeight: number; 454 | isCompressed: boolean; 455 | channels: number; 456 | }; 457 | "etc2-rgba8unorm-srgb": { 458 | bytesPerBlock: number; 459 | blockWidth: number; 460 | blockHeight: number; 461 | isCompressed: boolean; 462 | channels: number; 463 | }; 464 | "eac-r11unorm": { 465 | bytesPerBlock: number; 466 | blockWidth: number; 467 | blockHeight: number; 468 | isCompressed: boolean; 469 | channels: number; 470 | }; 471 | "eac-r11snorm": { 472 | bytesPerBlock: number; 473 | blockWidth: number; 474 | blockHeight: number; 475 | isCompressed: boolean; 476 | channels: number; 477 | }; 478 | "eac-rg11unorm": { 479 | bytesPerBlock: number; 480 | blockWidth: number; 481 | blockHeight: number; 482 | isCompressed: boolean; 483 | channels: number; 484 | }; 485 | "eac-rg11snorm": { 486 | bytesPerBlock: number; 487 | blockWidth: number; 488 | blockHeight: number; 489 | isCompressed: boolean; 490 | channels: number; 491 | }; 492 | "astc-4x4-unorm": { 493 | bytesPerBlock: number; 494 | blockWidth: number; 495 | blockHeight: number; 496 | isCompressed: boolean; 497 | channels: number; 498 | }; 499 | "astc-4x4-unorm-srgb": { 500 | bytesPerBlock: number; 501 | blockWidth: number; 502 | blockHeight: number; 503 | isCompressed: boolean; 504 | channels: number; 505 | }; 506 | "astc-5x4-unorm": { 507 | bytesPerBlock: number; 508 | blockWidth: number; 509 | blockHeight: number; 510 | isCompressed: boolean; 511 | channels: number; 512 | }; 513 | "astc-5x4-unorm-srgb": { 514 | bytesPerBlock: number; 515 | blockWidth: number; 516 | blockHeight: number; 517 | isCompressed: boolean; 518 | channels: number; 519 | }; 520 | "astc-5x5-unorm": { 521 | bytesPerBlock: number; 522 | blockWidth: number; 523 | blockHeight: number; 524 | isCompressed: boolean; 525 | channels: number; 526 | }; 527 | "astc-5x5-unorm-srgb": { 528 | bytesPerBlock: number; 529 | blockWidth: number; 530 | blockHeight: number; 531 | isCompressed: boolean; 532 | channels: number; 533 | }; 534 | "astc-6x5-unorm": { 535 | bytesPerBlock: number; 536 | blockWidth: number; 537 | blockHeight: number; 538 | isCompressed: boolean; 539 | channels: number; 540 | }; 541 | "astc-6x5-unorm-srgb": { 542 | bytesPerBlock: number; 543 | blockWidth: number; 544 | blockHeight: number; 545 | isCompressed: boolean; 546 | channels: number; 547 | }; 548 | "astc-6x6-unorm": { 549 | bytesPerBlock: number; 550 | blockWidth: number; 551 | blockHeight: number; 552 | isCompressed: boolean; 553 | channels: number; 554 | }; 555 | "astc-6x6-unorm-srgb": { 556 | bytesPerBlock: number; 557 | blockWidth: number; 558 | blockHeight: number; 559 | isCompressed: boolean; 560 | channels: number; 561 | }; 562 | "astc-8x5-unorm": { 563 | bytesPerBlock: number; 564 | blockWidth: number; 565 | blockHeight: number; 566 | isCompressed: boolean; 567 | channels: number; 568 | }; 569 | "astc-8x5-unorm-srgb": { 570 | bytesPerBlock: number; 571 | blockWidth: number; 572 | blockHeight: number; 573 | isCompressed: boolean; 574 | channels: number; 575 | }; 576 | "astc-8x6-unorm": { 577 | bytesPerBlock: number; 578 | blockWidth: number; 579 | blockHeight: number; 580 | isCompressed: boolean; 581 | channels: number; 582 | }; 583 | "astc-8x6-unorm-srgb": { 584 | bytesPerBlock: number; 585 | blockWidth: number; 586 | blockHeight: number; 587 | isCompressed: boolean; 588 | channels: number; 589 | }; 590 | "astc-8x8-unorm": { 591 | bytesPerBlock: number; 592 | blockWidth: number; 593 | blockHeight: number; 594 | isCompressed: boolean; 595 | channels: number; 596 | }; 597 | "astc-8x8-unorm-srgb": { 598 | bytesPerBlock: number; 599 | blockWidth: number; 600 | blockHeight: number; 601 | isCompressed: boolean; 602 | channels: number; 603 | }; 604 | "astc-10x5-unorm": { 605 | bytesPerBlock: number; 606 | blockWidth: number; 607 | blockHeight: number; 608 | isCompressed: boolean; 609 | channels: number; 610 | }; 611 | "astc-10x5-unorm-srgb": { 612 | bytesPerBlock: number; 613 | blockWidth: number; 614 | blockHeight: number; 615 | isCompressed: boolean; 616 | channels: number; 617 | }; 618 | "astc-10x6-unorm": { 619 | bytesPerBlock: number; 620 | blockWidth: number; 621 | blockHeight: number; 622 | isCompressed: boolean; 623 | channels: number; 624 | }; 625 | "astc-10x6-unorm-srgb": { 626 | bytesPerBlock: number; 627 | blockWidth: number; 628 | blockHeight: number; 629 | isCompressed: boolean; 630 | channels: number; 631 | }; 632 | "astc-10x8-unorm": { 633 | bytesPerBlock: number; 634 | blockWidth: number; 635 | blockHeight: number; 636 | isCompressed: boolean; 637 | channels: number; 638 | }; 639 | "astc-10x8-unorm-srgb": { 640 | bytesPerBlock: number; 641 | blockWidth: number; 642 | blockHeight: number; 643 | isCompressed: boolean; 644 | channels: number; 645 | }; 646 | "astc-10x10-unorm": { 647 | bytesPerBlock: number; 648 | blockWidth: number; 649 | blockHeight: number; 650 | isCompressed: boolean; 651 | channels: number; 652 | }; 653 | "astc-10x10-unorm-srgb": { 654 | bytesPerBlock: number; 655 | blockWidth: number; 656 | blockHeight: number; 657 | isCompressed: boolean; 658 | channels: number; 659 | }; 660 | "astc-12x10-unorm": { 661 | bytesPerBlock: number; 662 | blockWidth: number; 663 | blockHeight: number; 664 | isCompressed: boolean; 665 | channels: number; 666 | }; 667 | "astc-12x10-unorm-srgb": { 668 | bytesPerBlock: number; 669 | blockWidth: number; 670 | blockHeight: number; 671 | isCompressed: boolean; 672 | channels: number; 673 | }; 674 | "astc-12x12-unorm": { 675 | bytesPerBlock: number; 676 | blockWidth: number; 677 | blockHeight: number; 678 | isCompressed: boolean; 679 | channels: number; 680 | }; 681 | "astc-12x12-unorm-srgb": { 682 | bytesPerBlock: number; 683 | blockWidth: number; 684 | blockHeight: number; 685 | isCompressed: boolean; 686 | channels: number; 687 | }; 688 | }; 689 | --------------------------------------------------------------------------------