@aethervtt/shard-gpu-webgl2 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/binding.d.ts +32 -0
- package/dist/binding.js +81 -0
- package/dist/cache.d.ts +73 -0
- package/dist/cache.js +184 -0
- package/dist/commands.d.ts +94 -0
- package/dist/commands.js +366 -0
- package/dist/constants.d.ts +41 -0
- package/dist/constants.js +38 -0
- package/dist/copies.d.ts +61 -0
- package/dist/copies.js +621 -0
- package/dist/device.d.ts +160 -0
- package/dist/device.js +384 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +18 -0
- package/dist/fake-gl.d.ts +316 -0
- package/dist/fake-gl.js +949 -0
- package/dist/fbo.d.ts +30 -0
- package/dist/fbo.js +142 -0
- package/dist/formats.d.ts +25 -0
- package/dist/formats.js +106 -0
- package/dist/gl.d.ts +216 -0
- package/dist/gl.js +232 -0
- package/dist/gpu.d.ts +58 -0
- package/dist/gpu.js +213 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +42 -0
- package/dist/naga.d.ts +37 -0
- package/dist/naga.js +99 -0
- package/dist/pipeline.d.ts +113 -0
- package/dist/pipeline.js +294 -0
- package/dist/present.d.ts +18 -0
- package/dist/present.js +108 -0
- package/dist/queue.d.ts +13 -0
- package/dist/queue.js +50 -0
- package/dist/readback.d.ts +17 -0
- package/dist/readback.js +104 -0
- package/dist/replay.d.ts +76 -0
- package/dist/replay.js +657 -0
- package/dist/resources.d.ts +93 -0
- package/dist/resources.js +353 -0
- package/dist/state.d.ts +43 -0
- package/dist/state.js +169 -0
- package/package.json +36 -4
- package/wasm/shard_naga.wasm +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Webgl2PipelineLayout } from './binding.js';
|
|
2
|
+
import type { Webgl2Device } from './device.js';
|
|
3
|
+
import type { GlslTranslation } from './naga.js';
|
|
4
|
+
export declare class Webgl2ShaderModule {
|
|
5
|
+
readonly id: number;
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly label: string;
|
|
8
|
+
constructor(d: GPUShaderModuleDescriptor);
|
|
9
|
+
/** Answered without translating: pipelines report what doesn't translate or link. */
|
|
10
|
+
getCompilationInfo(): Promise<{
|
|
11
|
+
messages: readonly unknown[];
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/** One stage's WGSL and entry point. */
|
|
15
|
+
export interface StageSource {
|
|
16
|
+
code: string;
|
|
17
|
+
entry: string;
|
|
18
|
+
}
|
|
19
|
+
/** A uniform block of the program, and the group binding that feeds it. */
|
|
20
|
+
export interface ProgramBlock {
|
|
21
|
+
group: number;
|
|
22
|
+
binding: number;
|
|
23
|
+
point: number;
|
|
24
|
+
/**
|
|
25
|
+
* The block's size in GLSL (std140 rounds a block up to 16 bytes; WGSL doesn't round a uniform
|
|
26
|
+
* struct): a draw fails unless at least this much is bound.
|
|
27
|
+
*/
|
|
28
|
+
size: number;
|
|
29
|
+
}
|
|
30
|
+
/** A combined sampler of the program: its texture unit, texture binding and sampler binding. */
|
|
31
|
+
export interface ProgramSampler {
|
|
32
|
+
unit: number;
|
|
33
|
+
group: number;
|
|
34
|
+
binding: number;
|
|
35
|
+
/** The sampler's group and binding, or -1 for `texelFetch` reads (no sampler). */
|
|
36
|
+
samplerGroup: number;
|
|
37
|
+
samplerBinding: number;
|
|
38
|
+
}
|
|
39
|
+
export interface VertexAttribute {
|
|
40
|
+
location: number;
|
|
41
|
+
size: number;
|
|
42
|
+
type: number;
|
|
43
|
+
normalized: boolean;
|
|
44
|
+
integer: boolean;
|
|
45
|
+
offset: number;
|
|
46
|
+
}
|
|
47
|
+
export interface VertexBufferLayout {
|
|
48
|
+
stride: number;
|
|
49
|
+
instanced: boolean;
|
|
50
|
+
attributes: VertexAttribute[];
|
|
51
|
+
}
|
|
52
|
+
interface BlendState {
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
colorOp: number;
|
|
55
|
+
colorSrc: number;
|
|
56
|
+
colorDst: number;
|
|
57
|
+
alphaOp: number;
|
|
58
|
+
alphaSrc: number;
|
|
59
|
+
alphaDst: number;
|
|
60
|
+
}
|
|
61
|
+
export declare class Webgl2RenderPipeline {
|
|
62
|
+
readonly id: number;
|
|
63
|
+
readonly label: string;
|
|
64
|
+
readonly layout: Webgl2PipelineLayout;
|
|
65
|
+
program: WebGLProgram | null;
|
|
66
|
+
readonly blocks: ProgramBlock[];
|
|
67
|
+
readonly samplers: ProgramSampler[];
|
|
68
|
+
firstInstance: WebGLUniformLocation | null;
|
|
69
|
+
/** The first instance the program's uniform holds (-1: not set yet). */
|
|
70
|
+
firstInstanceValue: number;
|
|
71
|
+
/** Reads `@builtin(vertex_index)`: a nonzero baseVertex would shift it. */
|
|
72
|
+
readonly vertexIndex: boolean;
|
|
73
|
+
readonly vertexBuffers: VertexBufferLayout[];
|
|
74
|
+
readonly topology: number;
|
|
75
|
+
readonly cull: number;
|
|
76
|
+
readonly frontFace: number;
|
|
77
|
+
readonly depthTest: boolean;
|
|
78
|
+
readonly depthWrite: boolean;
|
|
79
|
+
readonly depthFunc: number;
|
|
80
|
+
readonly depthBias: number;
|
|
81
|
+
readonly depthSlope: number;
|
|
82
|
+
readonly stencil: boolean;
|
|
83
|
+
readonly stencilFront: [number, number, number, number];
|
|
84
|
+
readonly stencilBack: [number, number, number, number];
|
|
85
|
+
readonly stencilReadMask: number;
|
|
86
|
+
readonly stencilWriteMask: number;
|
|
87
|
+
readonly blends: BlendState[];
|
|
88
|
+
readonly masks: number[];
|
|
89
|
+
/** Every target blends and masks alike: one set of GL calls covers them all. */
|
|
90
|
+
readonly uniformTargets: boolean;
|
|
91
|
+
readonly targets: number;
|
|
92
|
+
readonly sampleCount: number;
|
|
93
|
+
private readonly device;
|
|
94
|
+
private readonly descriptor;
|
|
95
|
+
constructor(device: Webgl2Device, d: GPURenderPipelineDescriptor);
|
|
96
|
+
/** The WGSL and entry point of each stage, to translate. */
|
|
97
|
+
stages(): {
|
|
98
|
+
vertex: StageSource;
|
|
99
|
+
fragment: StageSource | undefined;
|
|
100
|
+
};
|
|
101
|
+
/** Compiles both stages' GLSL and starts linking the program. */
|
|
102
|
+
build(v: GlslTranslation, f: GlslTranslation | undefined): WebGLProgram;
|
|
103
|
+
/** The shaders and translations of a program still linking. */
|
|
104
|
+
private link;
|
|
105
|
+
/** Whether the driver has finished linking (KHR_parallel_shader_compile), or can't say. */
|
|
106
|
+
linked(): boolean;
|
|
107
|
+
/** After linking: the status, and the binding points and units from the reflection. */
|
|
108
|
+
finish(): void;
|
|
109
|
+
getBindGroupLayout(index: number): unknown;
|
|
110
|
+
}
|
|
111
|
+
/** The entry point a stage runs when the descriptor names none: the first of that stage. */
|
|
112
|
+
export declare function entryPointOf(code: string, stage: 'vertex' | 'fragment'): string;
|
|
113
|
+
export {};
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { unsupported } from "./errors.js";
|
|
2
|
+
import { GL } from "./gl.js";
|
|
3
|
+
import { COMPARE } from "./resources.js";
|
|
4
|
+
let nextId = 1;
|
|
5
|
+
class Webgl2ShaderModule {
|
|
6
|
+
id = nextId++;
|
|
7
|
+
code;
|
|
8
|
+
label;
|
|
9
|
+
constructor(d) {
|
|
10
|
+
this.code = d.code;
|
|
11
|
+
this.label = d.label ?? "";
|
|
12
|
+
}
|
|
13
|
+
/** Answered without translating: pipelines report what doesn't translate or link. */
|
|
14
|
+
getCompilationInfo() {
|
|
15
|
+
return Promise.resolve({ messages: [] });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const FACTOR = {
|
|
19
|
+
zero: GL.ZERO,
|
|
20
|
+
one: GL.ONE,
|
|
21
|
+
src: GL.SRC_COLOR,
|
|
22
|
+
"one-minus-src": GL.ONE_MINUS_SRC_COLOR,
|
|
23
|
+
"src-alpha": GL.SRC_ALPHA,
|
|
24
|
+
"one-minus-src-alpha": GL.ONE_MINUS_SRC_ALPHA,
|
|
25
|
+
dst: GL.DST_COLOR,
|
|
26
|
+
"one-minus-dst": GL.ONE_MINUS_DST_COLOR,
|
|
27
|
+
"dst-alpha": GL.DST_ALPHA,
|
|
28
|
+
"one-minus-dst-alpha": GL.ONE_MINUS_DST_ALPHA,
|
|
29
|
+
"src-alpha-saturated": GL.SRC_ALPHA_SATURATE,
|
|
30
|
+
constant: GL.CONSTANT_COLOR,
|
|
31
|
+
"one-minus-constant": GL.ONE_MINUS_CONSTANT_COLOR
|
|
32
|
+
};
|
|
33
|
+
const OPERATION = {
|
|
34
|
+
add: GL.FUNC_ADD,
|
|
35
|
+
subtract: GL.FUNC_SUBTRACT,
|
|
36
|
+
"reverse-subtract": GL.FUNC_REVERSE_SUBTRACT,
|
|
37
|
+
min: GL.MIN,
|
|
38
|
+
max: GL.MAX
|
|
39
|
+
};
|
|
40
|
+
const STENCIL_OP = {
|
|
41
|
+
keep: GL.KEEP,
|
|
42
|
+
zero: GL.ZERO,
|
|
43
|
+
replace: GL.REPLACE,
|
|
44
|
+
invert: GL.INVERT,
|
|
45
|
+
"increment-clamp": GL.INCR,
|
|
46
|
+
"decrement-clamp": GL.DECR,
|
|
47
|
+
"increment-wrap": GL.INCR_WRAP,
|
|
48
|
+
"decrement-wrap": GL.DECR_WRAP
|
|
49
|
+
};
|
|
50
|
+
const VERTEX_FORMAT = {
|
|
51
|
+
float32: [1, GL.FLOAT, false, false],
|
|
52
|
+
float32x2: [2, GL.FLOAT, false, false],
|
|
53
|
+
float32x3: [3, GL.FLOAT, false, false],
|
|
54
|
+
float32x4: [4, GL.FLOAT, false, false],
|
|
55
|
+
float16x2: [2, GL.HALF_FLOAT, false, false],
|
|
56
|
+
float16x4: [4, GL.HALF_FLOAT, false, false],
|
|
57
|
+
uint32: [1, GL.UNSIGNED_INT, false, true],
|
|
58
|
+
uint32x2: [2, GL.UNSIGNED_INT, false, true],
|
|
59
|
+
uint32x3: [3, GL.UNSIGNED_INT, false, true],
|
|
60
|
+
uint32x4: [4, GL.UNSIGNED_INT, false, true],
|
|
61
|
+
sint32: [1, GL.INT, false, true],
|
|
62
|
+
sint32x2: [2, GL.INT, false, true],
|
|
63
|
+
sint32x3: [3, GL.INT, false, true],
|
|
64
|
+
sint32x4: [4, GL.INT, false, true],
|
|
65
|
+
uint16x2: [2, GL.UNSIGNED_SHORT, false, true],
|
|
66
|
+
uint16x4: [4, GL.UNSIGNED_SHORT, false, true],
|
|
67
|
+
unorm16x2: [2, GL.UNSIGNED_SHORT, true, false],
|
|
68
|
+
unorm16x4: [4, GL.UNSIGNED_SHORT, true, false],
|
|
69
|
+
uint8x2: [2, GL.UNSIGNED_BYTE, false, true],
|
|
70
|
+
uint8x4: [4, GL.UNSIGNED_BYTE, false, true],
|
|
71
|
+
unorm8x2: [2, GL.UNSIGNED_BYTE, true, false],
|
|
72
|
+
unorm8x4: [4, GL.UNSIGNED_BYTE, true, false],
|
|
73
|
+
snorm8x4: [4, GL.BYTE, true, false]
|
|
74
|
+
};
|
|
75
|
+
const EMPTY_FRAGMENT = "#version 300 es\nprecision highp float;\nvoid main() {}\n";
|
|
76
|
+
class Webgl2RenderPipeline {
|
|
77
|
+
id = nextId++;
|
|
78
|
+
label;
|
|
79
|
+
layout;
|
|
80
|
+
program = null;
|
|
81
|
+
blocks = [];
|
|
82
|
+
samplers = [];
|
|
83
|
+
firstInstance = null;
|
|
84
|
+
/** The first instance the program's uniform holds (-1: not set yet). */
|
|
85
|
+
firstInstanceValue = -1;
|
|
86
|
+
/** Reads `@builtin(vertex_index)`: a nonzero baseVertex would shift it. */
|
|
87
|
+
vertexIndex;
|
|
88
|
+
vertexBuffers;
|
|
89
|
+
topology;
|
|
90
|
+
cull;
|
|
91
|
+
frontFace;
|
|
92
|
+
depthTest;
|
|
93
|
+
depthWrite;
|
|
94
|
+
depthFunc;
|
|
95
|
+
depthBias;
|
|
96
|
+
depthSlope;
|
|
97
|
+
stencil;
|
|
98
|
+
stencilFront;
|
|
99
|
+
stencilBack;
|
|
100
|
+
stencilReadMask;
|
|
101
|
+
stencilWriteMask;
|
|
102
|
+
blends;
|
|
103
|
+
masks;
|
|
104
|
+
/** Every target blends and masks alike: one set of GL calls covers them all. */
|
|
105
|
+
uniformTargets;
|
|
106
|
+
targets;
|
|
107
|
+
sampleCount;
|
|
108
|
+
device;
|
|
109
|
+
descriptor;
|
|
110
|
+
constructor(device, d) {
|
|
111
|
+
this.device = device;
|
|
112
|
+
this.descriptor = d;
|
|
113
|
+
this.label = d.label ?? "";
|
|
114
|
+
if (d.layout === "auto") throw unsupported(`make "${this.label}" with layout 'auto'`);
|
|
115
|
+
this.layout = d.layout;
|
|
116
|
+
const vs = d.vertex.module;
|
|
117
|
+
this.vertexIndex = /@builtin\(\s*vertex_index\s*\)/.test(vs.code);
|
|
118
|
+
this.vertexBuffers = (d.vertex.buffers ?? []).map((b) => ({
|
|
119
|
+
stride: b?.arrayStride ?? 0,
|
|
120
|
+
instanced: b?.stepMode === "instance",
|
|
121
|
+
attributes: (b ? [...b.attributes] : []).map((a) => {
|
|
122
|
+
const f = VERTEX_FORMAT[a.format];
|
|
123
|
+
if (!f) throw unsupported(`read the vertex format ${a.format}`);
|
|
124
|
+
return {
|
|
125
|
+
location: a.shaderLocation,
|
|
126
|
+
size: f[0],
|
|
127
|
+
type: f[1],
|
|
128
|
+
normalized: f[2],
|
|
129
|
+
integer: f[3],
|
|
130
|
+
offset: a.offset
|
|
131
|
+
};
|
|
132
|
+
})
|
|
133
|
+
}));
|
|
134
|
+
const p = d.primitive ?? {};
|
|
135
|
+
const topology = p.topology ?? "triangle-list";
|
|
136
|
+
this.topology = topology === "triangle-list" ? GL.TRIANGLES : topology === "triangle-strip" ? GL.TRIANGLE_STRIP : topology === "line-list" ? GL.LINES : topology === "line-strip" ? GL.LINE_STRIP : GL.POINTS;
|
|
137
|
+
this.cull = p.cullMode === "front" ? GL.FRONT : p.cullMode === "back" ? GL.BACK : 0;
|
|
138
|
+
this.frontFace = (p.frontFace ?? "ccw") === "ccw" ? GL.CW : GL.CCW;
|
|
139
|
+
const ds = d.depthStencil;
|
|
140
|
+
this.depthTest = ds !== void 0 && ((ds.depthCompare ?? "always") !== "always" || ds.depthWriteEnabled === true);
|
|
141
|
+
this.depthWrite = ds?.depthWriteEnabled === true;
|
|
142
|
+
this.depthFunc = COMPARE[ds?.depthCompare ?? "always"];
|
|
143
|
+
this.depthBias = ds?.depthBias ?? 0;
|
|
144
|
+
this.depthSlope = ds?.depthBiasSlopeScale ?? 0;
|
|
145
|
+
const face = (s) => [
|
|
146
|
+
COMPARE[s?.compare ?? "always"],
|
|
147
|
+
STENCIL_OP[s?.failOp ?? "keep"],
|
|
148
|
+
STENCIL_OP[s?.depthFailOp ?? "keep"],
|
|
149
|
+
STENCIL_OP[s?.passOp ?? "keep"]
|
|
150
|
+
];
|
|
151
|
+
this.stencilFront = face(ds?.stencilFront);
|
|
152
|
+
this.stencilBack = face(ds?.stencilBack);
|
|
153
|
+
this.stencilReadMask = ds?.stencilReadMask ?? 255;
|
|
154
|
+
this.stencilWriteMask = ds?.stencilWriteMask ?? 255;
|
|
155
|
+
this.stencil = ds !== void 0 && (ds.format === "stencil8" || ds.format.includes("stencil")) && (ds.stencilFront !== void 0 || ds.stencilBack !== void 0);
|
|
156
|
+
if (d.multisample?.alphaToCoverageEnabled)
|
|
157
|
+
throw unsupported(`use alpha to coverage ("${this.label}")`);
|
|
158
|
+
this.sampleCount = d.multisample?.count ?? 1;
|
|
159
|
+
const targets = d.fragment ? [...d.fragment.targets] : [];
|
|
160
|
+
this.targets = targets.length;
|
|
161
|
+
this.blends = targets.map((t) => {
|
|
162
|
+
const b = t?.blend;
|
|
163
|
+
return {
|
|
164
|
+
enabled: b !== void 0,
|
|
165
|
+
colorOp: OPERATION[b?.color.operation ?? "add"],
|
|
166
|
+
colorSrc: FACTOR[b?.color.srcFactor ?? "one"],
|
|
167
|
+
colorDst: FACTOR[b?.color.dstFactor ?? "zero"],
|
|
168
|
+
alphaOp: OPERATION[b?.alpha.operation ?? "add"],
|
|
169
|
+
alphaSrc: FACTOR[b?.alpha.srcFactor ?? "one"],
|
|
170
|
+
alphaDst: FACTOR[b?.alpha.dstFactor ?? "zero"]
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
this.masks = targets.map((t) => t?.writeMask ?? 15);
|
|
174
|
+
const same = (a, b) => a.enabled === b.enabled && a.colorOp === b.colorOp && a.colorSrc === b.colorSrc && a.colorDst === b.colorDst && a.alphaOp === b.alphaOp && a.alphaSrc === b.alphaSrc && a.alphaDst === b.alphaDst;
|
|
175
|
+
this.uniformTargets = this.blends.every(
|
|
176
|
+
(b, i) => same(b, this.blends[0]) && this.masks[i] === this.masks[0]
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
/** The WGSL and entry point of each stage, to translate. */
|
|
180
|
+
stages() {
|
|
181
|
+
const d = this.descriptor;
|
|
182
|
+
const vs = d.vertex.module;
|
|
183
|
+
const fs = d.fragment?.module;
|
|
184
|
+
return {
|
|
185
|
+
vertex: { code: vs.code, entry: d.vertex.entryPoint ?? entryPointOf(vs.code, "vertex") },
|
|
186
|
+
fragment: fs && d.fragment ? { code: fs.code, entry: d.fragment.entryPoint ?? entryPointOf(fs.code, "fragment") } : void 0
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** Compiles both stages' GLSL and starts linking the program. */
|
|
190
|
+
build(v, f) {
|
|
191
|
+
const gl = this.device.gl;
|
|
192
|
+
const program = gl.createProgram();
|
|
193
|
+
const shader = (type, source) => {
|
|
194
|
+
const s = gl.createShader(type);
|
|
195
|
+
gl.shaderSource(s, source);
|
|
196
|
+
gl.compileShader(s);
|
|
197
|
+
gl.attachShader(program, s);
|
|
198
|
+
return s;
|
|
199
|
+
};
|
|
200
|
+
const vsShader = shader(GL.VERTEX_SHADER, v.glsl);
|
|
201
|
+
const fsShader = shader(GL.FRAGMENT_SHADER, f?.glsl ?? EMPTY_FRAGMENT);
|
|
202
|
+
gl.linkProgram(program);
|
|
203
|
+
this.program = program;
|
|
204
|
+
this.link = { vs: vsShader, fs: fsShader, v, f };
|
|
205
|
+
return program;
|
|
206
|
+
}
|
|
207
|
+
/** The shaders and translations of a program still linking. */
|
|
208
|
+
link;
|
|
209
|
+
/** Whether the driver has finished linking (KHR_parallel_shader_compile), or can't say. */
|
|
210
|
+
linked() {
|
|
211
|
+
if (!this.device.caps.parallelCompile || !this.program) return true;
|
|
212
|
+
return this.device.gl.getProgramParameter(this.program, GL.COMPLETION_STATUS_KHR);
|
|
213
|
+
}
|
|
214
|
+
/** After linking: the status, and the binding points and units from the reflection. */
|
|
215
|
+
finish() {
|
|
216
|
+
const gl = this.device.gl;
|
|
217
|
+
const program = this.program;
|
|
218
|
+
const link = this.link;
|
|
219
|
+
this.link = void 0;
|
|
220
|
+
if (!gl.getProgramParameter(program, GL.LINK_STATUS)) {
|
|
221
|
+
const log = [
|
|
222
|
+
gl.getShaderInfoLog(link.vs),
|
|
223
|
+
gl.getShaderInfoLog(link.fs),
|
|
224
|
+
gl.getProgramInfoLog(program)
|
|
225
|
+
].filter(Boolean).join("\n");
|
|
226
|
+
gl.deleteProgram(program);
|
|
227
|
+
this.program = null;
|
|
228
|
+
throw new Error(`GLSL for "${this.label}" didn't link: ${log}`);
|
|
229
|
+
}
|
|
230
|
+
gl.deleteShader(link.vs);
|
|
231
|
+
gl.deleteShader(link.fs);
|
|
232
|
+
const points = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const u of [...link.v.uniforms, ...link.f?.uniforms ?? []]) {
|
|
234
|
+
const index = gl.getUniformBlockIndex(program, u.name);
|
|
235
|
+
if (index === 4294967295) continue;
|
|
236
|
+
const key = `${u.group}:${u.binding}`;
|
|
237
|
+
const size = gl.getActiveUniformBlockParameter(
|
|
238
|
+
program,
|
|
239
|
+
index,
|
|
240
|
+
GL.UNIFORM_BLOCK_DATA_SIZE
|
|
241
|
+
);
|
|
242
|
+
let point = points.get(key);
|
|
243
|
+
if (point === void 0) {
|
|
244
|
+
point = points.size;
|
|
245
|
+
points.set(key, point);
|
|
246
|
+
this.blocks.push({ group: u.group, binding: u.binding, point, size });
|
|
247
|
+
} else {
|
|
248
|
+
const block = this.blocks[point];
|
|
249
|
+
block.size = Math.max(block.size, size);
|
|
250
|
+
}
|
|
251
|
+
gl.uniformBlockBinding(program, index, point);
|
|
252
|
+
}
|
|
253
|
+
gl.useProgram(program);
|
|
254
|
+
const units = /* @__PURE__ */ new Map();
|
|
255
|
+
for (const t of [...link.v.textures, ...link.f?.textures ?? []]) {
|
|
256
|
+
const location = gl.getUniformLocation(program, t.name);
|
|
257
|
+
if (!location) continue;
|
|
258
|
+
const key = `${t.group}:${t.binding}:${t.sampler ? t.sampler.join(":") : "-"}`;
|
|
259
|
+
let unit = units.get(key);
|
|
260
|
+
if (unit === void 0) {
|
|
261
|
+
unit = units.size;
|
|
262
|
+
units.set(key, unit);
|
|
263
|
+
this.samplers.push({
|
|
264
|
+
unit,
|
|
265
|
+
group: t.group,
|
|
266
|
+
binding: t.binding,
|
|
267
|
+
samplerGroup: t.sampler ? t.sampler[0] : -1,
|
|
268
|
+
samplerBinding: t.sampler ? t.sampler[1] : -1
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
gl.uniform1i(location, unit);
|
|
272
|
+
}
|
|
273
|
+
if (units.size > this.device.caps.combinedUnits - 1) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`"${this.label}" samples ${units.size} textures; this device binds ${this.device.caps.combinedUnits - 1}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
this.firstInstance = link.v.firstInstance ? gl.getUniformLocation(program, "naga_vs_first_instance") : null;
|
|
279
|
+
this.device.state.forgetProgram();
|
|
280
|
+
}
|
|
281
|
+
getBindGroupLayout(index) {
|
|
282
|
+
return this.layout.groups[index];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function entryPointOf(code, stage) {
|
|
286
|
+
const m = new RegExp(`@${stage}\\s+fn\\s+([A-Za-z_]\\w*)`).exec(code);
|
|
287
|
+
if (!m) throw new Error(`No @${stage} entry point`);
|
|
288
|
+
return m[1];
|
|
289
|
+
}
|
|
290
|
+
export {
|
|
291
|
+
Webgl2RenderPipeline,
|
|
292
|
+
Webgl2ShaderModule,
|
|
293
|
+
entryPointOf
|
|
294
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Webgl2Texture } from './resources.js';
|
|
2
|
+
/** The canvas context for `canvas`: one per canvas, as `getContext` gives. */
|
|
3
|
+
export declare function canvasContext(canvas: HTMLCanvasElement | OffscreenCanvas): Webgl2CanvasContext;
|
|
4
|
+
export declare class Webgl2CanvasContext {
|
|
5
|
+
readonly canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
6
|
+
private device;
|
|
7
|
+
private configuration;
|
|
8
|
+
private texture;
|
|
9
|
+
private target;
|
|
10
|
+
private readonly read;
|
|
11
|
+
constructor(canvas: HTMLCanvasElement | OffscreenCanvas);
|
|
12
|
+
configure(configuration: GPUCanvasConfiguration): void;
|
|
13
|
+
getConfiguration(): GPUCanvasConfiguration | null;
|
|
14
|
+
unconfigure(): void;
|
|
15
|
+
getCurrentTexture(): Webgl2Texture;
|
|
16
|
+
/** @internal Shows `texture` on the canvas. */
|
|
17
|
+
present(texture: Webgl2Texture): void;
|
|
18
|
+
}
|
package/dist/present.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { TextureUsage } from "./constants.js";
|
|
2
|
+
import { unsupported } from "./errors.js";
|
|
3
|
+
import { Attachments } from "./fbo.js";
|
|
4
|
+
import { GL } from "./gl.js";
|
|
5
|
+
import { Webgl2Texture } from "./resources.js";
|
|
6
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function canvasContext(canvas) {
|
|
8
|
+
let context = contexts.get(canvas);
|
|
9
|
+
if (!context) {
|
|
10
|
+
context = new Webgl2CanvasContext(canvas);
|
|
11
|
+
contexts.set(canvas, context);
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
15
|
+
class Webgl2CanvasContext {
|
|
16
|
+
canvas;
|
|
17
|
+
device;
|
|
18
|
+
configuration;
|
|
19
|
+
texture;
|
|
20
|
+
target;
|
|
21
|
+
read = new Attachments();
|
|
22
|
+
constructor(canvas) {
|
|
23
|
+
this.canvas = canvas;
|
|
24
|
+
}
|
|
25
|
+
configure(configuration) {
|
|
26
|
+
if (configuration.format !== "rgba8unorm") {
|
|
27
|
+
throw unsupported(
|
|
28
|
+
`present ${configuration.format}`,
|
|
29
|
+
"Configure the canvas with getPreferredCanvasFormat() ('rgba8unorm' on WebGL2)."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
this.texture?.destroy();
|
|
33
|
+
this.texture = void 0;
|
|
34
|
+
this.device = configuration.device;
|
|
35
|
+
this.configuration = configuration;
|
|
36
|
+
}
|
|
37
|
+
getConfiguration() {
|
|
38
|
+
return this.configuration ?? null;
|
|
39
|
+
}
|
|
40
|
+
unconfigure() {
|
|
41
|
+
this.texture?.destroy();
|
|
42
|
+
this.texture = void 0;
|
|
43
|
+
this.device = void 0;
|
|
44
|
+
this.configuration = void 0;
|
|
45
|
+
}
|
|
46
|
+
getCurrentTexture() {
|
|
47
|
+
const device = this.device;
|
|
48
|
+
const configuration = this.configuration;
|
|
49
|
+
if (!device || !configuration)
|
|
50
|
+
throw new Error("getCurrentTexture: the canvas context is not configured");
|
|
51
|
+
const width = Math.max(1, this.canvas.width);
|
|
52
|
+
const height = Math.max(1, this.canvas.height);
|
|
53
|
+
let texture = this.texture;
|
|
54
|
+
if (!texture || texture.destroyed || texture.width !== width || texture.height !== height) {
|
|
55
|
+
texture?.destroy();
|
|
56
|
+
texture = new Webgl2Texture(device, {
|
|
57
|
+
label: "canvas",
|
|
58
|
+
size: [width, height],
|
|
59
|
+
format: "rgba8unorm",
|
|
60
|
+
usage: (configuration.usage ?? TextureUsage.RENDER_ATTACHMENT) | TextureUsage.RENDER_ATTACHMENT
|
|
61
|
+
});
|
|
62
|
+
texture.surface = this;
|
|
63
|
+
this.texture = texture;
|
|
64
|
+
}
|
|
65
|
+
return texture;
|
|
66
|
+
}
|
|
67
|
+
/** @internal Shows `texture` on the canvas. */
|
|
68
|
+
present(texture) {
|
|
69
|
+
const device = this.device;
|
|
70
|
+
if (!device || device.isLost || texture !== this.texture) return;
|
|
71
|
+
const gl = device.gl;
|
|
72
|
+
const glCanvas = device.canvas;
|
|
73
|
+
if (!glCanvas) return;
|
|
74
|
+
const w = texture.width;
|
|
75
|
+
const h = texture.height;
|
|
76
|
+
const own = glCanvas === this.canvas;
|
|
77
|
+
if (!own) {
|
|
78
|
+
if (glCanvas.width !== w) glCanvas.width = w;
|
|
79
|
+
if (glCanvas.height !== h) glCanvas.height = h;
|
|
80
|
+
}
|
|
81
|
+
this.read.reset();
|
|
82
|
+
this.read.color(0, texture, 0, 0);
|
|
83
|
+
gl.bindFramebuffer(GL.READ_FRAMEBUFFER, device.fbos.get(this.read));
|
|
84
|
+
gl.bindFramebuffer(GL.DRAW_FRAMEBUFFER, null);
|
|
85
|
+
device.state.enable(GL.SCISSOR_TEST, false);
|
|
86
|
+
gl.blitFramebuffer(0, 0, w, h, 0, h, w, 0, GL.COLOR_BUFFER_BIT, GL.NEAREST);
|
|
87
|
+
const opaque = this.configuration?.alphaMode !== "premultiplied";
|
|
88
|
+
if (opaque) {
|
|
89
|
+
gl.bindFramebuffer(GL.FRAMEBUFFER, null);
|
|
90
|
+
device.state.prepareClear();
|
|
91
|
+
gl.colorMask(false, false, false, true);
|
|
92
|
+
gl.clearColor(0, 0, 0, 1);
|
|
93
|
+
gl.clear(GL.COLOR_BUFFER_BIT);
|
|
94
|
+
gl.colorMask(true, true, true, true);
|
|
95
|
+
}
|
|
96
|
+
if (own) return;
|
|
97
|
+
if (this.target === void 0) {
|
|
98
|
+
this.target = this.canvas.getContext("2d", { alpha: !opaque });
|
|
99
|
+
}
|
|
100
|
+
if (!this.target) return;
|
|
101
|
+
this.target.globalCompositeOperation = "copy";
|
|
102
|
+
this.target.drawImage(glCanvas, 0, 0);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
Webgl2CanvasContext,
|
|
107
|
+
canvasContext
|
|
108
|
+
};
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Webgl2CommandBuffer } from './commands.js';
|
|
2
|
+
import type { Webgl2Device } from './device.js';
|
|
3
|
+
import type { Webgl2Buffer } from './resources.js';
|
|
4
|
+
export declare class Webgl2Queue {
|
|
5
|
+
label: string;
|
|
6
|
+
private readonly device;
|
|
7
|
+
constructor(device: Webgl2Device);
|
|
8
|
+
submit(buffers: Iterable<Webgl2CommandBuffer>): void;
|
|
9
|
+
onSubmittedWorkDone(): Promise<undefined>;
|
|
10
|
+
writeBuffer(buffer: Webgl2Buffer, offset: number, data: BufferSource, dataOffset?: number, size?: number): void;
|
|
11
|
+
writeTexture(destination: GPUTexelCopyTextureInfo, data: BufferSource, layout: GPUTexelCopyBufferLayout, size: GPUExtent3D): void;
|
|
12
|
+
copyExternalImageToTexture(source: GPUCopyExternalImageSourceInfo, destination: GPUCopyExternalImageDestInfo, size: GPUExtent3D): void;
|
|
13
|
+
}
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function axis(value, i, fallback) {
|
|
2
|
+
if (value === void 0) return fallback;
|
|
3
|
+
if (Array.isArray(value)) return value[i] ?? fallback;
|
|
4
|
+
const d = value;
|
|
5
|
+
const v = i === 0 ? d.x ?? d.width : i === 1 ? d.y ?? d.height : d.z ?? d.depthOrArrayLayers;
|
|
6
|
+
return v ?? fallback;
|
|
7
|
+
}
|
|
8
|
+
class Webgl2Queue {
|
|
9
|
+
label = "";
|
|
10
|
+
device;
|
|
11
|
+
constructor(device) {
|
|
12
|
+
this.device = device;
|
|
13
|
+
}
|
|
14
|
+
submit(buffers) {
|
|
15
|
+
this.device.submit(buffers);
|
|
16
|
+
}
|
|
17
|
+
onSubmittedWorkDone() {
|
|
18
|
+
return this.device.afterGpu().then(() => void 0);
|
|
19
|
+
}
|
|
20
|
+
writeBuffer(buffer, offset, data, dataOffset, size) {
|
|
21
|
+
if (this.device.isLost) return;
|
|
22
|
+
buffer.write(offset, data, dataOffset, size);
|
|
23
|
+
}
|
|
24
|
+
writeTexture(destination, data, layout, size) {
|
|
25
|
+
if (this.device.isLost) return;
|
|
26
|
+
const texture = destination.texture;
|
|
27
|
+
const o = destination.origin;
|
|
28
|
+
this.device.copier.writeTexture(
|
|
29
|
+
texture,
|
|
30
|
+
destination.mipLevel ?? 0,
|
|
31
|
+
axis(o, 0, 0),
|
|
32
|
+
axis(o, 1, 0),
|
|
33
|
+
axis(o, 2, 0),
|
|
34
|
+
data,
|
|
35
|
+
layout.offset ?? 0,
|
|
36
|
+
layout.bytesPerRow,
|
|
37
|
+
layout.rowsPerImage,
|
|
38
|
+
axis(size, 0, 1),
|
|
39
|
+
axis(size, 1, 1),
|
|
40
|
+
axis(size, 2, 1)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
copyExternalImageToTexture(source, destination, size) {
|
|
44
|
+
if (this.device.isLost) return;
|
|
45
|
+
this.device.copier.copyExternal(source, destination, axis(size, 0, 1), axis(size, 1, 1));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
Webgl2Queue
|
|
50
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GlFormat } from './formats.js';
|
|
2
|
+
/** A readPixels format/type pair, the bytes a texel takes in it, and whether that's WebGPU's layout. */
|
|
3
|
+
export interface ReadFormat {
|
|
4
|
+
format: number;
|
|
5
|
+
type: number;
|
|
6
|
+
bytes: number;
|
|
7
|
+
direct: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** How a format reads back. Depth reads as floats: it's drawn into an R32F target first. */
|
|
10
|
+
export declare function readFormatOf(info: GlFormat): ReadFormat;
|
|
11
|
+
/** A float32 as float16 bits (round to nearest; exact for values a half target stored). */
|
|
12
|
+
export declare function floatToHalf(value: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Rewrites `rows` rows of `width` texels read as `read` (tight rows, `src`) into WebGPU's bytes for
|
|
15
|
+
* `info`, `bytesPerRow` apart from `outOffset` in `out`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function repack(src: Uint8Array, read: ReadFormat, info: GlFormat, width: number, rows: number, out: Uint8Array, outOffset: number, bytesPerRow: number): void;
|