@swf2es/context3d 0.1.18 → 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 +80 -7
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +91 -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 +51 -6
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +379 -71
- 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 +11 -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 +164 -7
- package/src/errors.ts +27 -1
- package/src/index.ts +14 -1
- package/src/webgl/context.ts +476 -73
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +185 -9
package/src/agal.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// AGAL 1, 2 and 3 to GLSL ES 3.00, with AIR's validation.
|
|
2
2
|
// Format: https://github.com/adobe/glsl2agal/blob/master/agalassembler/agal.h
|
|
3
|
-
|
|
3
|
+
// Each version's registers and rules are as AIR 51 enforces them (tests/context3d/AGALProfiles.as).
|
|
4
|
+
import { type Profile, profileLimits, SamplerBiasError } from "./backend.js";
|
|
4
5
|
import { Context3DError } from "./errors.js";
|
|
5
6
|
|
|
6
7
|
export type ProgramType = "vertex" | "fragment";
|
|
@@ -10,7 +11,8 @@ export interface SamplerSource {
|
|
|
10
11
|
/** What the sampler reads: 0 RGBA, 1 DXT1 ("compressed"), 2 DXT5 ("compressedAlpha"). */
|
|
11
12
|
format: number;
|
|
12
13
|
filter: number;
|
|
13
|
-
|
|
14
|
+
/** AGAL's wrap: 0 clamp, 1 repeat, 2 clamp_u_repeat_v, 3 repeat_u_clamp_v. */
|
|
15
|
+
wrap: number;
|
|
14
16
|
mip: number;
|
|
15
17
|
cube: boolean;
|
|
16
18
|
ignore: boolean;
|
|
@@ -25,27 +27,107 @@ export interface ShaderSource {
|
|
|
25
27
|
constants: number[];
|
|
26
28
|
indirect: boolean;
|
|
27
29
|
samplers: SamplerSource[];
|
|
30
|
+
/** Colour outputs a fragment program writes, oc0 up: 1 to 4. */
|
|
31
|
+
outputs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The registers each AGAL version has, and how long its programs may be. */
|
|
35
|
+
interface Registers {
|
|
36
|
+
attributes: number;
|
|
37
|
+
vertexConstants: number;
|
|
38
|
+
fragmentConstants: number;
|
|
39
|
+
temporaries: number;
|
|
40
|
+
varyings: number;
|
|
41
|
+
samplers: number;
|
|
42
|
+
outputs: number;
|
|
43
|
+
depth: boolean;
|
|
44
|
+
tokens: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function registers(version: number, profile: Profile): Registers {
|
|
48
|
+
if (version === 1) {
|
|
49
|
+
return {
|
|
50
|
+
attributes: 8,
|
|
51
|
+
vertexConstants: 128,
|
|
52
|
+
fragmentConstants: 28,
|
|
53
|
+
temporaries: profile === "baselineConstrained" ? 7 : 8,
|
|
54
|
+
varyings: 8,
|
|
55
|
+
samplers: 8,
|
|
56
|
+
outputs: 1,
|
|
57
|
+
depth: false,
|
|
58
|
+
tokens: 200,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const constrained = profile === "standardConstrained";
|
|
63
|
+
return {
|
|
64
|
+
attributes: version === 2 ? 8 : 16,
|
|
65
|
+
vertexConstants: 250,
|
|
66
|
+
fragmentConstants: version === 2 ? 64 : 200,
|
|
67
|
+
temporaries: 26,
|
|
68
|
+
varyings: constrained ? 8 : 10,
|
|
69
|
+
samplers: constrained ? 8 : 16,
|
|
70
|
+
outputs: constrained ? 1 : 4,
|
|
71
|
+
depth: !constrained,
|
|
72
|
+
tokens: version === 2 ? 1024 : 2048,
|
|
73
|
+
};
|
|
28
74
|
}
|
|
29
75
|
|
|
30
76
|
const SUPPORTED = new Set([
|
|
31
77
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
|
32
|
-
0x15, 0x16, 0x17, 0x18, 0x19, 0x27, 0x28, 0x29, 0x2a, 0x2c,
|
|
78
|
+
0x15, 0x16, 0x17, 0x18, 0x19, 0x27, 0x28, 0x29, 0x2a, 0x2c, 0x2d,
|
|
79
|
+
]);
|
|
80
|
+
/** ddx, ddy and the conditionals: ife, ine, ifg, ifl, els and eif. */
|
|
81
|
+
const VERSION2 = new Set([0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21]);
|
|
82
|
+
const UNARY = new Set([
|
|
83
|
+
0, 5, 8, 9, 0x0a, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x14, 0x15, 0x16, 0x1a, 0x1b, 0x27,
|
|
33
84
|
]);
|
|
34
|
-
const
|
|
85
|
+
const COMPARISONS = ["==", "!=", ">", "<"];
|
|
86
|
+
/**
|
|
87
|
+
* The vertex constants an indirect read reaches: AIR's reach the first 128 in AGAL 2 and 3
|
|
88
|
+
* too, and read 0 past them; the rest only direct reads reach.
|
|
89
|
+
*/
|
|
90
|
+
export const INDIRECT_BANK = 128;
|
|
91
|
+
/** The instructions a baselineConstrained fragment program may have. */
|
|
92
|
+
const CONSTRAINED_FRAGMENT = 96;
|
|
93
|
+
/** The deepest AIR nests conditionals. */
|
|
94
|
+
const NESTING = 4;
|
|
95
|
+
|
|
96
|
+
/** A conditional being translated: its predicate, and what was written before and in it. */
|
|
97
|
+
interface Block {
|
|
98
|
+
predicate: string;
|
|
99
|
+
condition: string;
|
|
100
|
+
parent: string;
|
|
101
|
+
otherwise: boolean;
|
|
102
|
+
instructions: number;
|
|
103
|
+
temporaries: Uint8Array;
|
|
104
|
+
varyings: Uint8Array;
|
|
105
|
+
}
|
|
35
106
|
|
|
36
|
-
export function translate(
|
|
107
|
+
export function translate(
|
|
108
|
+
bytes: Uint8Array,
|
|
109
|
+
stage: ProgramType,
|
|
110
|
+
profile: Profile = "baseline",
|
|
111
|
+
): ShaderSource {
|
|
37
112
|
const data = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
38
113
|
if (bytes.length < 31 || (bytes.length - 7) % 24 !== 0) {
|
|
39
114
|
throw new Context3DError("agalLength", "Invalid AGAL program length");
|
|
40
115
|
}
|
|
41
|
-
|
|
116
|
+
|
|
117
|
+
// AIR takes version 0 as 1.
|
|
118
|
+
const version = data.getUint32(1, true) || 1;
|
|
119
|
+
const limits = registers(Math.min(version, 3), profile);
|
|
120
|
+
if ((bytes.length - 7) / 24 > limits.tokens) {
|
|
42
121
|
throw new Context3DError("agalTokens", "Too many AGAL instructions");
|
|
43
122
|
}
|
|
44
123
|
if (bytes[0] !== 0xa0) {
|
|
45
124
|
throw new Context3DError("agalMagic", "Invalid AGAL magic byte");
|
|
46
125
|
}
|
|
47
|
-
if (
|
|
48
|
-
throw new Context3DError(
|
|
126
|
+
if (version > profileLimits(profile).agal) {
|
|
127
|
+
throw new Context3DError(
|
|
128
|
+
"agalProfileVersion",
|
|
129
|
+
`AGAL version ${version} is not valid in the ${profile} profile`,
|
|
130
|
+
);
|
|
49
131
|
}
|
|
50
132
|
if (bytes[5] !== 0xa1) {
|
|
51
133
|
throw new Context3DError("agalStageMarker", "Invalid AGAL stage marker");
|
|
@@ -73,6 +155,7 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
73
155
|
}
|
|
74
156
|
}
|
|
75
157
|
|
|
158
|
+
const bank = INDIRECT_BANK;
|
|
76
159
|
const names = new Map<string, string>();
|
|
77
160
|
const attributes = new Set<number>();
|
|
78
161
|
const constants = new Set<number>();
|
|
@@ -80,8 +163,11 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
80
163
|
const lines: string[] = [];
|
|
81
164
|
const components = "xyzw";
|
|
82
165
|
let output = 0;
|
|
83
|
-
|
|
84
|
-
|
|
166
|
+
let outputs = 0;
|
|
167
|
+
let temporaries: Uint8Array = new Uint8Array(limits.temporaries);
|
|
168
|
+
let varyings: Uint8Array = new Uint8Array(limits.varyings);
|
|
169
|
+
const blocks: Block[] = [];
|
|
170
|
+
let hoisted = 0;
|
|
85
171
|
const readTemporary = (index: number, mask: number): void => {
|
|
86
172
|
if (!temporaries[index]) {
|
|
87
173
|
throw new Context3DError("agalTemporaryRead", "AGAL temporary read before writing");
|
|
@@ -93,12 +179,20 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
93
179
|
);
|
|
94
180
|
}
|
|
95
181
|
};
|
|
182
|
+
const outputInBlock = (): void => {
|
|
183
|
+
if (blocks.length) {
|
|
184
|
+
throw new Context3DError(
|
|
185
|
+
"agalConditionOutput",
|
|
186
|
+
"AGAL outputs cannot be written in conditionals",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
96
190
|
const register = (type: number, index: number, write: boolean): string => {
|
|
97
191
|
let name: string;
|
|
98
192
|
let declaration: string;
|
|
99
193
|
switch (type) {
|
|
100
194
|
case 0:
|
|
101
|
-
if (write || stage !== "vertex" || index >=
|
|
195
|
+
if (write || stage !== "vertex" || index >= limits.attributes) {
|
|
102
196
|
const reason = write
|
|
103
197
|
? "agalAttributeWrite"
|
|
104
198
|
: stage !== "vertex"
|
|
@@ -111,15 +205,18 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
111
205
|
attributes.add(index);
|
|
112
206
|
break;
|
|
113
207
|
case 1:
|
|
114
|
-
if (
|
|
208
|
+
if (
|
|
209
|
+
write ||
|
|
210
|
+
index >= (stage === "vertex" ? limits.vertexConstants : limits.fragmentConstants)
|
|
211
|
+
) {
|
|
115
212
|
throw new Context3DError(
|
|
116
213
|
write ? "agalConstantWrite" : "agalConstantIndex",
|
|
117
214
|
"Invalid AGAL constant",
|
|
118
215
|
);
|
|
119
216
|
}
|
|
120
217
|
|
|
121
|
-
if (indirect && stage === "vertex") {
|
|
122
|
-
names.set("vc",
|
|
218
|
+
if (indirect && stage === "vertex" && index < bank) {
|
|
219
|
+
names.set("vc", `uniform vec4 vc[${bank}];`);
|
|
123
220
|
return `vc[${index}]`;
|
|
124
221
|
}
|
|
125
222
|
|
|
@@ -128,30 +225,67 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
128
225
|
constants.add(index);
|
|
129
226
|
break;
|
|
130
227
|
case 2:
|
|
131
|
-
if (index >=
|
|
228
|
+
if (index >= limits.temporaries) {
|
|
132
229
|
throw new Context3DError("agalTemporaryIndex", "Invalid AGAL temporary");
|
|
133
230
|
}
|
|
134
231
|
name = `t${index}`;
|
|
135
232
|
declaration = `vec4 ${name};`;
|
|
136
233
|
break;
|
|
137
234
|
case 3:
|
|
138
|
-
if (!write || index
|
|
235
|
+
if (!write || index >= (stage === "vertex" ? 1 : limits.outputs)) {
|
|
139
236
|
throw new Context3DError(
|
|
140
237
|
write ? "agalOutputIndex" : "agalOutputRead",
|
|
141
238
|
"Invalid AGAL output",
|
|
142
239
|
);
|
|
143
240
|
}
|
|
144
|
-
|
|
241
|
+
outputInBlock();
|
|
242
|
+
if (index === 0) {
|
|
243
|
+
return "result";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
name = `result${index}`;
|
|
247
|
+
declaration = `vec4 ${name} = vec4(0.0);`;
|
|
248
|
+
break;
|
|
145
249
|
case 4:
|
|
146
|
-
if (index >=
|
|
250
|
+
if (index >= limits.varyings || write !== (stage === "vertex")) {
|
|
147
251
|
throw new Context3DError(
|
|
148
|
-
index >=
|
|
252
|
+
index >= limits.varyings
|
|
253
|
+
? "agalVaryingIndex"
|
|
254
|
+
: write
|
|
255
|
+
? "agalVaryingWrite"
|
|
256
|
+
: "agalVaryingRead",
|
|
149
257
|
"Invalid AGAL varying",
|
|
150
258
|
);
|
|
151
259
|
}
|
|
152
260
|
name = `v${index}`;
|
|
153
261
|
declaration = `${stage === "vertex" ? "out" : "in"} vec4 ${name};`;
|
|
154
262
|
break;
|
|
263
|
+
case 6:
|
|
264
|
+
if (stage === "vertex") {
|
|
265
|
+
throw new Context3DError("agalDepthStage", "AGAL depth output in a vertex program");
|
|
266
|
+
}
|
|
267
|
+
if (!write) {
|
|
268
|
+
throw new Context3DError("agalRegister", "Unsupported AGAL register");
|
|
269
|
+
}
|
|
270
|
+
if (!limits.depth || index !== 0) {
|
|
271
|
+
throw new Context3DError("agalDepthIndex", "Invalid AGAL depth output");
|
|
272
|
+
}
|
|
273
|
+
outputInBlock();
|
|
274
|
+
name = "depth";
|
|
275
|
+
declaration = "vec4 depth = vec4(0.0);";
|
|
276
|
+
break;
|
|
277
|
+
case 7:
|
|
278
|
+
if (stage !== "vertex") {
|
|
279
|
+
throw new Context3DError("agalInstanceStage", "AGAL instance id in a fragment program");
|
|
280
|
+
}
|
|
281
|
+
if (write || index !== 0) {
|
|
282
|
+
throw new Context3DError("agalRegister", "Unsupported AGAL register");
|
|
283
|
+
}
|
|
284
|
+
// AIR reads the instance id from vertex stream 15, which a draw must bind (#3611).
|
|
285
|
+
name = "va15";
|
|
286
|
+
declaration = "layout(location = 15) in vec4 va15;";
|
|
287
|
+
attributes.add(15);
|
|
288
|
+
break;
|
|
155
289
|
default:
|
|
156
290
|
throw new Context3DError("agalRegister", "Unsupported AGAL register");
|
|
157
291
|
}
|
|
@@ -173,8 +307,7 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
173
307
|
high >>> 24 !== 128 ||
|
|
174
308
|
(high & 255) !== 1 ||
|
|
175
309
|
type > 2 ||
|
|
176
|
-
component > 3
|
|
177
|
-
add >= 128
|
|
310
|
+
component > 3
|
|
178
311
|
) {
|
|
179
312
|
throw new Context3DError("agalIndirect", "Unsupported AGAL indirect constant");
|
|
180
313
|
}
|
|
@@ -183,7 +316,7 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
183
316
|
if (type === 2) {
|
|
184
317
|
readTemporary(low & 65535, 1 << component);
|
|
185
318
|
}
|
|
186
|
-
names.set("vc",
|
|
319
|
+
names.set("vc", `uniform vec4 vc[${bank}];`);
|
|
187
320
|
name = `readConstant(${index}.${components[component]}, ${add + row}.0)`;
|
|
188
321
|
} else {
|
|
189
322
|
if (high >>> 8 || ((low >>> 16) & 255) !== 0) {
|
|
@@ -214,24 +347,119 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
214
347
|
return `${name}.${lanes}`;
|
|
215
348
|
};
|
|
216
349
|
|
|
350
|
+
const scalar = (offset: number): boolean => {
|
|
351
|
+
const swizzle = data.getUint32(offset, true) >>> 24;
|
|
352
|
+
return swizzle === (swizzle & 3) * 0x55;
|
|
353
|
+
};
|
|
354
|
+
|
|
217
355
|
for (let offset = 7; offset < bytes.length; offset += 24) {
|
|
218
356
|
const opcode = data.getUint32(offset, true);
|
|
357
|
+
if (opcode > 0x2e) {
|
|
358
|
+
throw new Context3DError("agalOpcodeRange", "AGAL opcode out of range");
|
|
359
|
+
}
|
|
360
|
+
// tld, a vertex texture read AIR names but does not do: its sampler is refused first.
|
|
361
|
+
if (opcode === 0x2e && stage === "vertex") {
|
|
362
|
+
throw new Context3DError("agalSamplerStage", "AGAL sampler in a vertex program");
|
|
363
|
+
}
|
|
219
364
|
// Unsupported instructions may have no destination or a sampler operand;
|
|
220
365
|
// do not misreport those layouts as malformed arithmetic registers.
|
|
221
|
-
if (!SUPPORTED.has(opcode)) {
|
|
366
|
+
if (!SUPPORTED.has(opcode) && !VERSION2.has(opcode)) {
|
|
222
367
|
throw new Context3DError("agalOpcode", "Unsupported AGAL opcode");
|
|
223
368
|
}
|
|
369
|
+
if ((opcode === 0x1a || opcode === 0x1b) && stage !== "fragment") {
|
|
370
|
+
throw new Context3DError("agalFragmentOnly", "AGAL opcode only in fragment programs");
|
|
371
|
+
}
|
|
372
|
+
if (VERSION2.has(opcode) && version < 2) {
|
|
373
|
+
throw new Context3DError("agalOpcodeVersion", "AGAL opcode needs version 2");
|
|
374
|
+
}
|
|
224
375
|
|
|
225
376
|
const dest = data.getUint32(offset + 4, true);
|
|
377
|
+
const block = blocks[blocks.length - 1];
|
|
378
|
+
if (opcode === 0x20 || opcode === 0x21) {
|
|
379
|
+
if (!block) {
|
|
380
|
+
throw new Context3DError(
|
|
381
|
+
opcode === 0x20 ? "agalElse" : "agalEndIf",
|
|
382
|
+
"AGAL else or eif without if",
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (opcode === 0x20 && block.otherwise) {
|
|
386
|
+
throw new Context3DError("agalElse", "AGAL else chain broken");
|
|
387
|
+
}
|
|
388
|
+
if (!block.instructions) {
|
|
389
|
+
throw new Context3DError("agalEmptyBranch", "Empty AGAL conditional branch");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (opcode === 0x20) {
|
|
393
|
+
// The else branch starts from what was written before the if.
|
|
394
|
+
block.otherwise = true;
|
|
395
|
+
temporaries = block.temporaries.slice();
|
|
396
|
+
varyings = block.varyings.slice();
|
|
397
|
+
block.instructions = 0;
|
|
398
|
+
block.predicate = `p${offset}`;
|
|
399
|
+
lines.push(`bool ${block.predicate} = ${block.parent}!${block.condition};`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// After the eif AIR counts what the else branch wrote, or, with none, what was written
|
|
404
|
+
// before the if: an if branch's writes alone are not.
|
|
405
|
+
if (!block.otherwise) {
|
|
406
|
+
temporaries = block.temporaries;
|
|
407
|
+
varyings = block.varyings;
|
|
408
|
+
}
|
|
409
|
+
blocks.pop();
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (opcode >= 0x1c && opcode <= 0x1f) {
|
|
414
|
+
if (dest !== 0) {
|
|
415
|
+
throw new Context3DError("agalConditionDestination", "AGAL conditional with a destination");
|
|
416
|
+
}
|
|
417
|
+
if (!scalar(offset + 8) || !scalar(offset + 16)) {
|
|
418
|
+
throw new Context3DError("agalScalar", "AGAL conditional operands must be scalar");
|
|
419
|
+
}
|
|
420
|
+
if (blocks.length === NESTING) {
|
|
421
|
+
throw new Context3DError("agalNesting", "AGAL conditionals nested too deep");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const a = source(offset + 8);
|
|
425
|
+
const b = source(offset + 16);
|
|
426
|
+
if (block) {
|
|
427
|
+
block.instructions++;
|
|
428
|
+
}
|
|
429
|
+
// Both branches run for every pixel and only their writes are predicated, as
|
|
430
|
+
// Direct3D's predication runs them: derivatives stay defined.
|
|
431
|
+
const parent = block ? `${block.predicate} && ` : "";
|
|
432
|
+
const condition = `c${offset}`;
|
|
433
|
+
const name = `p${offset}`;
|
|
434
|
+
lines.push(`bool ${condition} = ${a}.x ${COMPARISONS[opcode - 0x1c]} ${b}.x;`);
|
|
435
|
+
lines.push(`bool ${name} = ${parent}${condition};`);
|
|
436
|
+
blocks.push({
|
|
437
|
+
predicate: name,
|
|
438
|
+
condition,
|
|
439
|
+
parent,
|
|
440
|
+
otherwise: false,
|
|
441
|
+
instructions: 0,
|
|
442
|
+
temporaries: temporaries.slice(),
|
|
443
|
+
varyings: varyings.slice(),
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
|
|
226
448
|
const unary = UNARY.has(opcode);
|
|
227
449
|
if (unary && (data.getUint32(offset + 16, true) || data.getUint32(offset + 20, true))) {
|
|
228
450
|
throw new Context3DError("agalUnusedSource", "Nonempty unused AGAL source");
|
|
229
451
|
}
|
|
452
|
+
if (block) {
|
|
453
|
+
block.instructions++;
|
|
454
|
+
}
|
|
230
455
|
|
|
231
456
|
if (opcode === 0x27) {
|
|
232
457
|
const operand = data.getUint32(offset + 8, true);
|
|
233
458
|
const type = data.getUint32(offset + 12, true);
|
|
234
459
|
const swizzle = operand >>> 24;
|
|
460
|
+
if (type === 1 && stage === "fragment" && dest === 0) {
|
|
461
|
+
throw new Context3DError("agalConstants", "AGAL operands are both constants");
|
|
462
|
+
}
|
|
235
463
|
if (
|
|
236
464
|
stage !== "fragment" ||
|
|
237
465
|
dest !== 0 ||
|
|
@@ -241,7 +469,8 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
241
469
|
throw new Context3DError("agalDiscard", "Invalid AGAL discard");
|
|
242
470
|
}
|
|
243
471
|
|
|
244
|
-
|
|
472
|
+
const p = block ? `${block.predicate} && ` : "";
|
|
473
|
+
lines.push(`if (${p}${source(offset + 8)}.x < 0.0) { discard; }`);
|
|
245
474
|
continue;
|
|
246
475
|
}
|
|
247
476
|
|
|
@@ -253,8 +482,34 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
253
482
|
const targetType = dest >>> 24;
|
|
254
483
|
const targetIndex = dest & 65535;
|
|
255
484
|
const target = register(targetType, targetIndex, true);
|
|
256
|
-
if (stage === "fragment" && targetType === 3
|
|
257
|
-
|
|
485
|
+
if (stage === "fragment" && targetType === 3) {
|
|
486
|
+
if (mask !== 15) {
|
|
487
|
+
throw new Context3DError("agalColorMask", "AGAL color output must write all components");
|
|
488
|
+
}
|
|
489
|
+
if (outputs & (1 << targetIndex)) {
|
|
490
|
+
throw new Context3DError("agalOutputTwice", "AGAL color output written twice");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (targetType === 6 && mask !== 1) {
|
|
494
|
+
throw new Context3DError("agalDepthMask", "AGAL depth output must write only x");
|
|
495
|
+
}
|
|
496
|
+
if (opcode === 0x28 && block && data.getUint32(offset + 12, true) !== 4) {
|
|
497
|
+
throw new Context3DError(
|
|
498
|
+
"agalConditionTexture",
|
|
499
|
+
"AGAL texture reads in conditionals take only varyings",
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if ((opcode === 0x1a || opcode === 0x1b) && block) {
|
|
503
|
+
throw new Context3DError("agalConditionDerivative", "AGAL ddx and ddy in a conditional");
|
|
504
|
+
}
|
|
505
|
+
// Two constants make a value AIR wants precomputed; a lone one in kil, ddx and ddy too.
|
|
506
|
+
if (
|
|
507
|
+
opcode !== 0 &&
|
|
508
|
+
opcode !== 0x28 &&
|
|
509
|
+
(data.getUint32(offset + 12, true) & 255) === 1 &&
|
|
510
|
+
(unary ? opcode === 0x1a || opcode === 0x1b : (data.getUint32(offset + 20, true) & 255) === 1)
|
|
511
|
+
) {
|
|
512
|
+
throw new Context3DError("agalConstants", "AGAL operands are both constants");
|
|
258
513
|
}
|
|
259
514
|
const a = source(offset + 8);
|
|
260
515
|
const b = unary || opcode === 0x28 ? "" : source(offset + 16);
|
|
@@ -374,29 +629,48 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
374
629
|
expression = `vec${rows}(${dots.join(", ")})`;
|
|
375
630
|
break;
|
|
376
631
|
}
|
|
632
|
+
case 0x1a:
|
|
633
|
+
expression = `dFdx(${a})`;
|
|
634
|
+
break;
|
|
635
|
+
case 0x1b:
|
|
636
|
+
// AIR's y grows down its target; GL's up the back buffer, and down a texture, which
|
|
637
|
+
// targetY flips.
|
|
638
|
+
names.set("targetY", "uniform float targetY;");
|
|
639
|
+
expression = `(-targetY * dFdy(${a}))`;
|
|
640
|
+
break;
|
|
377
641
|
case 0x28: {
|
|
378
642
|
const low = data.getUint32(offset + 16, true);
|
|
379
643
|
const high = data.getUint32(offset + 20, true);
|
|
380
644
|
const index = low & 65535;
|
|
381
645
|
const bias = data.getInt8(offset + 18) / 8;
|
|
382
646
|
const format = (high >>> 8) & 15;
|
|
647
|
+
const dimension = (high >>> 12) & 15;
|
|
648
|
+
const special = (high >>> 16) & 15;
|
|
649
|
+
const wrap = (high >>> 20) & 15;
|
|
383
650
|
const mip = (high >>> 24) & 15;
|
|
384
651
|
const filter = high >>> 28;
|
|
385
|
-
if (
|
|
386
|
-
stage !== "fragment" ||
|
|
387
|
-
index >= 8 ||
|
|
388
|
-
low >>> 24 !== 0 ||
|
|
389
|
-
(high & ~0x73141f00) !== 5 ||
|
|
390
|
-
format > 2 ||
|
|
391
|
-
filter > 5 ||
|
|
392
|
-
mip > 2
|
|
393
|
-
) {
|
|
652
|
+
if (stage !== "fragment" || low >>> 24 !== 0 || (high & 255) !== 5) {
|
|
394
653
|
throw new Context3DError("agalSampler", "Unsupported AGAL sampler encoding");
|
|
395
654
|
}
|
|
655
|
+
if (index >= limits.samplers) {
|
|
656
|
+
throw new Context3DError("agalSamplerIndex", "AGAL sampler index out of bounds");
|
|
657
|
+
}
|
|
658
|
+
if (dimension > 1) {
|
|
659
|
+
throw new Context3DError("agalSamplerDimension", "Unknown AGAL sampler dimension");
|
|
660
|
+
}
|
|
661
|
+
// Of the special flags only ignoresampler: AIR refuses centroid and single.
|
|
662
|
+
if (special & ~4 || wrap > 3) {
|
|
663
|
+
throw new Context3DError("agalSampler", "Unsupported AGAL sampler encoding");
|
|
664
|
+
}
|
|
665
|
+
if (mip > 2) {
|
|
666
|
+
throw new Context3DError("agalSamplerMip", "Unknown AGAL sampler mipmap mode");
|
|
667
|
+
}
|
|
668
|
+
if (filter > 5) {
|
|
669
|
+
throw new Context3DError("agalSamplerFilter", "Unknown AGAL sampler filter");
|
|
670
|
+
}
|
|
396
671
|
|
|
397
|
-
const cube =
|
|
398
|
-
const
|
|
399
|
-
const ignore = (high & 0x00040000) !== 0;
|
|
672
|
+
const cube = dimension === 1;
|
|
673
|
+
const ignore = (special & 4) !== 0;
|
|
400
674
|
const previous = samplers.get(index);
|
|
401
675
|
if (previous && previous.bias !== bias) {
|
|
402
676
|
throw new SamplerBiasError("Conflicting AGAL sampler biases");
|
|
@@ -406,7 +680,7 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
406
680
|
previous &&
|
|
407
681
|
(previous.format !== format ||
|
|
408
682
|
previous.filter !== filter ||
|
|
409
|
-
previous.
|
|
683
|
+
previous.wrap !== wrap ||
|
|
410
684
|
previous.mip !== mip ||
|
|
411
685
|
previous.cube !== cube ||
|
|
412
686
|
previous.ignore !== ignore)
|
|
@@ -414,7 +688,7 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
414
688
|
throw new Context3DError("agalSampler", "Conflicting AGAL sampler states");
|
|
415
689
|
}
|
|
416
690
|
|
|
417
|
-
samplers.set(index, { index, format, filter,
|
|
691
|
+
samplers.set(index, { index, format, filter, wrap, mip, cube, ignore, bias });
|
|
418
692
|
names.set(`fs${index}`, `uniform ${cube ? "samplerCube" : "sampler2D"} fs${index};`);
|
|
419
693
|
// Bias stays in the shader even when ignoresampler preserves runtime state.
|
|
420
694
|
expression = `texture(fs${index}, ${a}.${cube ? "xyz" : "xy"}${bias === 0 ? "" : `, ${bias.toFixed(3)}`})`;
|
|
@@ -429,6 +703,9 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
429
703
|
case 0x2c:
|
|
430
704
|
expression = `vec4(equal(${a}, ${b}))`;
|
|
431
705
|
break;
|
|
706
|
+
case 0x2d:
|
|
707
|
+
expression = `vec4(notEqual(${a}, ${b}))`;
|
|
708
|
+
break;
|
|
432
709
|
default:
|
|
433
710
|
throw new Context3DError("agalOpcode", "Unsupported AGAL opcode");
|
|
434
711
|
}
|
|
@@ -442,13 +719,28 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
442
719
|
|
|
443
720
|
// One vector assignment evaluates every source before changing any lane,
|
|
444
721
|
// including overlapping temporary registers in matrix operations.
|
|
445
|
-
|
|
722
|
+
if (!block) {
|
|
723
|
+
lines.push(`${target}.${lanes} = ${expression}.${lanes};`);
|
|
724
|
+
} else if (opcode === 0x28) {
|
|
725
|
+
// A texture read's derivatives are taken outside the branch, where every pixel of a
|
|
726
|
+
// quad computes them.
|
|
727
|
+
const value = `h${hoisted++}`;
|
|
728
|
+
lines.push(`vec4 ${value} = ${expression};`);
|
|
729
|
+
lines.push(`if (${block.predicate}) { ${target}.${lanes} = ${value}.${lanes}; }`);
|
|
730
|
+
} else {
|
|
731
|
+
lines.push(`if (${block.predicate}) { ${target}.${lanes} = ${expression}.${lanes}; }`);
|
|
732
|
+
}
|
|
446
733
|
switch (targetType) {
|
|
447
734
|
case 2:
|
|
448
735
|
temporaries[targetIndex] |= mask;
|
|
449
736
|
break;
|
|
450
737
|
case 3:
|
|
451
|
-
|
|
738
|
+
if (stage === "fragment") {
|
|
739
|
+
outputs |= 1 << targetIndex;
|
|
740
|
+
}
|
|
741
|
+
if (targetIndex === 0) {
|
|
742
|
+
output |= mask;
|
|
743
|
+
}
|
|
452
744
|
break;
|
|
453
745
|
case 4:
|
|
454
746
|
varyings[targetIndex] |= mask;
|
|
@@ -462,26 +754,57 @@ export function translate(bytes: Uint8Array, stage: ProgramType): ShaderSource {
|
|
|
462
754
|
"AGAL program does not write its complete output",
|
|
463
755
|
);
|
|
464
756
|
}
|
|
757
|
+
if (outputs & (outputs + 1)) {
|
|
758
|
+
throw new Context3DError("agalOutputSlots", "AGAL color outputs are not consecutive");
|
|
759
|
+
}
|
|
760
|
+
// ps_2_0's 64 arithmetic and 32 texture instructions, which AIR counts once the program
|
|
761
|
+
// is valid; an instruction here is taken as one there.
|
|
762
|
+
if (
|
|
763
|
+
profile === "baselineConstrained" &&
|
|
764
|
+
stage === "fragment" &&
|
|
765
|
+
(bytes.length - 7) / 24 > CONSTRAINED_FRAGMENT
|
|
766
|
+
) {
|
|
767
|
+
throw new Context3DError("agalNativeLength", "Too many instructions for the native shader");
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const count = stage === "fragment" ? 32 - Math.clz32(outputs) : 1;
|
|
771
|
+
let results: string;
|
|
772
|
+
let finish: string;
|
|
773
|
+
if (stage === "vertex") {
|
|
774
|
+
results = "uniform float targetY;";
|
|
775
|
+
finish =
|
|
776
|
+
"gl_Position = vec4(result.x, result.y * targetY, result.z * 2.0 - result.w, result.w);";
|
|
777
|
+
} else if (count === 1) {
|
|
778
|
+
results = "out vec4 color;";
|
|
779
|
+
finish = "color = result;";
|
|
780
|
+
} else {
|
|
781
|
+
const outs: string[] = [];
|
|
782
|
+
const sets: string[] = [];
|
|
783
|
+
for (let i = 0; i < count; i++) {
|
|
784
|
+
outs.push(`layout(location = ${i}) out vec4 color${i || ""};`);
|
|
785
|
+
sets.push(`color${i || ""} = result${i || ""};`);
|
|
786
|
+
}
|
|
787
|
+
results = outs.join("\n");
|
|
788
|
+
finish = sets.join("\n ");
|
|
789
|
+
}
|
|
790
|
+
if (names.has("depth")) {
|
|
791
|
+
finish += "\n gl_FragDepth = depth.x;";
|
|
792
|
+
}
|
|
465
793
|
|
|
466
|
-
const declarations = [...names.values()].join("\n");
|
|
467
|
-
const finish =
|
|
468
|
-
stage === "vertex"
|
|
469
|
-
? "gl_Position = vec4(result.x, result.y * targetY, result.z * 2.0 - result.w, result.w);"
|
|
470
|
-
: "color = result;";
|
|
471
794
|
return {
|
|
472
795
|
source: `#version 300 es
|
|
473
796
|
precision highp float;
|
|
474
|
-
${
|
|
797
|
+
${[...names.values()].join("\n")}
|
|
475
798
|
${
|
|
476
799
|
indirect
|
|
477
800
|
? `vec4 readConstant(float address, float offset) {
|
|
478
801
|
float index = roundEven(address) + offset;
|
|
479
|
-
if (!(index >= 0.0 && index <
|
|
802
|
+
if (!(index >= 0.0 && index < ${bank}.0)) { return vec4(0.0); }
|
|
480
803
|
return vc[int(index)];
|
|
481
804
|
}`
|
|
482
805
|
: ""
|
|
483
806
|
}
|
|
484
|
-
${
|
|
807
|
+
${results}
|
|
485
808
|
void main() {
|
|
486
809
|
vec4 result = vec4(0.0);
|
|
487
810
|
${lines.join("\n ")}
|
|
@@ -492,5 +815,6 @@ void main() {
|
|
|
492
815
|
constants: [...constants],
|
|
493
816
|
indirect,
|
|
494
817
|
samplers: [...samplers.values()],
|
|
818
|
+
outputs: count,
|
|
495
819
|
};
|
|
496
820
|
}
|