@swf2es/context3d 0.1.18 → 0.1.19

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.
@@ -1,4 +1,4 @@
1
- import { SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_WRAPS, ScissorBoundsError, TextureBindingError, VERTEX_FORMATS, VertexOffsetError, } from "../backend.js";
1
+ import { MOST_BACK_BUFFER, SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_WRAPS, ScissorBoundsError, screenPixels, TextureBindingError, VERTEX_FORMATS, VertexOffsetError, } from "../backend.js";
2
2
  import { GlState } from "../device/state.js";
3
3
  import { blockBytes } from "../dxt.js";
4
4
  import { Context3DError } from "../errors.js";
@@ -31,6 +31,13 @@ export class WebGLContext3D {
31
31
  drawState;
32
32
  width = 0;
33
33
  height = 0;
34
+ /**
35
+ * The back buffer's storage: its size, or less where the GPU allows less. The SWF still
36
+ * sees the size it asked for, drawn at this one and stretched to it when presented.
37
+ */
38
+ drawnWidth = 0;
39
+ drawnHeight = 0;
40
+ gpuMost = null;
34
41
  disposed = false;
35
42
  samplerFormats = true;
36
43
  buffers = new Map();
@@ -71,6 +78,8 @@ export class WebGLContext3D {
71
78
  resolveDirty = false;
72
79
  depthBuffer = null;
73
80
  antiAlias = 0;
81
+ /** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
82
+ scale = 1;
74
83
  depthWrite = true;
75
84
  depthCompare = "less";
76
85
  blendEnabled = false;
@@ -150,17 +159,17 @@ export class WebGLContext3D {
150
159
  }
151
160
  }
152
161
  this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
153
- this.drawState.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
162
+ this.drawState.viewport(0, 0, texture?.width ?? this.drawnWidth, texture?.height ?? this.drawnHeight);
154
163
  }
155
164
  /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
156
165
  bindFramebuffer(target, framebuffer) {
157
166
  this.drawState.forget();
158
167
  this.gl.bindFramebuffer(target, framebuffer);
159
168
  }
160
- configure(width, height, depth = false, antiAlias = 0) {
169
+ configure(width, height, depth = false, antiAlias = 0, scale = 1) {
161
170
  this.alive();
162
- range(32, width - 32, 2048);
163
- range(32, height - 32, 2048);
171
+ range(32, width - 32, MOST_BACK_BUFFER);
172
+ range(32, height - 32, MOST_BACK_BUFFER);
164
173
  if (this.renderTexture) {
165
174
  throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
166
175
  }
@@ -171,10 +180,18 @@ export class WebGLContext3D {
171
180
  height === this.height &&
172
181
  depth === (this.depthBuffer !== null) &&
173
182
  antiAlias === this.antiAlias) {
183
+ if (scale !== this.scale) {
184
+ this.scale = scale;
185
+ this.showAt();
186
+ this.applyScissor();
187
+ }
174
188
  return;
175
189
  }
176
190
  const samples = selectSamples(this.gl, antiAlias, depth);
177
191
  const gl = this.gl;
192
+ const most = this.storageLimit();
193
+ const drawnWidth = Math.min(width, most[0]);
194
+ const drawnHeight = Math.min(height, most[1]);
178
195
  const texture = gl.createTexture();
179
196
  const framebuffer = gl.createFramebuffer();
180
197
  const depthBuffer = depth ? gl.createRenderbuffer() : null;
@@ -192,7 +209,7 @@ export class WebGLContext3D {
192
209
  gl.bindTexture(gl.TEXTURE_2D, texture);
193
210
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
194
211
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
195
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
212
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, drawnWidth, drawnHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
196
213
  this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
197
214
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
198
215
  if (resolveFramebuffer) {
@@ -201,16 +218,16 @@ export class WebGLContext3D {
201
218
  }
202
219
  this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
203
220
  gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
204
- gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, width, height);
221
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, drawnWidth, drawnHeight);
205
222
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
206
223
  }
207
224
  if (depthBuffer) {
208
225
  gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
209
226
  if (samples) {
210
- gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height);
227
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
211
228
  }
212
229
  else {
213
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
230
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
214
231
  }
215
232
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
216
233
  }
@@ -242,13 +259,68 @@ export class WebGLContext3D {
242
259
  this.antiAlias = antiAlias;
243
260
  this.width = width;
244
261
  this.height = height;
245
- gl.canvas.width = width;
246
- gl.canvas.height = height;
247
- this.pixels = new Uint8Array(width * height * 4);
248
- this.topDown = new Uint8Array(this.pixels.length);
262
+ this.drawnWidth = drawnWidth;
263
+ this.drawnHeight = drawnHeight;
264
+ this.scale = scale;
265
+ gl.canvas.width = drawnWidth;
266
+ gl.canvas.height = drawnHeight;
267
+ this.showAt();
268
+ // Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
269
+ this.pixels = new Uint8Array(0);
270
+ this.topDown = new Uint8Array(0);
249
271
  this.applyScissor();
250
272
  this.clear(0, 0, 0, 1);
251
273
  }
274
+ /**
275
+ * Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
276
+ * divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
277
+ */
278
+ showAt() {
279
+ const style = this.gl.canvas.style;
280
+ if (!style) {
281
+ return;
282
+ }
283
+ const plain = this.scale === 1;
284
+ style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
285
+ style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
286
+ }
287
+ /** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
288
+ storageLimit() {
289
+ if (!this.gpuMost) {
290
+ const gl = this.gl;
291
+ const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
292
+ const side = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), gl.getParameter(gl.MAX_RENDERBUFFER_SIZE));
293
+ this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
294
+ }
295
+ return this.gpuMost;
296
+ }
297
+ unconfigure() {
298
+ this.alive();
299
+ if (this.renderTexture) {
300
+ throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
301
+ }
302
+ const gl = this.gl;
303
+ gl.deleteTexture(this.texture);
304
+ gl.deleteFramebuffer(this.framebuffer);
305
+ gl.deleteFramebuffer(this.resolveFramebuffer);
306
+ gl.deleteRenderbuffer(this.depthBuffer);
307
+ gl.deleteRenderbuffer(this.colorBuffer);
308
+ this.drawState.forget();
309
+ this.texture = null;
310
+ this.framebuffer = null;
311
+ this.resolveFramebuffer = null;
312
+ this.colorBuffer = null;
313
+ this.depthBuffer = null;
314
+ this.resolveDirty = false;
315
+ this.antiAlias = 0;
316
+ this.scale = 1;
317
+ this.width = 0;
318
+ this.height = 0;
319
+ this.drawnWidth = 0;
320
+ this.drawnHeight = 0;
321
+ this.pixels = new Uint8Array(0);
322
+ this.topDown = new Uint8Array(0);
323
+ }
252
324
  clear(red, green, blue, alpha, depth = 1, mask = 0xffffffff, stencil = 0) {
253
325
  this.target(false, (mask & 1) !== 0);
254
326
  const gl = this.gl;
@@ -401,13 +473,33 @@ export class WebGLContext3D {
401
473
  }
402
474
  const width = this.renderTexture?.width ?? this.width;
403
475
  const height = this.renderTexture?.height ?? this.height;
404
- const left = Math.max(0, Math.min(width, this.scissorLeft));
405
- const top = Math.max(0, Math.min(height, this.scissorTop));
406
- const right = Math.max(0, Math.min(width, this.scissorRight));
407
- const bottom = Math.max(0, Math.min(height, this.scissorBottom));
476
+ let left = Math.max(0, Math.min(width, this.scissorLeft));
477
+ let top = Math.max(0, Math.min(height, this.scissorTop));
478
+ let right = Math.max(0, Math.min(width, this.scissorRight));
479
+ let bottom = Math.max(0, Math.min(height, this.scissorBottom));
480
+ // AIR tells a rectangle outside from its own unit, then scales it to a back buffer
481
+ // configured for best resolution, whose pixels are the screen's.
408
482
  this.scissorOutside = right <= left || bottom <= top;
483
+ if (!this.renderTexture && this.scale !== 1) {
484
+ const scale = this.scale;
485
+ left = Math.max(0, Math.min(width, screenPixels(this.scissorLeft, scale)));
486
+ top = Math.max(0, Math.min(height, screenPixels(this.scissorTop, scale)));
487
+ right = Math.max(0, Math.min(width, screenPixels(this.scissorRight, scale)));
488
+ bottom = Math.max(0, Math.min(height, screenPixels(this.scissorBottom, scale)));
489
+ }
490
+ let drawnHeight = height;
491
+ if (!this.renderTexture && (this.drawnWidth !== width || this.drawnHeight !== height)) {
492
+ // The back buffer's storage is smaller than its size: the rectangle shrinks with it.
493
+ const x = this.drawnWidth / width;
494
+ const y = this.drawnHeight / height;
495
+ left = Math.round(left * x);
496
+ right = Math.round(right * x);
497
+ top = Math.round(top * y);
498
+ bottom = Math.round(bottom * y);
499
+ drawnHeight = this.drawnHeight;
500
+ }
409
501
  gl.enable(gl.SCISSOR_TEST);
410
- gl.scissor(left, this.renderTexture ? top : height - bottom, Math.max(0, right - left), Math.max(0, bottom - top));
502
+ gl.scissor(left, this.renderTexture ? top : drawnHeight - bottom, Math.max(0, right - left), Math.max(0, bottom - top));
411
503
  }
412
504
  setBlend(source, destination) {
413
505
  this.alive();
@@ -456,7 +548,7 @@ export class WebGLContext3D {
456
548
  break;
457
549
  }
458
550
  }
459
- createBuffer(count, stride) {
551
+ createBuffer(count, stride, dynamic = false) {
460
552
  this.alive();
461
553
  range(1, count - 1, stride === 0 ? 524287 : 65535);
462
554
  range(0, stride, 64);
@@ -482,7 +574,7 @@ export class WebGLContext3D {
482
574
  }
483
575
  gl.bindVertexArray(this.vao);
484
576
  gl.bindBuffer(target, handle);
485
- gl.bufferData(target, bytes, gl.STATIC_DRAW);
577
+ gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
486
578
  const state = {
487
579
  handle,
488
580
  indices: stride === 0 ? new Uint16Array(count) : null,
@@ -636,8 +728,8 @@ export class WebGLContext3D {
636
728
  }
637
729
  return state;
638
730
  }
639
- createTexture(width, height, cube = false, format = "bgra") {
640
- return this.allocateTexture(width, height, cube, false, format);
731
+ createTexture(width, height, cube = false, format = "bgra", streaming = 0) {
732
+ return this.allocateTexture(width, height, cube, false, format, streaming);
641
733
  }
642
734
  createRectangleTexture(width, height) {
643
735
  return this.allocateTexture(width, height, false, true, "bgra");
@@ -669,7 +761,7 @@ export class WebGLContext3D {
669
761
  }
670
762
  return size * faces;
671
763
  }
672
- allocateTexture(width, height, cube, rectangle, format) {
764
+ allocateTexture(width, height, cube, rectangle, format, streaming = 0) {
673
765
  this.alive();
674
766
  range(1, width - 1, 2048);
675
767
  range(1, height - 1, 2048);
@@ -681,6 +773,7 @@ export class WebGLContext3D {
681
773
  }
682
774
  const faces = cube ? 6 : 1;
683
775
  const levels = rectangle ? 1 : 1 + Math.floor(Math.log2(Math.max(width, height)));
776
+ range(streaming, 1, levels);
684
777
  if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
685
778
  throw new Context3DError("texturePowerOfTwo", "Context3D texture size must be a power of two");
686
779
  }
@@ -737,6 +830,7 @@ export class WebGLContext3D {
737
830
  multisample: null,
738
831
  samples: 0,
739
832
  antiAlias: 0,
833
+ base: 0,
740
834
  };
741
835
  const texture = {
742
836
  width,
@@ -745,6 +839,7 @@ export class WebGLContext3D {
745
839
  cube,
746
840
  rectangle,
747
841
  format,
842
+ streaming,
748
843
  upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
749
844
  this.alive();
750
845
  this.textureState(texture);
@@ -1383,7 +1478,10 @@ export class WebGLContext3D {
1383
1478
  throw new TextureBindingError("unuploaded");
1384
1479
  }
1385
1480
  }
1386
- for (let face = 0; face < resource.uploaded.length; face++) {
1481
+ const base = texture.streaming
1482
+ ? this.streamingBase(texture, resource, this.samplerStates[slot] >>> 5 !== 0)
1483
+ : 0;
1484
+ for (let face = 0; !texture.streaming && face < resource.uploaded.length; face++) {
1387
1485
  if (!texture.cube && !(resource.uploaded[face] & 1)) {
1388
1486
  throw new TextureBindingError("unuploaded");
1389
1487
  }
@@ -1403,6 +1501,13 @@ export class WebGLContext3D {
1403
1501
  gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
1404
1502
  this.texturesApplied[slot] = 1;
1405
1503
  }
1504
+ if (resource.base !== base) {
1505
+ const target = texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
1506
+ gl.activeTexture(gl.TEXTURE0 + slot);
1507
+ gl.bindTexture(target, resource.handle);
1508
+ gl.texParameteri(target, gl.TEXTURE_BASE_LEVEL, base);
1509
+ resource.base = base;
1510
+ }
1406
1511
  if (!state.samplersSet) {
1407
1512
  gl.uniform1i(binding.location, slot);
1408
1513
  }
@@ -1425,6 +1530,45 @@ export class WebGLContext3D {
1425
1530
  gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, first * 2);
1426
1531
  this.changedTarget();
1427
1532
  }
1533
+ /**
1534
+ * The level a streaming texture is drawn from, as AIR 51 draws one: with mip filtering, the
1535
+ * finest whose levels down to the smallest are uploaded on every face, which must reach its
1536
+ * streaming level (#3700 before); without, its level 0 alone (#3713), as a texture that
1537
+ * does not stream.
1538
+ */
1539
+ streamingBase(texture, resource, mip) {
1540
+ const faces = resource.uploaded.length;
1541
+ let uploaded = 0;
1542
+ for (let face = 0; face < faces; face++) {
1543
+ uploaded |= resource.uploaded[face];
1544
+ }
1545
+ if (!uploaded) {
1546
+ throw new TextureBindingError("unuploaded");
1547
+ }
1548
+ if (!mip) {
1549
+ for (let face = 0; face < faces; face++) {
1550
+ if (!(resource.uploaded[face] & 1)) {
1551
+ throw new TextureBindingError("streaming");
1552
+ }
1553
+ }
1554
+ return 0;
1555
+ }
1556
+ let base = texture.levels;
1557
+ while (base > 0) {
1558
+ let whole = true;
1559
+ for (let face = 0; face < faces; face++) {
1560
+ whole &&= (resource.uploaded[face] & (1 << (base - 1))) !== 0;
1561
+ }
1562
+ if (!whole) {
1563
+ break;
1564
+ }
1565
+ base--;
1566
+ }
1567
+ if (base > texture.streaming) {
1568
+ throw new TextureBindingError("mipmaps");
1569
+ }
1570
+ return base;
1571
+ }
1428
1572
  uniforms(locations, bank, previous, values) {
1429
1573
  for (let i = 0; i < locations.length; i++) {
1430
1574
  if (locations[i] && (bank.writing || previous[i] !== bank.versions[i])) {
@@ -1460,7 +1604,7 @@ export class WebGLContext3D {
1460
1604
  if (this.scissor) {
1461
1605
  gl.disable(gl.SCISSOR_TEST);
1462
1606
  }
1463
- gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
1607
+ gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, this.drawnWidth, this.drawnHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
1464
1608
  if (this.scissor) {
1465
1609
  gl.enable(gl.SCISSOR_TEST);
1466
1610
  }
@@ -1474,12 +1618,34 @@ export class WebGLContext3D {
1474
1618
  this.target(true);
1475
1619
  this.resolve();
1476
1620
  const gl = this.gl;
1621
+ const scaleX = this.drawnWidth / this.width;
1622
+ const scaleY = this.drawnHeight / this.height;
1623
+ const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
1624
+ const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
1625
+ if (this.pixels.length < drawnWidth * drawnHeight * 4) {
1626
+ this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
1627
+ }
1628
+ if (this.topDown.length < width * height * 4) {
1629
+ this.topDown = new Uint8Array(width * height * 4);
1630
+ }
1477
1631
  // GL's rows run bottom-up, so the top-left region starts height rows below the top.
1478
- gl.readPixels(0, this.height - height, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
1632
+ gl.readPixels(0, this.drawnHeight - drawnHeight, drawnWidth, drawnHeight, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
1479
1633
  const row = width * 4;
1634
+ if (drawnWidth === width && drawnHeight === height) {
1635
+ for (let y = 0; y < height; y++) {
1636
+ const from = (height - y - 1) * row;
1637
+ this.topDown.set(this.pixels.subarray(from, from + row), y * row);
1638
+ }
1639
+ return this.topDown;
1640
+ }
1641
+ // Storage smaller than the back buffer: each pixel is the stored one it falls on.
1642
+ const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
1643
+ const target = new Uint32Array(this.topDown.buffer, 0, width * height);
1480
1644
  for (let y = 0; y < height; y++) {
1481
- const from = (height - y - 1) * row;
1482
- this.topDown.set(this.pixels.subarray(from, from + row), y * row);
1645
+ const from = (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
1646
+ for (let x = 0; x < width; x++) {
1647
+ target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
1648
+ }
1483
1649
  }
1484
1650
  return this.topDown;
1485
1651
  }
@@ -1492,7 +1658,8 @@ export class WebGLContext3D {
1492
1658
  if (this.scissor) {
1493
1659
  gl.disable(gl.SCISSOR_TEST);
1494
1660
  }
1495
- gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
1661
+ // To the drawing buffer the browser gave, which it may have made smaller than the canvas.
1662
+ gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
1496
1663
  if (this.scissor) {
1497
1664
  gl.enable(gl.SCISSOR_TEST);
1498
1665
  }