@swf2es/context3d 0.1.19 → 0.1.21

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.
Files changed (56) hide show
  1. package/README.md +120 -27
  2. package/dist/agal.d.ts +11 -2
  3. package/dist/agal.d.ts.map +1 -1
  4. package/dist/agal.js +318 -47
  5. package/dist/agal.js.map +1 -1
  6. package/dist/backend.d.ts +50 -3
  7. package/dist/backend.d.ts.map +1 -1
  8. package/dist/backend.js +74 -2
  9. package/dist/backend.js.map +1 -1
  10. package/dist/errors.d.ts +1 -1
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/webgl/back-buffer.d.ts +6 -0
  18. package/dist/webgl/back-buffer.d.ts.map +1 -0
  19. package/dist/webgl/back-buffer.js +197 -0
  20. package/dist/webgl/back-buffer.js.map +1 -0
  21. package/dist/webgl/buffers.d.ts +2 -2
  22. package/dist/webgl/buffers.d.ts.map +1 -1
  23. package/dist/webgl/buffers.js +105 -2
  24. package/dist/webgl/buffers.js.map +1 -1
  25. package/dist/webgl/context.d.ts +29 -48
  26. package/dist/webgl/context.d.ts.map +1 -1
  27. package/dist/webgl/context.js +227 -364
  28. package/dist/webgl/context.js.map +1 -1
  29. package/dist/webgl/programs.d.ts +4 -1
  30. package/dist/webgl/programs.d.ts.map +1 -1
  31. package/dist/webgl/programs.js +7 -6
  32. package/dist/webgl/programs.js.map +1 -1
  33. package/dist/webgl/readback.d.ts +3 -0
  34. package/dist/webgl/readback.d.ts.map +1 -0
  35. package/dist/webgl/readback.js +40 -0
  36. package/dist/webgl/readback.js.map +1 -0
  37. package/dist/webgl/targets.d.ts +3 -1
  38. package/dist/webgl/targets.d.ts.map +1 -1
  39. package/dist/webgl/targets.js +5 -3
  40. package/dist/webgl/targets.js.map +1 -1
  41. package/dist/webgl/textures.d.ts +9 -1
  42. package/dist/webgl/textures.d.ts.map +1 -1
  43. package/dist/webgl/textures.js +161 -7
  44. package/dist/webgl/textures.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/agal.ts +375 -51
  47. package/src/backend.ts +113 -3
  48. package/src/errors.ts +27 -1
  49. package/src/index.ts +12 -1
  50. package/src/webgl/back-buffer.ts +287 -0
  51. package/src/webgl/buffers.ts +114 -2
  52. package/src/webgl/context.ts +291 -489
  53. package/src/webgl/programs.ts +10 -5
  54. package/src/webgl/readback.ts +54 -0
  55. package/src/webgl/targets.ts +4 -2
  56. package/src/webgl/textures.ts +183 -9
package/src/backend.ts CHANGED
@@ -73,10 +73,104 @@ export type SamplerFilter = (typeof SAMPLER_FILTERS)[number];
73
73
  export const SAMPLER_MIPS = ["mipnone", "mipnearest", "miplinear"] as const;
74
74
  export type SamplerMip = (typeof SAMPLER_MIPS)[number];
75
75
 
76
- /** The texture formats a backend stores: BGRA, and S3TC's DXT1 and DXT5 as Flash calls them. */
77
- export const TEXTURE_FORMATS = ["bgra", "compressed", "compressedAlpha"] as const;
76
+ /**
77
+ * The texture formats a backend stores: BGRA, S3TC's DXT1 and DXT5 as Flash calls them,
78
+ * 16-bit 5:6:5 colour and 4:4:4:4 colour with alpha, and RGBA in half floats.
79
+ */
80
+ export const TEXTURE_FORMATS = [
81
+ "bgra",
82
+ "compressed",
83
+ "compressedAlpha",
84
+ "bgrPacked565",
85
+ "bgraPacked4444",
86
+ "rgbaHalfFloat",
87
+ ] as const;
78
88
  export type TextureFormat = (typeof TEXTURE_FORMATS)[number];
79
89
 
90
+ /** Bytes a texel of each uncompressed format takes, as its byte uploads give it. */
91
+ export const TEXEL_BYTES: Partial<Record<TextureFormat, number>> = {
92
+ bgra: 4,
93
+ bgrPacked565: 2,
94
+ bgraPacked4444: 2,
95
+ rgbaHalfFloat: 8,
96
+ };
97
+
98
+ /** Whether a format holds S3TC blocks, which no draw renders into. */
99
+ export function compressedFormat(format: TextureFormat): boolean {
100
+ return format === "compressed" || format === "compressedAlpha";
101
+ }
102
+
103
+ /** Stage3D's profiles, from what the least GPU does to the most. */
104
+ export const PROFILES = [
105
+ "baselineConstrained",
106
+ "baseline",
107
+ "baselineExtended",
108
+ "standardConstrained",
109
+ "standard",
110
+ "standardExtended",
111
+ ] as const;
112
+ export type Profile = (typeof PROFILES)[number];
113
+
114
+ /** What a profile allows, as AIR 51 grants it on a desktop GPU (docs/architecture/rendering.md). */
115
+ export interface ProfileLimits {
116
+ /** The newest AGAL version its programs may be. */
117
+ readonly agal: number;
118
+ /** The widest and tallest Texture. */
119
+ readonly textureSize: number;
120
+ /** The widest and tallest RectangleTexture, 0 where the profile has none. */
121
+ readonly rectangleSize: number;
122
+ /** Whether it creates rgbaHalfFloat textures. */
123
+ readonly halfFloat: boolean;
124
+ /** How many textures a draw renders into at once. */
125
+ readonly colorOutputs: number;
126
+ }
127
+
128
+ const LIMITS: Record<Profile, ProfileLimits> = {
129
+ baselineConstrained: {
130
+ agal: 1,
131
+ textureSize: 2048,
132
+ rectangleSize: 0,
133
+ halfFloat: false,
134
+ colorOutputs: 1,
135
+ },
136
+ baseline: { agal: 1, textureSize: 2048, rectangleSize: 2048, halfFloat: false, colorOutputs: 1 },
137
+ baselineExtended: {
138
+ agal: 1,
139
+ textureSize: 4096,
140
+ rectangleSize: 4096,
141
+ halfFloat: false,
142
+ colorOutputs: 1,
143
+ },
144
+ standardConstrained: {
145
+ agal: 2,
146
+ textureSize: 4096,
147
+ rectangleSize: 4096,
148
+ halfFloat: true,
149
+ colorOutputs: 1,
150
+ },
151
+ standard: { agal: 2, textureSize: 4096, rectangleSize: 4096, halfFloat: true, colorOutputs: 4 },
152
+ standardExtended: {
153
+ agal: 3,
154
+ textureSize: 4096,
155
+ rectangleSize: 4096,
156
+ halfFloat: true,
157
+ colorOutputs: 4,
158
+ },
159
+ };
160
+
161
+ export function profileLimits(profile: Profile): ProfileLimits {
162
+ return LIMITS[profile];
163
+ }
164
+
165
+ /** Cube faces are at most this wide in every profile. */
166
+ export const MOST_CUBE = 1024;
167
+
168
+ /** Sampler, vertex attribute and constant registers a context holds in every profile. */
169
+ export const SAMPLER_SLOTS = 16;
170
+ export const VERTEX_SLOTS = 16;
171
+ export const VERTEX_CONSTANTS = 250;
172
+ export const FRAGMENT_CONSTANTS = 200;
173
+
80
174
  export const VERTEX_FORMATS = ["float1", "float2", "float3", "float4", "bytes4"] as const;
81
175
  export type VertexFormat = (typeof VERTEX_FORMATS)[number];
82
176
 
@@ -121,6 +215,10 @@ export interface Buffer3D {
121
215
  }
122
216
 
123
217
  export interface Program3D {
218
+ /** The colour outputs its fragment program writes, 1 to 4; 1 before an upload. */
219
+ readonly outputs: number;
220
+ /** The vertex attributes it reads, a bit each; 0 before an upload. */
221
+ readonly attributes: number;
124
222
  upload(vertex: Uint8Array, fragment: Uint8Array): void;
125
223
  dispose(): void;
126
224
  }
@@ -166,6 +264,11 @@ export interface Context3DLifecycle {
166
264
  /** Loss callbacks are asynchronous. Keep the lost surface until remove(), including
167
265
  * its restoration listener; remove() must detach both listeners before releasing it. */
168
266
  export interface Stage3DHost {
267
+ /**
268
+ * What a request waits for before create(): a host that loads its backend at the first
269
+ * request gives the load, null once loaded. A rejection makes the context unavailable (#3702).
270
+ */
271
+ ready?(): Promise<unknown> | null;
169
272
  create(slot: number, lifecycle: Context3DLifecycle): Context3D;
170
273
  place(slot: number, x: number, y: number, visible: boolean): void;
171
274
  remove(slot: number): void;
@@ -180,6 +283,8 @@ export interface Context3D {
180
283
  * format (TextureBindingError "format"): AIR checks from SWF 17 on.
181
284
  */
182
285
  samplerFormats: boolean;
286
+ /** The profile it was made for: what its programs, textures and targets may be. */
287
+ profile: Profile;
183
288
  /** Optional acceleration for pixels uploaded to compressed textures. */
184
289
  setDxtEncoder?(encoder: DxtEncoder): void;
185
290
  /**
@@ -230,7 +335,7 @@ export interface Context3D {
230
335
  format?: TextureFormat,
231
336
  streaming?: number,
232
337
  ): Texture3D;
233
- createRectangleTexture(width: number, height: number): Texture3D;
338
+ createRectangleTexture(width: number, height: number, format?: TextureFormat): Texture3D;
234
339
  /** Level-zero target; face selects a cube surface. Null resumes the back buffer. */
235
340
  setRenderTarget(
236
341
  texture: Texture3D | null,
@@ -238,6 +343,11 @@ export interface Context3D {
238
343
  face?: number,
239
344
  antiAlias?: number,
240
345
  ): void;
346
+ /**
347
+ * Colour output `index`, 1 to 3, draws into `texture` (`face` of a cube) beside
348
+ * setRenderTarget's; null stops it. The texture's size and format are the target's.
349
+ */
350
+ setColorOutput(index: number, texture: Texture3D | null, face?: number): void;
241
351
  setSampler(index: number, wrap: SamplerWrap, filter: SamplerFilter, mip: SamplerMip): void;
242
352
  setTexture(index: number, texture: Texture3D | null): void;
243
353
  setProgram(program: Program3D): void;
package/src/errors.ts CHANGED
@@ -56,7 +56,33 @@ export type Context3DErrorReason =
56
56
  | "agalDiscard"
57
57
  | "agalMask"
58
58
  | "agalReserved"
59
- | "agalSampler";
59
+ | "agalSampler"
60
+ | "agalProfileVersion"
61
+ | "agalOpcodeRange"
62
+ | "agalOpcodeVersion"
63
+ | "agalFragmentOnly"
64
+ | "agalSamplerStage"
65
+ | "agalElse"
66
+ | "agalEndIf"
67
+ | "agalEmptyBranch"
68
+ | "agalConditionDestination"
69
+ | "agalScalar"
70
+ | "agalNesting"
71
+ | "agalConditionOutput"
72
+ | "agalConditionTexture"
73
+ | "agalDepthStage"
74
+ | "agalDepthIndex"
75
+ | "agalDepthMask"
76
+ | "agalInstanceStage"
77
+ | "agalOutputTwice"
78
+ | "agalOutputSlots"
79
+ | "agalConditionDerivative"
80
+ | "agalConstants"
81
+ | "agalNativeLength"
82
+ | "agalSamplerIndex"
83
+ | "agalSamplerDimension"
84
+ | "agalSamplerMip"
85
+ | "agalSamplerFilter";
60
86
 
61
87
  export class Context3DError extends Error {
62
88
  readonly reason: Context3DErrorReason;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Experimental renderer contracts and AGAL v1 translation, without browser dependencies. */
1
+ /** Stage3D's renderer contracts and AGAL translation, without browser dependencies. */
2
2
 
3
3
  export { type ProgramType, type SamplerSource, type ShaderSource, translate } from "./agal.js";
4
4
  export {
@@ -11,10 +11,18 @@ export {
11
11
  type Context3DLifecycle,
12
12
  CULL_FACES,
13
13
  type CullFace,
14
+ compressedFormat,
15
+ FRAGMENT_CONSTANTS,
14
16
  MOST_BACK_BUFFER,
17
+ MOST_CUBE,
18
+ PROFILES,
19
+ type Profile,
20
+ type ProfileLimits,
15
21
  type Program3D,
22
+ profileLimits,
16
23
  SAMPLER_FILTERS,
17
24
  SAMPLER_MIPS,
25
+ SAMPLER_SLOTS,
18
26
  SAMPLER_WRAPS,
19
27
  SamplerBiasError,
20
28
  type SamplerFilter,
@@ -25,11 +33,14 @@ export {
25
33
  type Stage3DHost,
26
34
  type StencilAction,
27
35
  screenPixels,
36
+ TEXEL_BYTES,
28
37
  TEXTURE_FORMATS,
29
38
  type Texture3D,
30
39
  TextureBindingError,
31
40
  type TextureFormat,
41
+ VERTEX_CONSTANTS,
32
42
  VERTEX_FORMATS,
43
+ VERTEX_SLOTS,
33
44
  type VertexFormat,
34
45
  VertexOffsetError,
35
46
  } from "./backend.js";
@@ -0,0 +1,287 @@
1
+ // The back buffer: its storage, as large as the GPU gives, its multisample resolve and its
2
+ // presentation to the canvas.
3
+
4
+ import { MOST_BACK_BUFFER } from "../backend.js";
5
+ import { Context3DError } from "../errors.js";
6
+
7
+ import type { WebGLContext3D } from "./context.js";
8
+ import { range } from "./state.js";
9
+ import { selectSamples } from "./targets.js";
10
+
11
+ export function configureBackBuffer(
12
+ ctx: WebGLContext3D,
13
+ width: number,
14
+ height: number,
15
+ depth: boolean,
16
+ antiAlias: number,
17
+ scale: number,
18
+ ): void {
19
+ ctx.alive();
20
+ range(32, width - 32, MOST_BACK_BUFFER);
21
+ range(32, height - 32, MOST_BACK_BUFFER);
22
+ if (ctx.renderTexture) {
23
+ throw new Context3DError(
24
+ "operation",
25
+ "Context3D experiment: resize while rendering to texture",
26
+ );
27
+ }
28
+
29
+ // AIR preserves color, depth and bindings when the request is unchanged.
30
+ // Shadow passes can issue this every frame when returning to the back buffer.
31
+ if (
32
+ ctx.framebuffer &&
33
+ width === ctx.width &&
34
+ height === ctx.height &&
35
+ depth === (ctx.depthBuffer !== null) &&
36
+ antiAlias === ctx.antiAlias
37
+ ) {
38
+ if (scale !== ctx.scale) {
39
+ ctx.scale = scale;
40
+ showAt(ctx);
41
+ ctx.applyScissor();
42
+ }
43
+
44
+ return;
45
+ }
46
+
47
+ const samples = selectSamples(ctx.gl, antiAlias, depth);
48
+ const gl = ctx.gl;
49
+ const most = storageLimit(ctx);
50
+ const drawnWidth = Math.min(width, most[0]);
51
+ const drawnHeight = Math.min(height, most[1]);
52
+ const texture = gl.createTexture();
53
+ const framebuffer = gl.createFramebuffer();
54
+ const depthBuffer = depth ? gl.createRenderbuffer() : null;
55
+ const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
56
+ const colorBuffer = samples ? gl.createRenderbuffer() : null;
57
+ try {
58
+ if (
59
+ !texture ||
60
+ !framebuffer ||
61
+ (depth && !depthBuffer) ||
62
+ (samples && (!resolveFramebuffer || !colorBuffer))
63
+ ) {
64
+ throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
65
+ }
66
+
67
+ ctx.texturesApplied[0] = 0;
68
+ gl.activeTexture(gl.TEXTURE0);
69
+ gl.bindTexture(gl.TEXTURE_2D, texture);
70
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
71
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
72
+ gl.texImage2D(
73
+ gl.TEXTURE_2D,
74
+ 0,
75
+ gl.RGBA8,
76
+ drawnWidth,
77
+ drawnHeight,
78
+ 0,
79
+ gl.RGBA,
80
+ gl.UNSIGNED_BYTE,
81
+ null,
82
+ );
83
+ ctx.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
84
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
85
+ if (resolveFramebuffer) {
86
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
87
+ throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
88
+ }
89
+
90
+ ctx.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
91
+ gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
92
+ gl.renderbufferStorageMultisample(
93
+ gl.RENDERBUFFER,
94
+ samples,
95
+ gl.RGBA8,
96
+ drawnWidth,
97
+ drawnHeight,
98
+ );
99
+ gl.framebufferRenderbuffer(
100
+ gl.FRAMEBUFFER,
101
+ gl.COLOR_ATTACHMENT0,
102
+ gl.RENDERBUFFER,
103
+ colorBuffer,
104
+ );
105
+ }
106
+ if (depthBuffer) {
107
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
108
+ if (samples) {
109
+ gl.renderbufferStorageMultisample(
110
+ gl.RENDERBUFFER,
111
+ samples,
112
+ gl.DEPTH24_STENCIL8,
113
+ drawnWidth,
114
+ drawnHeight,
115
+ );
116
+ } else {
117
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
118
+ }
119
+ gl.framebufferRenderbuffer(
120
+ gl.FRAMEBUFFER,
121
+ gl.DEPTH_STENCIL_ATTACHMENT,
122
+ gl.RENDERBUFFER,
123
+ depthBuffer,
124
+ );
125
+ }
126
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
127
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
128
+ throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
129
+ }
130
+ } catch (error) {
131
+ gl.deleteTexture(texture);
132
+ gl.deleteFramebuffer(framebuffer);
133
+ gl.deleteFramebuffer(resolveFramebuffer);
134
+ gl.deleteRenderbuffer(depthBuffer);
135
+ gl.deleteRenderbuffer(colorBuffer);
136
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
137
+ ctx.bindFramebuffer(gl.FRAMEBUFFER, ctx.framebuffer);
138
+ throw error;
139
+ }
140
+
141
+ gl.deleteTexture(ctx.texture);
142
+ gl.deleteFramebuffer(ctx.framebuffer);
143
+ gl.deleteFramebuffer(ctx.resolveFramebuffer);
144
+ gl.deleteRenderbuffer(ctx.depthBuffer);
145
+ gl.deleteRenderbuffer(ctx.colorBuffer);
146
+ ctx.texture = texture;
147
+ ctx.framebuffer = framebuffer;
148
+ ctx.resolveFramebuffer = resolveFramebuffer;
149
+ ctx.colorBuffer = colorBuffer;
150
+ ctx.depthBuffer = depthBuffer;
151
+ ctx.antiAlias = antiAlias;
152
+ ctx.width = width;
153
+ ctx.height = height;
154
+ ctx.drawnWidth = drawnWidth;
155
+ ctx.drawnHeight = drawnHeight;
156
+ ctx.scale = scale;
157
+ gl.canvas.width = drawnWidth;
158
+ gl.canvas.height = drawnHeight;
159
+ showAt(ctx);
160
+ // Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
161
+ ctx.pixels = new Uint8Array(0);
162
+ ctx.topDown = new Uint8Array(0);
163
+ ctx.applyScissor();
164
+ ctx.clear(0, 0, 0, 1);
165
+ }
166
+
167
+ /**
168
+ * Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
169
+ * divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
170
+ */
171
+ function showAt(ctx: WebGLContext3D): void {
172
+ const style = (ctx.gl.canvas as Partial<HTMLCanvasElement>).style;
173
+ if (!style) {
174
+ return;
175
+ }
176
+
177
+ const plain = ctx.scale === 1;
178
+ style.width = plain && ctx.drawnWidth === ctx.width ? "" : `${ctx.width / ctx.scale}px`;
179
+ style.height = plain && ctx.drawnHeight === ctx.height ? "" : `${ctx.height / ctx.scale}px`;
180
+ }
181
+
182
+ /** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
183
+ function storageLimit(ctx: WebGLContext3D): Int32Array {
184
+ if (!ctx.gpuMost) {
185
+ const gl = ctx.gl;
186
+ const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
187
+ const side = Math.min(
188
+ gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
189
+ gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
190
+ );
191
+ ctx.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
192
+ }
193
+
194
+ return ctx.gpuMost;
195
+ }
196
+
197
+ export function unconfigureBackBuffer(ctx: WebGLContext3D): void {
198
+ ctx.alive();
199
+ if (ctx.renderTexture) {
200
+ throw new Context3DError(
201
+ "operation",
202
+ "Context3D experiment: resize while rendering to texture",
203
+ );
204
+ }
205
+
206
+ const gl = ctx.gl;
207
+ gl.deleteTexture(ctx.texture);
208
+ gl.deleteFramebuffer(ctx.framebuffer);
209
+ gl.deleteFramebuffer(ctx.resolveFramebuffer);
210
+ gl.deleteRenderbuffer(ctx.depthBuffer);
211
+ gl.deleteRenderbuffer(ctx.colorBuffer);
212
+ ctx.drawState.forget();
213
+ ctx.texture = null;
214
+ ctx.framebuffer = null;
215
+ ctx.resolveFramebuffer = null;
216
+ ctx.colorBuffer = null;
217
+ ctx.depthBuffer = null;
218
+ ctx.resolveDirty = false;
219
+ ctx.antiAlias = 0;
220
+ ctx.scale = 1;
221
+ ctx.width = 0;
222
+ ctx.height = 0;
223
+ ctx.drawnWidth = 0;
224
+ ctx.drawnHeight = 0;
225
+ ctx.pixels = new Uint8Array(0);
226
+ ctx.topDown = new Uint8Array(0);
227
+ }
228
+
229
+ export function resolveBackBuffer(ctx: WebGLContext3D): void {
230
+ if (!ctx.resolveFramebuffer) {
231
+ return;
232
+ }
233
+
234
+ const gl = ctx.gl;
235
+ if (ctx.resolveDirty) {
236
+ ctx.bindFramebuffer(gl.READ_FRAMEBUFFER, ctx.framebuffer);
237
+ ctx.bindFramebuffer(gl.DRAW_FRAMEBUFFER, ctx.resolveFramebuffer);
238
+ if (ctx.scissor) {
239
+ gl.disable(gl.SCISSOR_TEST);
240
+ }
241
+ gl.blitFramebuffer(
242
+ 0,
243
+ 0,
244
+ ctx.drawnWidth,
245
+ ctx.drawnHeight,
246
+ 0,
247
+ 0,
248
+ ctx.drawnWidth,
249
+ ctx.drawnHeight,
250
+ gl.COLOR_BUFFER_BIT,
251
+ gl.NEAREST,
252
+ );
253
+ if (ctx.scissor) {
254
+ gl.enable(gl.SCISSOR_TEST);
255
+ }
256
+ ctx.resolveDirty = false;
257
+ }
258
+ ctx.bindFramebuffer(gl.READ_FRAMEBUFFER, ctx.resolveFramebuffer);
259
+ }
260
+
261
+ export function presentBackBuffer(ctx: WebGLContext3D): void {
262
+ ctx.setRenderTarget(null);
263
+ ctx.target();
264
+ resolveBackBuffer(ctx);
265
+ const gl = ctx.gl;
266
+ ctx.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
267
+ if (ctx.scissor) {
268
+ gl.disable(gl.SCISSOR_TEST);
269
+ }
270
+ // To the drawing buffer the browser gave, which it may have made smaller than the canvas.
271
+ gl.blitFramebuffer(
272
+ 0,
273
+ 0,
274
+ ctx.drawnWidth,
275
+ ctx.drawnHeight,
276
+ 0,
277
+ 0,
278
+ gl.drawingBufferWidth,
279
+ gl.drawingBufferHeight,
280
+ gl.COLOR_BUFFER_BIT,
281
+ gl.NEAREST,
282
+ );
283
+ if (ctx.scissor) {
284
+ gl.enable(gl.SCISSOR_TEST);
285
+ }
286
+ gl.flush();
287
+ }
@@ -1,4 +1,7 @@
1
1
  import type { Buffer3D } from "../backend.js";
2
+ import { Context3DError } from "../errors.js";
3
+
4
+ import type { WebGLContext3D } from "./context.js";
2
5
  import { range } from "./state.js";
3
6
 
4
7
  export interface BufferState {
@@ -42,7 +45,7 @@ function uploaded(state: BufferState, start: number, amount: number): void {
42
45
  }
43
46
  }
44
47
 
45
- export function uploadBuffer(
48
+ function uploadBuffer(
46
49
  gl: WebGL2RenderingContext,
47
50
  vao: WebGLVertexArrayObject,
48
51
  buffer: Buffer3D,
@@ -69,7 +72,7 @@ export function uploadBuffer(
69
72
  gl.bufferSubData(target, start * size * (stride === 0 ? 2 : 4), data);
70
73
  }
71
74
 
72
- export function uploadBufferBytes(
75
+ function uploadBufferBytes(
73
76
  gl: WebGL2RenderingContext,
74
77
  vao: WebGLVertexArrayObject,
75
78
  buffer: Buffer3D,
@@ -104,3 +107,112 @@ export function uploadBufferBytes(
104
107
  gl.bindBuffer(target, handle);
105
108
  gl.bufferSubData(target, start * bytesPerEntry, data, offset, length);
106
109
  }
110
+
111
+ export function allocateBuffer(
112
+ ctx: WebGLContext3D,
113
+ count: number,
114
+ stride: number,
115
+ dynamic: boolean,
116
+ ): Buffer3D {
117
+ ctx.alive();
118
+ range(1, count - 1, stride === 0 ? 524287 : 65535);
119
+ range(0, stride, 64);
120
+ const countLimit = stride === 0 ? ctx.limits.maxIndexBuffers : ctx.limits.maxVertexBuffers;
121
+ const used = stride === 0 ? ctx.buffers.size - ctx.vertexBuffers : ctx.vertexBuffers;
122
+ if (ctx.buffers.size >= ctx.limits.maxBuffers || used >= countLimit) {
123
+ throw new Context3DError("limit", "Context3D experiment: buffer limit");
124
+ }
125
+
126
+ const bytes = count * (stride === 0 ? 2 : stride * 4);
127
+ const usedBytes = stride === 0 ? ctx.bufferBytes - ctx.vertexBufferBytes : ctx.vertexBufferBytes;
128
+ const byteLimit = stride === 0 ? ctx.limits.maxIndexBufferBytes : ctx.limits.maxVertexBufferBytes;
129
+ if (ctx.bufferBytes + bytes > ctx.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
130
+ throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
131
+ }
132
+
133
+ const gl = ctx.gl;
134
+ const handle = gl.createBuffer();
135
+ if (!handle) {
136
+ throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
137
+ }
138
+
139
+ const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
140
+ if (stride === 0) {
141
+ ctx.indexBuffer = null;
142
+ }
143
+ gl.bindVertexArray(ctx.vao);
144
+ gl.bindBuffer(target, handle);
145
+ gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
146
+ const state: BufferState = {
147
+ handle,
148
+ indices: stride === 0 ? new Uint16Array(count) : null,
149
+ uploaded: new Uint8Array(count),
150
+ prefix: 0,
151
+ version: 0,
152
+ checkedVersion: -1,
153
+ checkedFirst: 0,
154
+ checkedCount: 0,
155
+ maximum: -1,
156
+ ranges: null,
157
+ };
158
+ const buffer: Buffer3D = {
159
+ count,
160
+ stride,
161
+ upload: (values, start, amount) => {
162
+ ctx.alive();
163
+ ctx.buffer(buffer);
164
+ try {
165
+ uploadBuffer(gl, ctx.vao, buffer, state, values, start, amount);
166
+ } finally {
167
+ // Source getters can draw before the upload replaces the VAO's index binding.
168
+ if (stride === 0) {
169
+ ctx.indexBuffer = null;
170
+ }
171
+ }
172
+ },
173
+ uploadBytes: (data, offset, start, amount) => {
174
+ ctx.alive();
175
+ ctx.buffer(buffer);
176
+ try {
177
+ uploadBufferBytes(gl, ctx.vao, buffer, state, data, offset, start, amount);
178
+ } finally {
179
+ if (stride === 0) {
180
+ ctx.indexBuffer = null;
181
+ }
182
+ }
183
+ },
184
+ dispose: () => {
185
+ if (!ctx.buffers.delete(buffer)) {
186
+ return;
187
+ }
188
+
189
+ if (ctx.indexBuffer === handle) {
190
+ ctx.indexBuffer = null;
191
+ }
192
+ gl.deleteBuffer(handle);
193
+ ctx.bufferBytes -= bytes;
194
+ if (stride !== 0) {
195
+ ctx.vertexBuffers--;
196
+ ctx.vertexBufferBytes -= bytes;
197
+ }
198
+ state.indices = null;
199
+ state.ranges = null;
200
+ state.uploaded = new Uint8Array(0);
201
+ for (let i = 0; i < ctx.vertices.length; i++) {
202
+ const binding = ctx.vertices[i];
203
+ if (binding?.buffer === buffer) {
204
+ ctx.vertices[i] = null;
205
+ } else if (binding?.checkedIndex === state) {
206
+ binding.checkedIndex = null;
207
+ }
208
+ }
209
+ },
210
+ };
211
+ ctx.buffers.set(buffer, state);
212
+ if (stride !== 0) {
213
+ ctx.vertexBuffers++;
214
+ ctx.vertexBufferBytes += bytes;
215
+ }
216
+ ctx.bufferBytes += bytes;
217
+ return buffer;
218
+ }