@swf2es/context3d 0.1.17 → 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.
@@ -5,6 +5,7 @@ import {
5
5
  type CompareMode,
6
6
  type Context3D,
7
7
  type CullFace,
8
+ MOST_BACK_BUFFER,
8
9
  type Program3D,
9
10
  SAMPLER_FILTERS,
10
11
  SAMPLER_MIPS,
@@ -14,6 +15,7 @@ import {
14
15
  type SamplerWrap,
15
16
  ScissorBoundsError,
16
17
  type StencilAction,
18
+ screenPixels,
17
19
  type Texture3D,
18
20
  TextureBindingError,
19
21
  type TextureFormat,
@@ -94,6 +96,13 @@ export class WebGLContext3D implements Context3D {
94
96
  private readonly drawState: GlState;
95
97
  width = 0;
96
98
  height = 0;
99
+ /**
100
+ * The back buffer's storage: its size, or less where the GPU allows less. The SWF still
101
+ * sees the size it asked for, drawn at this one and stretched to it when presented.
102
+ */
103
+ private drawnWidth = 0;
104
+ private drawnHeight = 0;
105
+ private gpuMost: Int32Array | null = null;
97
106
  disposed = false;
98
107
  samplerFormats = true;
99
108
  private readonly buffers = new Map<Buffer3D, BufferState>();
@@ -134,6 +143,8 @@ export class WebGLContext3D implements Context3D {
134
143
  private resolveDirty = false;
135
144
  private depthBuffer: WebGLRenderbuffer | null = null;
136
145
  private antiAlias = 0;
146
+ /** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
147
+ private scale = 1;
137
148
  private depthWrite = true;
138
149
  private depthCompare: CompareMode = "less";
139
150
  private blendEnabled = false;
@@ -225,7 +236,12 @@ export class WebGLContext3D implements Context3D {
225
236
  }
226
237
  }
227
238
  this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
228
- this.drawState.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
239
+ this.drawState.viewport(
240
+ 0,
241
+ 0,
242
+ texture?.width ?? this.drawnWidth,
243
+ texture?.height ?? this.drawnHeight,
244
+ );
229
245
  }
230
246
 
231
247
  /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
@@ -234,10 +250,10 @@ export class WebGLContext3D implements Context3D {
234
250
  this.gl.bindFramebuffer(target, framebuffer);
235
251
  }
236
252
 
237
- configure(width: number, height: number, depth = false, antiAlias = 0): void {
253
+ configure(width: number, height: number, depth = false, antiAlias = 0, scale = 1): void {
238
254
  this.alive();
239
- range(32, width - 32, 2048);
240
- range(32, height - 32, 2048);
255
+ range(32, width - 32, MOST_BACK_BUFFER);
256
+ range(32, height - 32, MOST_BACK_BUFFER);
241
257
  if (this.renderTexture) {
242
258
  throw new Context3DError(
243
259
  "operation",
@@ -254,11 +270,20 @@ export class WebGLContext3D implements Context3D {
254
270
  depth === (this.depthBuffer !== null) &&
255
271
  antiAlias === this.antiAlias
256
272
  ) {
273
+ if (scale !== this.scale) {
274
+ this.scale = scale;
275
+ this.showAt();
276
+ this.applyScissor();
277
+ }
278
+
257
279
  return;
258
280
  }
259
281
 
260
282
  const samples = selectSamples(this.gl, antiAlias, depth);
261
283
  const gl = this.gl;
284
+ const most = this.storageLimit();
285
+ const drawnWidth = Math.min(width, most[0]);
286
+ const drawnHeight = Math.min(height, most[1]);
262
287
  const texture = gl.createTexture();
263
288
  const framebuffer = gl.createFramebuffer();
264
289
  const depthBuffer = depth ? gl.createRenderbuffer() : null;
@@ -279,7 +304,17 @@ export class WebGLContext3D implements Context3D {
279
304
  gl.bindTexture(gl.TEXTURE_2D, texture);
280
305
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
281
306
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
282
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
307
+ gl.texImage2D(
308
+ gl.TEXTURE_2D,
309
+ 0,
310
+ gl.RGBA8,
311
+ drawnWidth,
312
+ drawnHeight,
313
+ 0,
314
+ gl.RGBA,
315
+ gl.UNSIGNED_BYTE,
316
+ null,
317
+ );
283
318
  this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
284
319
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
285
320
  if (resolveFramebuffer) {
@@ -289,7 +324,13 @@ export class WebGLContext3D implements Context3D {
289
324
 
290
325
  this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
291
326
  gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
292
- gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, width, height);
327
+ gl.renderbufferStorageMultisample(
328
+ gl.RENDERBUFFER,
329
+ samples,
330
+ gl.RGBA8,
331
+ drawnWidth,
332
+ drawnHeight,
333
+ );
293
334
  gl.framebufferRenderbuffer(
294
335
  gl.FRAMEBUFFER,
295
336
  gl.COLOR_ATTACHMENT0,
@@ -304,11 +345,11 @@ export class WebGLContext3D implements Context3D {
304
345
  gl.RENDERBUFFER,
305
346
  samples,
306
347
  gl.DEPTH24_STENCIL8,
307
- width,
308
- height,
348
+ drawnWidth,
349
+ drawnHeight,
309
350
  );
310
351
  } else {
311
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
352
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
312
353
  }
313
354
  gl.framebufferRenderbuffer(
314
355
  gl.FRAMEBUFFER,
@@ -345,14 +386,81 @@ export class WebGLContext3D implements Context3D {
345
386
  this.antiAlias = antiAlias;
346
387
  this.width = width;
347
388
  this.height = height;
348
- gl.canvas.width = width;
349
- gl.canvas.height = height;
350
- this.pixels = new Uint8Array(width * height * 4);
351
- this.topDown = new Uint8Array(this.pixels.length);
389
+ this.drawnWidth = drawnWidth;
390
+ this.drawnHeight = drawnHeight;
391
+ this.scale = scale;
392
+ gl.canvas.width = drawnWidth;
393
+ gl.canvas.height = drawnHeight;
394
+ this.showAt();
395
+ // Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
396
+ this.pixels = new Uint8Array(0);
397
+ this.topDown = new Uint8Array(0);
352
398
  this.applyScissor();
353
399
  this.clear(0, 0, 0, 1);
354
400
  }
355
401
 
402
+ /**
403
+ * Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
404
+ * divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
405
+ */
406
+ private showAt(): void {
407
+ const style = (this.gl.canvas as Partial<HTMLCanvasElement>).style;
408
+ if (!style) {
409
+ return;
410
+ }
411
+
412
+ const plain = this.scale === 1;
413
+ style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
414
+ style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
415
+ }
416
+
417
+ /** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
418
+ private storageLimit(): Int32Array {
419
+ if (!this.gpuMost) {
420
+ const gl = this.gl;
421
+ const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
422
+ const side = Math.min(
423
+ gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
424
+ gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
425
+ );
426
+ this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
427
+ }
428
+
429
+ return this.gpuMost;
430
+ }
431
+
432
+ unconfigure(): void {
433
+ this.alive();
434
+ if (this.renderTexture) {
435
+ throw new Context3DError(
436
+ "operation",
437
+ "Context3D experiment: resize while rendering to texture",
438
+ );
439
+ }
440
+
441
+ const gl = this.gl;
442
+ gl.deleteTexture(this.texture);
443
+ gl.deleteFramebuffer(this.framebuffer);
444
+ gl.deleteFramebuffer(this.resolveFramebuffer);
445
+ gl.deleteRenderbuffer(this.depthBuffer);
446
+ gl.deleteRenderbuffer(this.colorBuffer);
447
+ this.drawState.forget();
448
+ this.texture = null;
449
+ this.framebuffer = null;
450
+ this.resolveFramebuffer = null;
451
+ this.colorBuffer = null;
452
+ this.depthBuffer = null;
453
+ this.resolveDirty = false;
454
+ this.antiAlias = 0;
455
+ this.scale = 1;
456
+ this.width = 0;
457
+ this.height = 0;
458
+ this.drawnWidth = 0;
459
+ this.drawnHeight = 0;
460
+ this.pixels = new Uint8Array(0);
461
+ this.topDown = new Uint8Array(0);
462
+ }
463
+
356
464
  clear(
357
465
  red: number,
358
466
  green: number,
@@ -565,15 +673,36 @@ export class WebGLContext3D implements Context3D {
565
673
 
566
674
  const width = this.renderTexture?.width ?? this.width;
567
675
  const height = this.renderTexture?.height ?? this.height;
568
- const left = Math.max(0, Math.min(width, this.scissorLeft));
569
- const top = Math.max(0, Math.min(height, this.scissorTop));
570
- const right = Math.max(0, Math.min(width, this.scissorRight));
571
- const bottom = Math.max(0, Math.min(height, this.scissorBottom));
676
+ let left = Math.max(0, Math.min(width, this.scissorLeft));
677
+ let top = Math.max(0, Math.min(height, this.scissorTop));
678
+ let right = Math.max(0, Math.min(width, this.scissorRight));
679
+ let bottom = Math.max(0, Math.min(height, this.scissorBottom));
680
+ // AIR tells a rectangle outside from its own unit, then scales it to a back buffer
681
+ // configured for best resolution, whose pixels are the screen's.
572
682
  this.scissorOutside = right <= left || bottom <= top;
683
+ if (!this.renderTexture && this.scale !== 1) {
684
+ const scale = this.scale;
685
+ left = Math.max(0, Math.min(width, screenPixels(this.scissorLeft, scale)));
686
+ top = Math.max(0, Math.min(height, screenPixels(this.scissorTop, scale)));
687
+ right = Math.max(0, Math.min(width, screenPixels(this.scissorRight, scale)));
688
+ bottom = Math.max(0, Math.min(height, screenPixels(this.scissorBottom, scale)));
689
+ }
690
+ let drawnHeight = height;
691
+ if (!this.renderTexture && (this.drawnWidth !== width || this.drawnHeight !== height)) {
692
+ // The back buffer's storage is smaller than its size: the rectangle shrinks with it.
693
+ const x = this.drawnWidth / width;
694
+ const y = this.drawnHeight / height;
695
+ left = Math.round(left * x);
696
+ right = Math.round(right * x);
697
+ top = Math.round(top * y);
698
+ bottom = Math.round(bottom * y);
699
+ drawnHeight = this.drawnHeight;
700
+ }
701
+
573
702
  gl.enable(gl.SCISSOR_TEST);
574
703
  gl.scissor(
575
704
  left,
576
- this.renderTexture ? top : height - bottom,
705
+ this.renderTexture ? top : drawnHeight - bottom,
577
706
  Math.max(0, right - left),
578
707
  Math.max(0, bottom - top),
579
708
  );
@@ -630,7 +759,7 @@ export class WebGLContext3D implements Context3D {
630
759
  }
631
760
  }
632
761
 
633
- createBuffer(count: number, stride: number): Buffer3D {
762
+ createBuffer(count: number, stride: number, dynamic = false): Buffer3D {
634
763
  this.alive();
635
764
  range(1, count - 1, stride === 0 ? 524287 : 65535);
636
765
  range(0, stride, 64);
@@ -661,7 +790,7 @@ export class WebGLContext3D implements Context3D {
661
790
  }
662
791
  gl.bindVertexArray(this.vao);
663
792
  gl.bindBuffer(target, handle);
664
- gl.bufferData(target, bytes, gl.STATIC_DRAW);
793
+ gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
665
794
  const state: BufferState = {
666
795
  handle,
667
796
  indices: stride === 0 ? new Uint16Array(count) : null,
@@ -834,8 +963,9 @@ export class WebGLContext3D implements Context3D {
834
963
  height: number,
835
964
  cube = false,
836
965
  format: TextureFormat = "bgra",
966
+ streaming = 0,
837
967
  ): Texture3D {
838
- return this.allocateTexture(width, height, cube, false, format);
968
+ return this.allocateTexture(width, height, cube, false, format, streaming);
839
969
  }
840
970
 
841
971
  createRectangleTexture(width: number, height: number): Texture3D {
@@ -885,6 +1015,7 @@ export class WebGLContext3D implements Context3D {
885
1015
  cube: boolean,
886
1016
  rectangle: boolean,
887
1017
  format: TextureFormat,
1018
+ streaming = 0,
888
1019
  ): Texture3D {
889
1020
  this.alive();
890
1021
  range(1, width - 1, 2048);
@@ -898,6 +1029,7 @@ export class WebGLContext3D implements Context3D {
898
1029
 
899
1030
  const faces = cube ? 6 : 1;
900
1031
  const levels = rectangle ? 1 : 1 + Math.floor(Math.log2(Math.max(width, height)));
1032
+ range(streaming, 1, levels);
901
1033
  if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
902
1034
  throw new Context3DError(
903
1035
  "texturePowerOfTwo",
@@ -963,6 +1095,7 @@ export class WebGLContext3D implements Context3D {
963
1095
  multisample: null,
964
1096
  samples: 0,
965
1097
  antiAlias: 0,
1098
+ base: 0,
966
1099
  };
967
1100
  const texture: Texture3D = {
968
1101
  width,
@@ -971,6 +1104,7 @@ export class WebGLContext3D implements Context3D {
971
1104
  cube,
972
1105
  rectangle,
973
1106
  format,
1107
+ streaming,
974
1108
  upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
975
1109
  this.alive();
976
1110
  this.textureState(texture);
@@ -1800,7 +1934,10 @@ export class WebGLContext3D implements Context3D {
1800
1934
  throw new TextureBindingError("unuploaded");
1801
1935
  }
1802
1936
  }
1803
- for (let face = 0; face < resource.uploaded.length; face++) {
1937
+ const base = texture.streaming
1938
+ ? this.streamingBase(texture, resource, this.samplerStates[slot] >>> 5 !== 0)
1939
+ : 0;
1940
+ for (let face = 0; !texture.streaming && face < resource.uploaded.length; face++) {
1804
1941
  if (!texture.cube && !(resource.uploaded[face] & 1)) {
1805
1942
  throw new TextureBindingError("unuploaded");
1806
1943
  }
@@ -1823,6 +1960,13 @@ export class WebGLContext3D implements Context3D {
1823
1960
  gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
1824
1961
  this.texturesApplied[slot] = 1;
1825
1962
  }
1963
+ if (resource.base !== base) {
1964
+ const target = texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
1965
+ gl.activeTexture(gl.TEXTURE0 + slot);
1966
+ gl.bindTexture(target, resource.handle);
1967
+ gl.texParameteri(target, gl.TEXTURE_BASE_LEVEL, base);
1968
+ resource.base = base;
1969
+ }
1826
1970
  if (!state.samplersSet) {
1827
1971
  gl.uniform1i(binding.location, slot);
1828
1972
  }
@@ -1852,6 +1996,50 @@ export class WebGLContext3D implements Context3D {
1852
1996
  this.changedTarget();
1853
1997
  }
1854
1998
 
1999
+ /**
2000
+ * The level a streaming texture is drawn from, as AIR 51 draws one: with mip filtering, the
2001
+ * finest whose levels down to the smallest are uploaded on every face, which must reach its
2002
+ * streaming level (#3700 before); without, its level 0 alone (#3713), as a texture that
2003
+ * does not stream.
2004
+ */
2005
+ private streamingBase(texture: Texture3D, resource: TextureState, mip: boolean): number {
2006
+ const faces = resource.uploaded.length;
2007
+ let uploaded = 0;
2008
+ for (let face = 0; face < faces; face++) {
2009
+ uploaded |= resource.uploaded[face];
2010
+ }
2011
+ if (!uploaded) {
2012
+ throw new TextureBindingError("unuploaded");
2013
+ }
2014
+ if (!mip) {
2015
+ for (let face = 0; face < faces; face++) {
2016
+ if (!(resource.uploaded[face] & 1)) {
2017
+ throw new TextureBindingError("streaming");
2018
+ }
2019
+ }
2020
+
2021
+ return 0;
2022
+ }
2023
+
2024
+ let base = texture.levels;
2025
+ while (base > 0) {
2026
+ let whole = true;
2027
+ for (let face = 0; face < faces; face++) {
2028
+ whole &&= (resource.uploaded[face] & (1 << (base - 1))) !== 0;
2029
+ }
2030
+ if (!whole) {
2031
+ break;
2032
+ }
2033
+
2034
+ base--;
2035
+ }
2036
+ if (base > texture.streaming) {
2037
+ throw new TextureBindingError("mipmaps");
2038
+ }
2039
+
2040
+ return base;
2041
+ }
2042
+
1855
2043
  private uniforms(
1856
2044
  locations: (WebGLUniformLocation | null)[],
1857
2045
  bank: Constants,
@@ -1899,12 +2087,12 @@ export class WebGLContext3D implements Context3D {
1899
2087
  gl.blitFramebuffer(
1900
2088
  0,
1901
2089
  0,
1902
- this.width,
1903
- this.height,
2090
+ this.drawnWidth,
2091
+ this.drawnHeight,
1904
2092
  0,
1905
2093
  0,
1906
- this.width,
1907
- this.height,
2094
+ this.drawnWidth,
2095
+ this.drawnHeight,
1908
2096
  gl.COLOR_BUFFER_BIT,
1909
2097
  gl.NEAREST,
1910
2098
  );
@@ -1922,12 +2110,46 @@ export class WebGLContext3D implements Context3D {
1922
2110
  this.target(true);
1923
2111
  this.resolve();
1924
2112
  const gl = this.gl;
2113
+ const scaleX = this.drawnWidth / this.width;
2114
+ const scaleY = this.drawnHeight / this.height;
2115
+ const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
2116
+ const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
2117
+ if (this.pixels.length < drawnWidth * drawnHeight * 4) {
2118
+ this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
2119
+ }
2120
+ if (this.topDown.length < width * height * 4) {
2121
+ this.topDown = new Uint8Array(width * height * 4);
2122
+ }
2123
+
1925
2124
  // GL's rows run bottom-up, so the top-left region starts height rows below the top.
1926
- gl.readPixels(0, this.height - height, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
2125
+ gl.readPixels(
2126
+ 0,
2127
+ this.drawnHeight - drawnHeight,
2128
+ drawnWidth,
2129
+ drawnHeight,
2130
+ gl.RGBA,
2131
+ gl.UNSIGNED_BYTE,
2132
+ this.pixels,
2133
+ );
1927
2134
  const row = width * 4;
2135
+ if (drawnWidth === width && drawnHeight === height) {
2136
+ for (let y = 0; y < height; y++) {
2137
+ const from = (height - y - 1) * row;
2138
+ this.topDown.set(this.pixels.subarray(from, from + row), y * row);
2139
+ }
2140
+
2141
+ return this.topDown;
2142
+ }
2143
+
2144
+ // Storage smaller than the back buffer: each pixel is the stored one it falls on.
2145
+ const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
2146
+ const target = new Uint32Array(this.topDown.buffer, 0, width * height);
1928
2147
  for (let y = 0; y < height; y++) {
1929
- const from = (height - y - 1) * row;
1930
- this.topDown.set(this.pixels.subarray(from, from + row), y * row);
2148
+ const from =
2149
+ (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
2150
+ for (let x = 0; x < width; x++) {
2151
+ target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
2152
+ }
1931
2153
  }
1932
2154
 
1933
2155
  return this.topDown;
@@ -1942,15 +2164,16 @@ export class WebGLContext3D implements Context3D {
1942
2164
  if (this.scissor) {
1943
2165
  gl.disable(gl.SCISSOR_TEST);
1944
2166
  }
2167
+ // To the drawing buffer the browser gave, which it may have made smaller than the canvas.
1945
2168
  gl.blitFramebuffer(
1946
2169
  0,
1947
2170
  0,
1948
- this.width,
1949
- this.height,
2171
+ this.drawnWidth,
2172
+ this.drawnHeight,
1950
2173
  0,
1951
2174
  0,
1952
- this.width,
1953
- this.height,
2175
+ gl.drawingBufferWidth,
2176
+ gl.drawingBufferHeight,
1954
2177
  gl.COLOR_BUFFER_BIT,
1955
2178
  gl.NEAREST,
1956
2179
  );
@@ -20,6 +20,8 @@ export interface TextureState {
20
20
  multisample: MultisampleTarget | null;
21
21
  samples: number;
22
22
  antiAlias: number;
23
+ /** TEXTURE_BASE_LEVEL: a streaming texture's finest level whole to the smallest. */
24
+ base: number;
23
25
  }
24
26
 
25
27
  /** The staging array, made base-sized at its first use: a texture given only blocks on S3TC has none. */