@swf2es/context3d 0.1.20 → 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.
@@ -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
+ }