@swf2es/context3d 0.1.19 → 0.1.20
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/README.md +112 -22
- package/dist/agal.d.ts +11 -2
- package/dist/agal.d.ts.map +1 -1
- package/dist/agal.js +318 -47
- package/dist/agal.js.map +1 -1
- package/dist/backend.d.ts +50 -3
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +74 -2
- package/dist/backend.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/webgl/context.d.ts +24 -3
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +184 -43
- package/dist/webgl/context.js.map +1 -1
- package/dist/webgl/programs.d.ts +4 -1
- package/dist/webgl/programs.d.ts.map +1 -1
- package/dist/webgl/programs.js +7 -6
- package/dist/webgl/programs.js.map +1 -1
- package/dist/webgl/targets.d.ts +3 -1
- package/dist/webgl/targets.d.ts.map +1 -1
- package/dist/webgl/targets.js +5 -3
- package/dist/webgl/targets.js.map +1 -1
- package/dist/webgl/textures.d.ts +9 -1
- package/dist/webgl/textures.d.ts.map +1 -1
- package/dist/webgl/textures.js +161 -7
- package/dist/webgl/textures.js.map +1 -1
- package/package.json +1 -1
- package/src/agal.ts +375 -51
- package/src/backend.ts +113 -3
- package/src/errors.ts +27 -1
- package/src/index.ts +12 -1
- package/src/webgl/context.ts +220 -40
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +183 -9
package/src/webgl/context.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { INDIRECT_BANK, type ProgramType } from "../agal.js";
|
|
2
2
|
import {
|
|
3
3
|
type BlendFactor,
|
|
4
4
|
type Buffer3D,
|
|
5
5
|
type CompareMode,
|
|
6
6
|
type Context3D,
|
|
7
7
|
type CullFace,
|
|
8
|
+
compressedFormat,
|
|
9
|
+
FRAGMENT_CONSTANTS,
|
|
8
10
|
MOST_BACK_BUFFER,
|
|
11
|
+
MOST_CUBE,
|
|
12
|
+
type Profile,
|
|
9
13
|
type Program3D,
|
|
14
|
+
profileLimits,
|
|
10
15
|
SAMPLER_FILTERS,
|
|
11
16
|
SAMPLER_MIPS,
|
|
17
|
+
SAMPLER_SLOTS,
|
|
12
18
|
SAMPLER_WRAPS,
|
|
13
19
|
type SamplerFilter,
|
|
14
20
|
type SamplerMip,
|
|
@@ -16,10 +22,13 @@ import {
|
|
|
16
22
|
ScissorBoundsError,
|
|
17
23
|
type StencilAction,
|
|
18
24
|
screenPixels,
|
|
25
|
+
TEXEL_BYTES,
|
|
19
26
|
type Texture3D,
|
|
20
27
|
TextureBindingError,
|
|
21
28
|
type TextureFormat,
|
|
29
|
+
VERTEX_CONSTANTS,
|
|
22
30
|
VERTEX_FORMATS,
|
|
31
|
+
VERTEX_SLOTS,
|
|
23
32
|
type VertexFormat,
|
|
24
33
|
VertexOffsetError,
|
|
25
34
|
} from "../backend.js";
|
|
@@ -56,6 +65,7 @@ import {
|
|
|
56
65
|
targetFramebuffer,
|
|
57
66
|
} from "./targets.js";
|
|
58
67
|
import {
|
|
68
|
+
storageFormat,
|
|
59
69
|
type TextureState,
|
|
60
70
|
uploadCompressedLevel,
|
|
61
71
|
uploadTexture,
|
|
@@ -68,8 +78,26 @@ const SAMPLER_FORMATS: Record<TextureFormat, number> = {
|
|
|
68
78
|
bgra: 0,
|
|
69
79
|
compressed: 1,
|
|
70
80
|
compressedAlpha: 2,
|
|
81
|
+
bgrPacked565: 0,
|
|
82
|
+
bgraPacked4444: 0,
|
|
83
|
+
rgbaHalfFloat: 0,
|
|
71
84
|
};
|
|
72
85
|
|
|
86
|
+
/**
|
|
87
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
88
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
89
|
+
*/
|
|
90
|
+
function clearChannel(value: number): number {
|
|
91
|
+
if (!(value > 0)) {
|
|
92
|
+
return value < 0 ? 0 : value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
99
|
+
const WRAPS = [0, 3, 2, 1];
|
|
100
|
+
|
|
73
101
|
interface Constants {
|
|
74
102
|
data: Float32Array;
|
|
75
103
|
bits: Uint32Array;
|
|
@@ -105,6 +133,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
105
133
|
private gpuMost: Int32Array | null = null;
|
|
106
134
|
disposed = false;
|
|
107
135
|
samplerFormats = true;
|
|
136
|
+
profile: Profile = "baseline";
|
|
108
137
|
private readonly buffers = new Map<Buffer3D, BufferState>();
|
|
109
138
|
private vertexBuffers = 0;
|
|
110
139
|
private bufferBytes = 0;
|
|
@@ -123,7 +152,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
123
152
|
private renderTexture: Texture3D | null = null;
|
|
124
153
|
private renderDepth = false;
|
|
125
154
|
private renderFace = 0;
|
|
126
|
-
|
|
155
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
156
|
+
private readonly outputs: (Texture3D | null)[] = [null, null, null];
|
|
157
|
+
private readonly outputFaces = new Uint8Array(3);
|
|
158
|
+
private outputsChanged = false;
|
|
159
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
160
|
+
private outputsBound = false;
|
|
161
|
+
private floatTargets: boolean | undefined;
|
|
162
|
+
private readonly textureSlots: (Texture3D | null)[] = new Array(SAMPLER_SLOTS).fill(null);
|
|
127
163
|
private textureMask = 0;
|
|
128
164
|
private readonly samplers: (WebGLSampler | null)[] = new Array(96).fill(null);
|
|
129
165
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -131,10 +167,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
131
167
|
private anisotropy: EXT_texture_filter_anisotropic | null | undefined;
|
|
132
168
|
private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
|
|
133
169
|
private maxAnisotropy = 1;
|
|
134
|
-
private readonly vertices: (VertexBinding | null)[] = new Array(
|
|
170
|
+
private readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
|
|
135
171
|
private enabledAttributes = 0;
|
|
136
172
|
private indexBuffer: WebGLBuffer | null = null;
|
|
137
|
-
private readonly texturesApplied = new Uint8Array(
|
|
173
|
+
private readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
138
174
|
private selected: Program3D | null = null;
|
|
139
175
|
private framebuffer: WebGLFramebuffer | null = null;
|
|
140
176
|
private texture: WebGLTexture | null = null;
|
|
@@ -164,8 +200,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
164
200
|
private scissorRight = 0;
|
|
165
201
|
private scissorBottom = 0;
|
|
166
202
|
private scissorOutside = false;
|
|
167
|
-
private readonly vertexConstants = constants(
|
|
168
|
-
private readonly fragmentConstants = constants(
|
|
203
|
+
private readonly vertexConstants = constants(VERTEX_CONSTANTS);
|
|
204
|
+
private readonly fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
169
205
|
private pixels = new Uint8Array(0);
|
|
170
206
|
private topDown = new Uint8Array(0);
|
|
171
207
|
private readonly vao: WebGLVertexArrayObject;
|
|
@@ -236,6 +272,9 @@ export class WebGLContext3D implements Context3D {
|
|
|
236
272
|
}
|
|
237
273
|
}
|
|
238
274
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
275
|
+
if (this.outputsChanged || state?.outputs) {
|
|
276
|
+
this.attachOutputs(state);
|
|
277
|
+
}
|
|
239
278
|
this.drawState.viewport(
|
|
240
279
|
0,
|
|
241
280
|
0,
|
|
@@ -474,7 +513,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
474
513
|
const gl = this.gl;
|
|
475
514
|
let bits = 0;
|
|
476
515
|
if (mask & 1) {
|
|
477
|
-
|
|
516
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
517
|
+
gl.clearColor(
|
|
518
|
+
clearChannel(red),
|
|
519
|
+
clearChannel(green),
|
|
520
|
+
clearChannel(blue),
|
|
521
|
+
clearChannel(alpha),
|
|
522
|
+
);
|
|
478
523
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
479
524
|
this.changedTarget();
|
|
480
525
|
}
|
|
@@ -891,6 +936,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
891
936
|
attributeMask: 0,
|
|
892
937
|
vertex: [],
|
|
893
938
|
vertexBank: null,
|
|
939
|
+
outputs: 1,
|
|
894
940
|
fragment: [],
|
|
895
941
|
samplers: [],
|
|
896
942
|
samplerMask: 0,
|
|
@@ -902,11 +948,17 @@ export class WebGLContext3D implements Context3D {
|
|
|
902
948
|
vertexVersion: -1,
|
|
903
949
|
};
|
|
904
950
|
const program: Program3D = {
|
|
951
|
+
get outputs() {
|
|
952
|
+
return state.outputs;
|
|
953
|
+
},
|
|
954
|
+
get attributes() {
|
|
955
|
+
return state.attributeMask;
|
|
956
|
+
},
|
|
905
957
|
upload: (vertex, fragment) => {
|
|
906
958
|
this.alive();
|
|
907
959
|
this.program(program);
|
|
908
960
|
this.drawState.forget();
|
|
909
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
961
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
910
962
|
|
|
911
963
|
if (this.selected === program) {
|
|
912
964
|
this.setProgram(program);
|
|
@@ -968,8 +1020,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
968
1020
|
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
969
1021
|
}
|
|
970
1022
|
|
|
971
|
-
createRectangleTexture(width: number, height: number): Texture3D {
|
|
972
|
-
return this.allocateTexture(width, height, false, true,
|
|
1023
|
+
createRectangleTexture(width: number, height: number, format: TextureFormat = "bgra"): Texture3D {
|
|
1024
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
973
1025
|
}
|
|
974
1026
|
|
|
975
1027
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
@@ -977,7 +1029,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
977
1029
|
if (this.s3tc === undefined) {
|
|
978
1030
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
979
1031
|
}
|
|
980
|
-
if (!this.s3tc || format
|
|
1032
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
981
1033
|
return 0;
|
|
982
1034
|
}
|
|
983
1035
|
|
|
@@ -1003,7 +1055,9 @@ export class WebGLContext3D implements Context3D {
|
|
|
1003
1055
|
for (let level = 0; level < levels; level++) {
|
|
1004
1056
|
const w = Math.max(1, width >> level);
|
|
1005
1057
|
const h = Math.max(1, height >> level);
|
|
1006
|
-
size +=
|
|
1058
|
+
size += compressedFormat(format)
|
|
1059
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
1060
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
1007
1061
|
}
|
|
1008
1062
|
|
|
1009
1063
|
return size * faces;
|
|
@@ -1018,12 +1072,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1018
1072
|
streaming = 0,
|
|
1019
1073
|
): Texture3D {
|
|
1020
1074
|
this.alive();
|
|
1021
|
-
|
|
1022
|
-
|
|
1075
|
+
const limits = profileLimits(this.profile);
|
|
1076
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
1077
|
+
range(1, width - 1, most);
|
|
1078
|
+
range(1, height - 1, most);
|
|
1023
1079
|
if (cube && width !== height) {
|
|
1024
1080
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
1025
1081
|
}
|
|
1026
|
-
if (cube && width >
|
|
1082
|
+
if (cube && width > MOST_CUBE) {
|
|
1027
1083
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
1028
1084
|
}
|
|
1029
1085
|
|
|
@@ -1036,15 +1092,15 @@ export class WebGLContext3D implements Context3D {
|
|
|
1036
1092
|
"Context3D texture size must be a power of two",
|
|
1037
1093
|
);
|
|
1038
1094
|
}
|
|
1039
|
-
if (format
|
|
1095
|
+
if (compressedFormat(format) && rectangle) {
|
|
1040
1096
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
1041
1097
|
}
|
|
1042
|
-
if (format
|
|
1098
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
1043
1099
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
1044
1100
|
}
|
|
1045
1101
|
|
|
1046
1102
|
const gl = this.gl;
|
|
1047
|
-
const storage = this.s3tcFormat(format) || gl
|
|
1103
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
1048
1104
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
1049
1105
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
1050
1106
|
const countLimit = cube
|
|
@@ -1060,7 +1116,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
1060
1116
|
// AIR reserves half the base size for mips, independently of the exact
|
|
1061
1117
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
1062
1118
|
const quotaBytes =
|
|
1063
|
-
(width * height * faces * (rectangle ? 4 : 6)) /
|
|
1119
|
+
(width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
1120
|
+
4 /
|
|
1064
1121
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
1065
1122
|
if (
|
|
1066
1123
|
this.textures.size >= this.limits.maxTextures ||
|
|
@@ -1095,6 +1152,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1095
1152
|
multisample: null,
|
|
1096
1153
|
samples: 0,
|
|
1097
1154
|
antiAlias: 0,
|
|
1155
|
+
outputs: 0,
|
|
1098
1156
|
base: 0,
|
|
1099
1157
|
};
|
|
1100
1158
|
const texture: Texture3D = {
|
|
@@ -1137,10 +1195,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
1137
1195
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
1138
1196
|
this.alive();
|
|
1139
1197
|
this.textureState(texture);
|
|
1140
|
-
if (format
|
|
1198
|
+
if (!compressedFormat(format)) {
|
|
1141
1199
|
throw new Context3DError(
|
|
1142
1200
|
"textureUpload",
|
|
1143
|
-
"Context3D experiment:
|
|
1201
|
+
"Context3D experiment: uncompressed texture given blocks",
|
|
1144
1202
|
);
|
|
1145
1203
|
}
|
|
1146
1204
|
|
|
@@ -1180,6 +1238,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1180
1238
|
state.mipFramebuffer = null;
|
|
1181
1239
|
state.depthBuffer = null;
|
|
1182
1240
|
|
|
1241
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1242
|
+
if (this.outputs[i] === texture) {
|
|
1243
|
+
this.outputs[i] = null;
|
|
1244
|
+
this.outputsChanged = true;
|
|
1245
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1183
1248
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
1184
1249
|
if (this.textureSlots[i] === texture) {
|
|
1185
1250
|
this.texturesApplied[i] = 0;
|
|
@@ -1335,6 +1400,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1335
1400
|
depth,
|
|
1336
1401
|
this.depthWrite,
|
|
1337
1402
|
this.stencilWrite,
|
|
1403
|
+
state.storage,
|
|
1338
1404
|
);
|
|
1339
1405
|
if (same) {
|
|
1340
1406
|
if (newDepth) {
|
|
@@ -1370,12 +1436,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1370
1436
|
const gl = this.gl;
|
|
1371
1437
|
if (texture) {
|
|
1372
1438
|
this.textureState(texture);
|
|
1373
|
-
if (texture.format
|
|
1439
|
+
if (compressedFormat(texture.format)) {
|
|
1374
1440
|
throw new Context3DError(
|
|
1375
1441
|
"operation",
|
|
1376
1442
|
"Context3D experiment: compressed textures are no render targets",
|
|
1377
1443
|
);
|
|
1378
1444
|
}
|
|
1445
|
+
this.renderFormat(texture.format);
|
|
1379
1446
|
}
|
|
1380
1447
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
1381
1448
|
this.finishTarget();
|
|
@@ -1521,6 +1588,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1521
1588
|
}
|
|
1522
1589
|
|
|
1523
1590
|
private changedTarget(): void {
|
|
1591
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
1592
|
+
const output = this.outputs[i];
|
|
1593
|
+
if (output) {
|
|
1594
|
+
this.textureState(output).dirty = true;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1524
1597
|
if (this.renderTexture) {
|
|
1525
1598
|
const state = this.textureState(this.renderTexture);
|
|
1526
1599
|
state.dirty = true;
|
|
@@ -1537,22 +1610,34 @@ export class WebGLContext3D implements Context3D {
|
|
|
1537
1610
|
}
|
|
1538
1611
|
|
|
1539
1612
|
private finishTarget(): void {
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
1613
|
+
if (this.renderTexture) {
|
|
1614
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1615
|
+
}
|
|
1616
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1617
|
+
const output = this.outputs[i];
|
|
1618
|
+
if (output) {
|
|
1619
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1620
|
+
}
|
|
1542
1621
|
}
|
|
1543
|
-
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1625
|
+
private finishTexture(texture: Texture3D, renderFace: number): void {
|
|
1544
1626
|
const state = this.textureState(texture);
|
|
1545
1627
|
if (state.dirty) {
|
|
1546
1628
|
const gl = this.gl;
|
|
1547
1629
|
this.drawState.forget();
|
|
1548
1630
|
this.texturesApplied[0] = 0;
|
|
1549
|
-
resolveTarget(this.gl, texture, state,
|
|
1631
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1550
1632
|
gl.activeTexture(gl.TEXTURE0);
|
|
1551
1633
|
if (texture.cube) {
|
|
1634
|
+
// An extra colour output may never have been a target of its own.
|
|
1635
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1636
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1552
1637
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1553
1638
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1554
1639
|
gl.disable(gl.SCISSOR_TEST);
|
|
1555
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1640
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1556
1641
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1557
1642
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1558
1643
|
if (state.depthAttached) {
|
|
@@ -1614,14 +1699,99 @@ export class WebGLContext3D implements Context3D {
|
|
|
1614
1699
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1615
1700
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1616
1701
|
}
|
|
1617
|
-
state.uploaded[
|
|
1702
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1618
1703
|
state.dirty = false;
|
|
1619
1704
|
}
|
|
1620
1705
|
}
|
|
1621
1706
|
|
|
1707
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1708
|
+
private renderFormat(format: TextureFormat): void {
|
|
1709
|
+
if (format !== "rgbaHalfFloat") {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1714
|
+
if (!this.floatTargets) {
|
|
1715
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
setColorOutput(index: number, texture: Texture3D | null, face = 0): void {
|
|
1720
|
+
this.alive();
|
|
1721
|
+
range(index - 1, 1, this.outputs.length);
|
|
1722
|
+
if (texture) {
|
|
1723
|
+
this.textureState(texture);
|
|
1724
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1725
|
+
if (compressedFormat(texture.format)) {
|
|
1726
|
+
throw new Context3DError(
|
|
1727
|
+
"operation",
|
|
1728
|
+
"Context3D experiment: compressed textures are no render targets",
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
this.renderFormat(texture.format);
|
|
1732
|
+
}
|
|
1733
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const previous = this.outputs[index - 1];
|
|
1738
|
+
if (previous) {
|
|
1739
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1740
|
+
}
|
|
1741
|
+
this.outputs[index - 1] = texture;
|
|
1742
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1743
|
+
this.outputsChanged = true;
|
|
1744
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1749
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1750
|
+
* before they draw.
|
|
1751
|
+
*/
|
|
1752
|
+
private attachOutputs(state: TextureState | null): void {
|
|
1753
|
+
const want = state ? this.outputMask() : 0;
|
|
1754
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (want && state.samples) {
|
|
1758
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const gl = this.gl;
|
|
1762
|
+
const buffers: number[] = [gl.COLOR_ATTACHMENT0];
|
|
1763
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1764
|
+
const output = this.outputs[i];
|
|
1765
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1766
|
+
gl.framebufferTexture2D(
|
|
1767
|
+
gl.FRAMEBUFFER,
|
|
1768
|
+
gl.COLOR_ATTACHMENT1 + i,
|
|
1769
|
+
output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D,
|
|
1770
|
+
handle,
|
|
1771
|
+
0,
|
|
1772
|
+
);
|
|
1773
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1774
|
+
}
|
|
1775
|
+
gl.drawBuffers(buffers);
|
|
1776
|
+
state.outputs = want;
|
|
1777
|
+
this.outputsChanged = false;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1781
|
+
private outputMask(): number {
|
|
1782
|
+
let mask = 0;
|
|
1783
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1784
|
+
if (this.outputs[i]) {
|
|
1785
|
+
mask |= 1 << i;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
return mask;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1622
1792
|
setTexture(index: number, texture: Texture3D | null): void {
|
|
1623
1793
|
this.alive();
|
|
1624
|
-
range(index, 1,
|
|
1794
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1625
1795
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1626
1796
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1627
1797
|
}
|
|
@@ -1726,7 +1896,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1726
1896
|
if (!source.ignore) {
|
|
1727
1897
|
this.setSamplerState(
|
|
1728
1898
|
source.index,
|
|
1729
|
-
|
|
1899
|
+
WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5),
|
|
1730
1900
|
);
|
|
1731
1901
|
}
|
|
1732
1902
|
}
|
|
@@ -1734,7 +1904,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1734
1904
|
|
|
1735
1905
|
setVertex(index: number, buffer: Buffer3D | null, offset: number, format: VertexFormat): void {
|
|
1736
1906
|
this.alive();
|
|
1737
|
-
range(index, 1,
|
|
1907
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1738
1908
|
if (buffer === null) {
|
|
1739
1909
|
this.vertices[index] = null;
|
|
1740
1910
|
return;
|
|
@@ -1857,13 +2027,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1857
2027
|
|
|
1858
2028
|
const gl = this.gl;
|
|
1859
2029
|
this.drawState.bindVertexArray(this.vao);
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2030
|
+
// The streams enabled that the program does not read, highest first.
|
|
2031
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
2032
|
+
while (stale) {
|
|
2033
|
+
const i = 31 - Math.clz32(stale);
|
|
2034
|
+
gl.disableVertexAttribArray(i);
|
|
2035
|
+
stale &= ~(1 << i);
|
|
1866
2036
|
}
|
|
2037
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1867
2038
|
|
|
1868
2039
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1869
2040
|
const slot = state.attributes[i];
|
|
@@ -1909,7 +2080,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1909
2080
|
const binding = state.samplers[i];
|
|
1910
2081
|
const slot = binding.source.index;
|
|
1911
2082
|
const texture = this.textureSlots[slot];
|
|
1912
|
-
if (
|
|
2083
|
+
if (
|
|
2084
|
+
texture &&
|
|
2085
|
+
(texture === this.renderTexture ||
|
|
2086
|
+
(this.outputsBound &&
|
|
2087
|
+
(texture === this.outputs[0] ||
|
|
2088
|
+
texture === this.outputs[1] ||
|
|
2089
|
+
texture === this.outputs[2])))
|
|
2090
|
+
) {
|
|
1913
2091
|
throw new TextureBindingError("feedback");
|
|
1914
2092
|
}
|
|
1915
2093
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1976,10 +2154,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1976
2154
|
|
|
1977
2155
|
if (state.vertexBank) {
|
|
1978
2156
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1979
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
2157
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1980
2158
|
state.vertexVersion = this.vertexConstants.version;
|
|
1981
2159
|
}
|
|
1982
|
-
}
|
|
2160
|
+
}
|
|
2161
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
2162
|
+
if (state.vertex.length) {
|
|
1983
2163
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1984
2164
|
}
|
|
1985
2165
|
this.uniforms(
|
package/src/webgl/programs.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SamplerSource, translate } from "../agal.js";
|
|
2
|
+
import type { Profile } from "../backend.js";
|
|
2
3
|
import { linkProgram as link, ShaderError } from "../device/programs.js";
|
|
3
4
|
import { Context3DError } from "../errors.js";
|
|
4
5
|
|
|
@@ -10,6 +11,8 @@ export interface ProgramState {
|
|
|
10
11
|
attributeMask: number;
|
|
11
12
|
vertex: (WebGLUniformLocation | null)[];
|
|
12
13
|
vertexBank: WebGLUniformLocation | null;
|
|
14
|
+
/** The colour outputs its fragment program writes. */
|
|
15
|
+
outputs: number;
|
|
13
16
|
fragment: (WebGLUniformLocation | null)[];
|
|
14
17
|
samplers: { source: SamplerSource; location: WebGLUniformLocation | null }[];
|
|
15
18
|
samplerMask: number;
|
|
@@ -32,19 +35,20 @@ export function uploadProgram(
|
|
|
32
35
|
state: ProgramState,
|
|
33
36
|
vertex: Uint8Array,
|
|
34
37
|
fragment: Uint8Array,
|
|
38
|
+
profile: Profile,
|
|
35
39
|
): void {
|
|
36
40
|
// Translate both before allocating anything on the GPU.
|
|
37
|
-
const vs = translate(vertex, "vertex");
|
|
38
|
-
const fs = translate(fragment, "fragment");
|
|
39
|
-
for (let i = 0; i <
|
|
40
|
-
if (fs.varyings[i] & ~vs.varyings[i]) {
|
|
41
|
+
const vs = translate(vertex, "vertex", profile);
|
|
42
|
+
const fs = translate(fragment, "fragment", profile);
|
|
43
|
+
for (let i = 0; i < fs.varyings.length; i++) {
|
|
44
|
+
if (fs.varyings[i] & ~(vs.varyings[i] ?? 0)) {
|
|
41
45
|
throw new Context3DError(
|
|
42
46
|
"agalVaryingMissing",
|
|
43
47
|
"Fragment program reads an unwritten varying component",
|
|
44
48
|
);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
for (let i = 0; i <
|
|
51
|
+
for (let i = 0; i < vs.varyings.length; i++) {
|
|
48
52
|
if (vs.varyings[i] && vs.varyings[i] !== 15) {
|
|
49
53
|
throw new Context3DError(
|
|
50
54
|
"agalVaryingPartial",
|
|
@@ -70,6 +74,7 @@ export function uploadProgram(
|
|
|
70
74
|
|
|
71
75
|
state.vertex = [];
|
|
72
76
|
state.vertexBank = vs.indirect ? gl.getUniformLocation(handle, "vc[0]") : null;
|
|
77
|
+
state.outputs = fs.outputs;
|
|
73
78
|
state.fragment = [];
|
|
74
79
|
state.samplers = [];
|
|
75
80
|
state.samplerMask = 0;
|
package/src/webgl/targets.ts
CHANGED
|
@@ -107,6 +107,8 @@ export function allocateMultisample(
|
|
|
107
107
|
depth: boolean,
|
|
108
108
|
depthWrite: boolean,
|
|
109
109
|
stencilWrite: number,
|
|
110
|
+
/** The texture's internal format, which its samples resolve into. */
|
|
111
|
+
storage: number = gl.RGBA8,
|
|
110
112
|
): MultisampleTarget {
|
|
111
113
|
const same = old?.samples === samples;
|
|
112
114
|
const newDepth = depth && (!same || !old.depth);
|
|
@@ -125,7 +127,7 @@ export function allocateMultisample(
|
|
|
125
127
|
if (texture.cube && !same) {
|
|
126
128
|
gl.activeTexture(gl.TEXTURE0);
|
|
127
129
|
gl.bindTexture(gl.TEXTURE_2D, resolved);
|
|
128
|
-
gl.texStorage2D(gl.TEXTURE_2D, 1,
|
|
130
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, storage, texture.width, texture.height);
|
|
129
131
|
gl.bindFramebuffer(gl.FRAMEBUFFER, resolve);
|
|
130
132
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resolved, 0);
|
|
131
133
|
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
@@ -148,7 +150,7 @@ export function allocateMultisample(
|
|
|
148
150
|
gl.renderbufferStorageMultisample(
|
|
149
151
|
gl.RENDERBUFFER,
|
|
150
152
|
samples,
|
|
151
|
-
|
|
153
|
+
storage,
|
|
152
154
|
texture.width,
|
|
153
155
|
texture.height,
|
|
154
156
|
);
|