@swf2es/context3d 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/README.md +219 -2
- package/dist/agal.d.ts +23 -0
- package/dist/agal.d.ts.map +1 -0
- package/dist/agal.js +410 -0
- package/dist/agal.js.map +1 -0
- package/dist/backend.d.ts +122 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +63 -0
- package/dist/backend.js.map +1 -0
- package/dist/device/index.d.ts +3 -0
- package/dist/device/index.d.ts.map +1 -0
- package/dist/device/index.js +6 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/programs.d.ts +13 -0
- package/dist/device/programs.d.ts.map +1 -0
- package/dist/device/programs.js +59 -0
- package/dist/device/programs.js.map +1 -0
- package/dist/device/state.d.ts +53 -0
- package/dist/device/state.d.ts.map +1 -0
- package/dist/device/state.js +196 -0
- package/dist/device/state.js.map +1 -0
- package/dist/dxt.d.ts +22 -0
- package/dist/dxt.d.ts.map +1 -0
- package/dist/dxt.js +304 -0
- package/dist/dxt.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/webgl/buffers.d.ts +27 -0
- package/dist/webgl/buffers.d.ts.map +1 -0
- package/dist/webgl/buffers.js +53 -0
- package/dist/webgl/buffers.js.map +1 -0
- package/dist/webgl/context.d.ts +123 -0
- package/dist/webgl/context.d.ts.map +1 -0
- package/dist/webgl/context.js +1368 -0
- package/dist/webgl/context.js.map +1 -0
- package/dist/webgl/index.d.ts +3 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +2 -0
- package/dist/webgl/index.js.map +1 -0
- package/dist/webgl/limits.d.ts +23 -0
- package/dist/webgl/limits.d.ts.map +1 -0
- package/dist/webgl/limits.js +35 -0
- package/dist/webgl/limits.js.map +1 -0
- package/dist/webgl/programs.d.ts +22 -0
- package/dist/webgl/programs.d.ts.map +1 -0
- package/dist/webgl/programs.js +110 -0
- package/dist/webgl/programs.js.map +1 -0
- package/dist/webgl/state.d.ts +42 -0
- package/dist/webgl/state.d.ts.map +1 -0
- package/dist/webgl/state.js +55 -0
- package/dist/webgl/state.js.map +1 -0
- package/dist/webgl/targets.d.ts +20 -0
- package/dist/webgl/targets.d.ts.map +1 -0
- package/dist/webgl/targets.js +138 -0
- package/dist/webgl/targets.js.map +1 -0
- package/dist/webgl/textures.d.ts +28 -0
- package/dist/webgl/textures.d.ts.map +1 -0
- package/dist/webgl/textures.js +119 -0
- package/dist/webgl/textures.js.map +1 -0
- package/dist/webgl/validation.d.ts +4 -0
- package/dist/webgl/validation.d.ts.map +1 -0
- package/dist/webgl/validation.js +52 -0
- package/dist/webgl/validation.js.map +1 -0
- package/package.json +37 -3
- package/src/agal.ts +496 -0
- package/src/backend.ts +210 -0
- package/src/device/index.ts +5 -0
- package/src/device/programs.ts +66 -0
- package/src/device/state.ts +214 -0
- package/src/dxt.ts +349 -0
- package/src/errors.ts +68 -0
- package/src/index.ts +35 -0
- package/src/webgl/buffers.ts +102 -0
- package/src/webgl/context.ts +1803 -0
- package/src/webgl/index.ts +2 -0
- package/src/webgl/limits.ts +61 -0
- package/src/webgl/programs.ts +145 -0
- package/src/webgl/state.ts +65 -0
- package/src/webgl/targets.ts +229 -0
- package/src/webgl/textures.ts +200 -0
- package/src/webgl/validation.ts +72 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// What the renderer (@swf2es/render) and Context3D share of a WebGL 2
|
|
2
|
+
// context: linking programs, and the cache of its state that its one owner
|
|
3
|
+
// sets it through (docs/renderer.md, "The backend").
|
|
4
|
+
export { linkProgram, ShaderError } from "./programs.js";
|
|
5
|
+
export { GlState } from "./state.js";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Why a program could not be made: no objects to make it of, a stage that did not compile, or no link. */
|
|
2
|
+
export class ShaderError extends Error {
|
|
3
|
+
constructor(
|
|
4
|
+
readonly stage: "allocate" | "compile" | "link",
|
|
5
|
+
readonly log: string,
|
|
6
|
+
) {
|
|
7
|
+
super(log);
|
|
8
|
+
this.name = "ShaderError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A program linked from GLSL `vertex` and `fragment`, its shaders detached
|
|
14
|
+
* and deleted once linked, and nothing left behind if it fails: a
|
|
15
|
+
* ShaderError says where.
|
|
16
|
+
*/
|
|
17
|
+
export function linkProgram(
|
|
18
|
+
gl: WebGL2RenderingContext,
|
|
19
|
+
vertex: string,
|
|
20
|
+
fragment: string,
|
|
21
|
+
): WebGLProgram {
|
|
22
|
+
const v = gl.createShader(gl.VERTEX_SHADER);
|
|
23
|
+
const f = gl.createShader(gl.FRAGMENT_SHADER);
|
|
24
|
+
const p = gl.createProgram();
|
|
25
|
+
let linked = false;
|
|
26
|
+
let attached = false;
|
|
27
|
+
try {
|
|
28
|
+
if (!v || !f || !p) {
|
|
29
|
+
throw new ShaderError("allocate", "cannot allocate shader");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
gl.shaderSource(v, vertex);
|
|
33
|
+
gl.shaderSource(f, fragment);
|
|
34
|
+
gl.compileShader(v);
|
|
35
|
+
gl.compileShader(f);
|
|
36
|
+
if (
|
|
37
|
+
!gl.getShaderParameter(v, gl.COMPILE_STATUS) ||
|
|
38
|
+
!gl.getShaderParameter(f, gl.COMPILE_STATUS)
|
|
39
|
+
) {
|
|
40
|
+
throw new ShaderError("compile", `${gl.getShaderInfoLog(v)} ${gl.getShaderInfoLog(f)}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
gl.attachShader(p, v);
|
|
44
|
+
gl.attachShader(p, f);
|
|
45
|
+
attached = true;
|
|
46
|
+
gl.linkProgram(p);
|
|
47
|
+
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
|
|
48
|
+
throw new ShaderError("link", `${gl.getProgramInfoLog(p)}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
linked = true;
|
|
52
|
+
return p;
|
|
53
|
+
} finally {
|
|
54
|
+
if (attached && p && v) {
|
|
55
|
+
gl.detachShader(p, v);
|
|
56
|
+
}
|
|
57
|
+
if (attached && p && f) {
|
|
58
|
+
gl.detachShader(p, f);
|
|
59
|
+
}
|
|
60
|
+
gl.deleteShader(v);
|
|
61
|
+
gl.deleteShader(f);
|
|
62
|
+
if (!linked) {
|
|
63
|
+
gl.deleteProgram(p);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GL state of a context as its one owner last set it, so that nothing
|
|
3
|
+
* is set twice: the program, vertex array, framebuffer, viewport, blending,
|
|
4
|
+
* scissor, colour mask and the textures on each unit. What sets state
|
|
5
|
+
* behind it (another library, a lost context) calls forget, and everything
|
|
6
|
+
* is set again on its next use.
|
|
7
|
+
*/
|
|
8
|
+
export class GlState {
|
|
9
|
+
readonly gl: WebGL2RenderingContext;
|
|
10
|
+
private program: WebGLProgram | null | undefined;
|
|
11
|
+
private vertexArray: WebGLVertexArrayObject | null | undefined;
|
|
12
|
+
private framebuffer: WebGLFramebuffer | null | undefined;
|
|
13
|
+
private readonly view = [Number.NaN, Number.NaN, Number.NaN, Number.NaN];
|
|
14
|
+
/** Blending: on or off, then the four factors and the equation, -1 as unknown. */
|
|
15
|
+
private blending: boolean | undefined;
|
|
16
|
+
private readonly blend = [-1, -1, -1, -1, -1];
|
|
17
|
+
private scissoring: boolean | undefined;
|
|
18
|
+
private readonly box = [Number.NaN, Number.NaN, Number.NaN, Number.NaN];
|
|
19
|
+
private mask = -1;
|
|
20
|
+
/** The stencil test: off (undefined unknown), or its function, reference and pass operation. */
|
|
21
|
+
private stencilling: boolean | undefined;
|
|
22
|
+
private readonly stencilState = [-1, -1, -1];
|
|
23
|
+
private unit = -1;
|
|
24
|
+
private readonly textures: (WebGLTexture | null | undefined)[] = [];
|
|
25
|
+
private readonly samplers: (WebGLSampler | null | undefined)[] = [];
|
|
26
|
+
|
|
27
|
+
constructor(gl: WebGL2RenderingContext) {
|
|
28
|
+
this.gl = gl;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Everything unknown: set again as next asked for. */
|
|
32
|
+
forget(): void {
|
|
33
|
+
this.program = undefined;
|
|
34
|
+
this.vertexArray = undefined;
|
|
35
|
+
this.framebuffer = undefined;
|
|
36
|
+
this.view.fill(Number.NaN);
|
|
37
|
+
this.blending = undefined;
|
|
38
|
+
this.blend.fill(-1);
|
|
39
|
+
this.scissoring = undefined;
|
|
40
|
+
this.box.fill(Number.NaN);
|
|
41
|
+
this.mask = -1;
|
|
42
|
+
this.stencilling = undefined;
|
|
43
|
+
this.stencilState.fill(-1);
|
|
44
|
+
this.unit = -1;
|
|
45
|
+
this.textures.length = 0;
|
|
46
|
+
this.samplers.length = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
useProgram(program: WebGLProgram | null): void {
|
|
50
|
+
if (program !== this.program) {
|
|
51
|
+
this.program = program;
|
|
52
|
+
this.gl.useProgram(program);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bindVertexArray(vertexArray: WebGLVertexArrayObject | null): void {
|
|
57
|
+
if (vertexArray !== this.vertexArray) {
|
|
58
|
+
this.vertexArray = vertexArray;
|
|
59
|
+
this.gl.bindVertexArray(vertexArray);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bindFramebuffer(framebuffer: WebGLFramebuffer | null): void {
|
|
64
|
+
if (framebuffer !== this.framebuffer) {
|
|
65
|
+
this.framebuffer = framebuffer;
|
|
66
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
viewport(x: number, y: number, width: number, height: number): void {
|
|
71
|
+
const v = this.view;
|
|
72
|
+
if (v[0] !== x || v[1] !== y || v[2] !== width || v[3] !== height) {
|
|
73
|
+
v[0] = x;
|
|
74
|
+
v[1] = y;
|
|
75
|
+
v[2] = width;
|
|
76
|
+
v[3] = height;
|
|
77
|
+
this.gl.viewport(x, y, width, height);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Blending by the factors and equation given, or off with `src` null. */
|
|
82
|
+
setBlend(src: number | null, dst = 0, srcAlpha = src ?? 0, dstAlpha = dst, equation = 0): void {
|
|
83
|
+
const gl = this.gl;
|
|
84
|
+
const on = src !== null;
|
|
85
|
+
if (on !== this.blending) {
|
|
86
|
+
this.blending = on;
|
|
87
|
+
if (on) {
|
|
88
|
+
gl.enable(gl.BLEND);
|
|
89
|
+
} else {
|
|
90
|
+
gl.disable(gl.BLEND);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!on) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const b = this.blend;
|
|
99
|
+
if (b[0] !== src || b[1] !== dst || b[2] !== srcAlpha || b[3] !== dstAlpha) {
|
|
100
|
+
b[0] = src;
|
|
101
|
+
b[1] = dst;
|
|
102
|
+
b[2] = srcAlpha;
|
|
103
|
+
b[3] = dstAlpha;
|
|
104
|
+
gl.blendFuncSeparate(src, dst, srcAlpha, dstAlpha);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const eq = equation || gl.FUNC_ADD;
|
|
108
|
+
if (b[4] !== eq) {
|
|
109
|
+
b[4] = eq;
|
|
110
|
+
gl.blendEquation(eq);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Scissoring to the box given, or off with none. */
|
|
115
|
+
scissor(box: readonly [number, number, number, number] | null): void {
|
|
116
|
+
const gl = this.gl;
|
|
117
|
+
const on = box !== null;
|
|
118
|
+
if (on !== this.scissoring) {
|
|
119
|
+
this.scissoring = on;
|
|
120
|
+
if (on) {
|
|
121
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
122
|
+
} else {
|
|
123
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const b = this.box;
|
|
128
|
+
if (box && (b[0] !== box[0] || b[1] !== box[1] || b[2] !== box[2] || b[3] !== box[3])) {
|
|
129
|
+
b[0] = box[0];
|
|
130
|
+
b[1] = box[1];
|
|
131
|
+
b[2] = box[2];
|
|
132
|
+
b[3] = box[3];
|
|
133
|
+
gl.scissor(box[0], box[1], box[2], box[3]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The stencil test: passing where `func` holds of the stencil and `ref`,
|
|
139
|
+
* then `pass` done to it (KEEP, INCR, DECR...); off with `func` null.
|
|
140
|
+
*/
|
|
141
|
+
stencil(func: number | null, ref = 0, pass = 0): void {
|
|
142
|
+
const gl = this.gl;
|
|
143
|
+
const on = func !== null;
|
|
144
|
+
if (on !== this.stencilling) {
|
|
145
|
+
this.stencilling = on;
|
|
146
|
+
if (on) {
|
|
147
|
+
gl.enable(gl.STENCIL_TEST);
|
|
148
|
+
} else {
|
|
149
|
+
gl.disable(gl.STENCIL_TEST);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!on) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const s = this.stencilState;
|
|
158
|
+
if (s[0] !== func || s[1] !== ref) {
|
|
159
|
+
s[0] = func;
|
|
160
|
+
s[1] = ref;
|
|
161
|
+
gl.stencilFunc(func, ref, 0xff);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const op = pass || gl.KEEP;
|
|
165
|
+
if (s[2] !== op) {
|
|
166
|
+
s[2] = op;
|
|
167
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, op);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Which of red, green, blue and alpha are written, as bits 1, 2, 4 and 8. */
|
|
172
|
+
colorMask(bits: number): void {
|
|
173
|
+
if (bits !== this.mask) {
|
|
174
|
+
this.mask = bits;
|
|
175
|
+
this.gl.colorMask((bits & 1) !== 0, (bits & 2) !== 0, (bits & 4) !== 0, (bits & 8) !== 0);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** `texture` bound to texture unit `unit` as a 2D texture. */
|
|
180
|
+
bindTexture(unit: number, texture: WebGLTexture | null): void {
|
|
181
|
+
if (this.textures[unit] === texture) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.textures[unit] = texture;
|
|
186
|
+
this.activeTexture(unit);
|
|
187
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** `sampler` bound to texture unit `unit`, or none for the texture's own parameters. */
|
|
191
|
+
bindSampler(unit: number, sampler: WebGLSampler | null): void {
|
|
192
|
+
if (this.samplers[unit] !== sampler) {
|
|
193
|
+
this.samplers[unit] = sampler;
|
|
194
|
+
this.gl.bindSampler(unit, sampler);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Make `unit` the active one, for an upload to the texture bound there. */
|
|
199
|
+
activeTexture(unit: number): void {
|
|
200
|
+
if (unit !== this.unit) {
|
|
201
|
+
this.unit = unit;
|
|
202
|
+
this.gl.activeTexture(this.gl.TEXTURE0 + unit);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** A texture deleted: no unit is thought to hold it any more. */
|
|
207
|
+
dropTexture(texture: WebGLTexture): void {
|
|
208
|
+
for (let i = 0; i < this.textures.length; i++) {
|
|
209
|
+
if (this.textures[i] === texture) {
|
|
210
|
+
this.textures[i] = undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
package/src/dxt.ts
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// S3TC's DXT1 and DXT5 on the CPU: blocks decoded to RGBA for a backend
|
|
2
|
+
// whose GPU takes no S3TC (WEBGL_compressed_texture_s3tc absent), and RGBA
|
|
3
|
+
// encoded to blocks for pixels uploaded to a compressed texture, which AIR
|
|
4
|
+
// compresses too. DXT1 is "compressed": a block whose first colour is not
|
|
5
|
+
// above its second has a third, the midpoint, and transparent black for its
|
|
6
|
+
// fourth index, as AIR draws it (GL's COMPRESSED_RGBA_S3TC_DXT1). DXT5's
|
|
7
|
+
// colours always have four.
|
|
8
|
+
|
|
9
|
+
/** RGB565 as 0xRRGGBB, each channel's high bits repeated into its low ones. */
|
|
10
|
+
function rgb(c: number): number {
|
|
11
|
+
const r = (c >> 11) & 31;
|
|
12
|
+
const g = (c >> 5) & 63;
|
|
13
|
+
const b = c & 31;
|
|
14
|
+
return (((r << 3) | (r >> 2)) << 16) | (((g << 2) | (g >> 4)) << 8) | ((b << 3) | (b >> 2));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `a` weighted `wa` against `b`, per channel of two 0xRRGGBB colours, over `total`. */
|
|
18
|
+
function mix(a: number, b: number, wa: number, wb: number, total: number): number {
|
|
19
|
+
let out = 0;
|
|
20
|
+
for (let shift = 0; shift < 24; shift += 8) {
|
|
21
|
+
const channel = ((((a >> shift) & 255) * wa + ((b >> shift) & 255) * wb) / total) | 0;
|
|
22
|
+
out |= channel << shift;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** DXT1's fourth colour where a block has three: black, and the one transparent texel. */
|
|
29
|
+
const TRANSPARENT = -1;
|
|
30
|
+
|
|
31
|
+
const palette = new Int32Array(4);
|
|
32
|
+
const texels = new Int32Array(16);
|
|
33
|
+
const alphas = new Uint8Array(8);
|
|
34
|
+
|
|
35
|
+
/** A whole RGBA level encoded to DXT blocks; null uses the TypeScript encoder. */
|
|
36
|
+
export type DxtEncoder = (
|
|
37
|
+
rgba: Uint8Array,
|
|
38
|
+
dxt5: boolean,
|
|
39
|
+
width: number,
|
|
40
|
+
height: number,
|
|
41
|
+
) => Uint8Array | null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `blocks`, a level `width` by `height` of DXT1 (8 bytes a 4x4 block) or,
|
|
45
|
+
* with `dxt5`, DXT5 (16), rows of blocks from the top, written to `out` as
|
|
46
|
+
* top-down RGBA rows of `width` pixels. The level's edge blocks may stand
|
|
47
|
+
* past it, as a 2x2 level's one block does; what stands past is dropped.
|
|
48
|
+
*/
|
|
49
|
+
export function decodeDxt(
|
|
50
|
+
blocks: Uint8Array,
|
|
51
|
+
dxt5: boolean,
|
|
52
|
+
width: number,
|
|
53
|
+
height: number,
|
|
54
|
+
out: Uint8Array,
|
|
55
|
+
): void {
|
|
56
|
+
const size = dxt5 ? 16 : 8;
|
|
57
|
+
const across = Math.max(1, (width + 3) >> 2);
|
|
58
|
+
const down = Math.max(1, (height + 3) >> 2);
|
|
59
|
+
|
|
60
|
+
for (let by = 0; by < down; by++) {
|
|
61
|
+
for (let bx = 0; bx < across; bx++) {
|
|
62
|
+
const at = (by * across + bx) * size;
|
|
63
|
+
const colour = dxt5 ? at + 8 : at;
|
|
64
|
+
const c0 = blocks[colour] | (blocks[colour + 1] << 8);
|
|
65
|
+
const c1 = blocks[colour + 2] | (blocks[colour + 3] << 8);
|
|
66
|
+
palette[0] = rgb(c0);
|
|
67
|
+
palette[1] = rgb(c1);
|
|
68
|
+
if (dxt5 || c0 > c1) {
|
|
69
|
+
palette[2] = mix(palette[0], palette[1], 2, 1, 3);
|
|
70
|
+
palette[3] = mix(palette[0], palette[1], 1, 2, 3);
|
|
71
|
+
} else {
|
|
72
|
+
palette[2] = mix(palette[0], palette[1], 1, 1, 2);
|
|
73
|
+
palette[3] = TRANSPARENT;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (dxt5) {
|
|
77
|
+
const a0 = blocks[at];
|
|
78
|
+
const a1 = blocks[at + 1];
|
|
79
|
+
alphas[0] = a0;
|
|
80
|
+
alphas[1] = a1;
|
|
81
|
+
if (a0 > a1) {
|
|
82
|
+
for (let i = 1; i < 7; i++) {
|
|
83
|
+
alphas[i + 1] = (((7 - i) * a0 + i * a1) / 7) | 0;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
for (let i = 1; i < 5; i++) {
|
|
87
|
+
alphas[i + 1] = (((5 - i) * a0 + i * a1) / 5) | 0;
|
|
88
|
+
}
|
|
89
|
+
alphas[6] = 0;
|
|
90
|
+
alphas[7] = 255;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const indices =
|
|
95
|
+
(blocks[colour + 4] |
|
|
96
|
+
(blocks[colour + 5] << 8) |
|
|
97
|
+
(blocks[colour + 6] << 16) |
|
|
98
|
+
(blocks[colour + 7] << 24)) >>>
|
|
99
|
+
0;
|
|
100
|
+
// DXT5's 48 bits of 3-bit alpha indices, as two 24-bit halves.
|
|
101
|
+
const low = dxt5 ? blocks[at + 2] | (blocks[at + 3] << 8) | (blocks[at + 4] << 16) : 0;
|
|
102
|
+
const high = dxt5 ? blocks[at + 5] | (blocks[at + 6] << 8) | (blocks[at + 7] << 16) : 0;
|
|
103
|
+
for (let y = 0; y < 4; y++) {
|
|
104
|
+
const py = by * 4 + y;
|
|
105
|
+
if (py >= height) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (let x = 0; x < 4; x++) {
|
|
110
|
+
const px = bx * 4 + x;
|
|
111
|
+
if (px >= width) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const i = y * 4 + x;
|
|
116
|
+
const c = palette[(indices >>> (i * 2)) & 3];
|
|
117
|
+
const o = (py * width + px) * 4;
|
|
118
|
+
if (c === TRANSPARENT) {
|
|
119
|
+
out[o] = out[o + 1] = out[o + 2] = out[o + 3] = 0;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
out[o] = c >> 16;
|
|
124
|
+
out[o + 1] = c >> 8;
|
|
125
|
+
out[o + 2] = c;
|
|
126
|
+
out[o + 3] = dxt5 ? alphas[(i < 8 ? low >> (i * 3) : high >> ((i - 8) * 3)) & 7] : 255;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A colour's distance from palette entry `c`, squared, per channel. */
|
|
134
|
+
function distance(r: number, g: number, b: number, c: number): number {
|
|
135
|
+
const dr = r - ((c >> 16) & 255);
|
|
136
|
+
const dg = g - ((c >> 8) & 255);
|
|
137
|
+
const db = b - (c & 255);
|
|
138
|
+
return dr * dr + dg * dg + db * db;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 0xRRGGBB as RGB565. */
|
|
142
|
+
function pack(r: number, g: number, b: number): number {
|
|
143
|
+
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The bytes of a level `width` by `height` as DXT1's blocks, or DXT5's: one block at least. */
|
|
147
|
+
export function blockBytes(width: number, height: number, dxt5: boolean): number {
|
|
148
|
+
return Math.max(1, (width + 3) >> 2) * Math.max(1, (height + 3) >> 2) * (dxt5 ? 16 : 8);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** encodeDxt's output, kept for the next level: each upload copies its blocks out at once. */
|
|
152
|
+
let encoded = new Uint8Array(0);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* `rgba`, top-down RGBA rows of `width` pixels, `height` high, as blocks,
|
|
156
|
+
* DXT1's or with `dxt5` DXT5's, for `decodeDxt` or a GPU: each block's
|
|
157
|
+
* colours on the line through them that their spread runs along (its
|
|
158
|
+
* principal axis, by a few power iterations), between the pixels that lie
|
|
159
|
+
* farthest out on it, four of them always; DXT5's alpha between its lowest
|
|
160
|
+
* and highest, eight. A level smaller than a block repeats its pixels across
|
|
161
|
+
* it. The result is a view of a buffer the next call reuses.
|
|
162
|
+
*/
|
|
163
|
+
export function encodeDxt(
|
|
164
|
+
rgba: Uint8Array,
|
|
165
|
+
dxt5: boolean,
|
|
166
|
+
width: number,
|
|
167
|
+
height: number,
|
|
168
|
+
): Uint8Array {
|
|
169
|
+
const size = dxt5 ? 16 : 8;
|
|
170
|
+
const across = Math.max(1, (width + 3) >> 2);
|
|
171
|
+
const down = Math.max(1, (height + 3) >> 2);
|
|
172
|
+
const length = blockBytes(width, height, dxt5);
|
|
173
|
+
if (encoded.length < length) {
|
|
174
|
+
encoded = new Uint8Array(length);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const out = encoded;
|
|
178
|
+
|
|
179
|
+
for (let by = 0; by < down; by++) {
|
|
180
|
+
for (let bx = 0; bx < across; bx++) {
|
|
181
|
+
const at = (by * across + bx) * size;
|
|
182
|
+
// The block's pixels, its edge repeated past a level smaller than a block.
|
|
183
|
+
let mr = 0;
|
|
184
|
+
let mg = 0;
|
|
185
|
+
let mb = 0;
|
|
186
|
+
let lowA = 255;
|
|
187
|
+
let highA = 0;
|
|
188
|
+
for (let i = 0; i < 16; i++) {
|
|
189
|
+
const p =
|
|
190
|
+
(Math.min(by * 4 + (i >> 2), height - 1) * width +
|
|
191
|
+
Math.min(bx * 4 + (i & 3), width - 1)) *
|
|
192
|
+
4;
|
|
193
|
+
texels[i] = p;
|
|
194
|
+
mr += rgba[p];
|
|
195
|
+
mg += rgba[p + 1];
|
|
196
|
+
mb += rgba[p + 2];
|
|
197
|
+
lowA = Math.min(lowA, rgba[p + 3]);
|
|
198
|
+
highA = Math.max(highA, rgba[p + 3]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
mr /= 16;
|
|
202
|
+
mg /= 16;
|
|
203
|
+
mb /= 16;
|
|
204
|
+
// The colours' covariance, and its principal axis.
|
|
205
|
+
let rr = 0;
|
|
206
|
+
let rg = 0;
|
|
207
|
+
let rb = 0;
|
|
208
|
+
let gg = 0;
|
|
209
|
+
let gb = 0;
|
|
210
|
+
let bb = 0;
|
|
211
|
+
for (let i = 0; i < 16; i++) {
|
|
212
|
+
const p = texels[i];
|
|
213
|
+
const r = rgba[p] - mr;
|
|
214
|
+
const g = rgba[p + 1] - mg;
|
|
215
|
+
const b = rgba[p + 2] - mb;
|
|
216
|
+
rr += r * r;
|
|
217
|
+
rg += r * g;
|
|
218
|
+
rb += r * b;
|
|
219
|
+
gg += g * g;
|
|
220
|
+
gb += g * b;
|
|
221
|
+
bb += b * b;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Iterated from the pixel farthest from the mean, which no axis is orthogonal to.
|
|
225
|
+
let vr = 0;
|
|
226
|
+
let vg = 0;
|
|
227
|
+
let vb = 0;
|
|
228
|
+
let spread = -1;
|
|
229
|
+
for (let i = 0; i < 16; i++) {
|
|
230
|
+
const p = texels[i];
|
|
231
|
+
const r = rgba[p] - mr;
|
|
232
|
+
const g = rgba[p + 1] - mg;
|
|
233
|
+
const b = rgba[p + 2] - mb;
|
|
234
|
+
if (r * r + g * g + b * b > spread) {
|
|
235
|
+
spread = r * r + g * g + b * b;
|
|
236
|
+
vr = r;
|
|
237
|
+
vg = g;
|
|
238
|
+
vb = b;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (let k = 0; k < 4; k++) {
|
|
243
|
+
const nr = rr * vr + rg * vg + rb * vb;
|
|
244
|
+
const ng = rg * vr + gg * vg + gb * vb;
|
|
245
|
+
const nb = rb * vr + gb * vg + bb * vb;
|
|
246
|
+
const m = Math.max(Math.abs(nr), Math.abs(ng), Math.abs(nb));
|
|
247
|
+
if (m === 0) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
vr = nr / m;
|
|
251
|
+
vg = ng / m;
|
|
252
|
+
vb = nb / m;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// The pixels farthest out along it, either way, are the endpoints.
|
|
256
|
+
let first = texels[0];
|
|
257
|
+
let second = texels[0];
|
|
258
|
+
let least = Infinity;
|
|
259
|
+
let most = -Infinity;
|
|
260
|
+
for (let i = 0; i < 16; i++) {
|
|
261
|
+
const p = texels[i];
|
|
262
|
+
const d = rgba[p] * vr + rgba[p + 1] * vg + rgba[p + 2] * vb;
|
|
263
|
+
if (d < least) {
|
|
264
|
+
least = d;
|
|
265
|
+
second = p;
|
|
266
|
+
}
|
|
267
|
+
if (d > most) {
|
|
268
|
+
most = d;
|
|
269
|
+
first = p;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Four colours need the first endpoint above the second; one alike stays one.
|
|
274
|
+
let c0 = pack(rgba[first], rgba[first + 1], rgba[first + 2]);
|
|
275
|
+
let c1 = pack(rgba[second], rgba[second + 1], rgba[second + 2]);
|
|
276
|
+
if (c0 < c1) {
|
|
277
|
+
const swap = c0;
|
|
278
|
+
c0 = c1;
|
|
279
|
+
c1 = swap;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
palette[0] = rgb(c0);
|
|
283
|
+
palette[1] = rgb(c1);
|
|
284
|
+
palette[2] = mix(palette[0], palette[1], 2, 1, 3);
|
|
285
|
+
palette[3] = mix(palette[0], palette[1], 1, 2, 3);
|
|
286
|
+
const colour = dxt5 ? at + 8 : at;
|
|
287
|
+
out[colour] = c0;
|
|
288
|
+
out[colour + 1] = c0 >> 8;
|
|
289
|
+
out[colour + 2] = c1;
|
|
290
|
+
out[colour + 3] = c1 >> 8;
|
|
291
|
+
if (dxt5) {
|
|
292
|
+
// Eight alphas: the first endpoint above the second, or alike.
|
|
293
|
+
alphas[0] = highA;
|
|
294
|
+
alphas[1] = lowA;
|
|
295
|
+
for (let i = 1; i < 7; i++) {
|
|
296
|
+
alphas[i + 1] = (((7 - i) * highA + i * lowA) / 7) | 0;
|
|
297
|
+
}
|
|
298
|
+
out[at] = highA;
|
|
299
|
+
out[at + 1] = lowA;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let indices = 0;
|
|
303
|
+
let low = 0;
|
|
304
|
+
let high = 0;
|
|
305
|
+
const colours = c0 === c1 ? 1 : 4;
|
|
306
|
+
for (let i = 0; i < 16; i++) {
|
|
307
|
+
const o = texels[i];
|
|
308
|
+
let best = 0;
|
|
309
|
+
let nearest = Infinity;
|
|
310
|
+
for (let p = 0; p < colours; p++) {
|
|
311
|
+
const d = distance(rgba[o], rgba[o + 1], rgba[o + 2], palette[p]);
|
|
312
|
+
if (d < nearest) {
|
|
313
|
+
nearest = d;
|
|
314
|
+
best = p;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
indices |= best << (i * 2);
|
|
318
|
+
if (dxt5) {
|
|
319
|
+
let index = 0;
|
|
320
|
+
for (let p = 1; p < 8; p++) {
|
|
321
|
+
if (Math.abs(rgba[o + 3] - alphas[p]) < Math.abs(rgba[o + 3] - alphas[index])) {
|
|
322
|
+
index = p;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (i < 8) {
|
|
326
|
+
low |= index << (i * 3);
|
|
327
|
+
} else {
|
|
328
|
+
high |= index << ((i - 8) * 3);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
out[colour + 4] = indices;
|
|
334
|
+
out[colour + 5] = indices >>> 8;
|
|
335
|
+
out[colour + 6] = indices >>> 16;
|
|
336
|
+
out[colour + 7] = indices >>> 24;
|
|
337
|
+
if (dxt5) {
|
|
338
|
+
out[at + 2] = low;
|
|
339
|
+
out[at + 3] = low >> 8;
|
|
340
|
+
out[at + 4] = low >> 16;
|
|
341
|
+
out[at + 5] = high;
|
|
342
|
+
out[at + 6] = high >> 8;
|
|
343
|
+
out[at + 7] = high >> 16;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return out.subarray(0, length);
|
|
349
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Backend failures stay independent of the API that exposes them to content. */
|
|
2
|
+
export type Context3DErrorReason =
|
|
3
|
+
| "disposed"
|
|
4
|
+
| "backBuffer"
|
|
5
|
+
| "limit"
|
|
6
|
+
| "bufferAllocation"
|
|
7
|
+
| "textureAllocation"
|
|
8
|
+
| "nativeShader"
|
|
9
|
+
| "program"
|
|
10
|
+
| "indexBuffer"
|
|
11
|
+
| "indexData"
|
|
12
|
+
| "vertexBinding"
|
|
13
|
+
| "vertexStream"
|
|
14
|
+
| "vertexData"
|
|
15
|
+
| "drawRange"
|
|
16
|
+
| "triangleCount"
|
|
17
|
+
| "textureSize"
|
|
18
|
+
| "compressedSize"
|
|
19
|
+
| "textureShape"
|
|
20
|
+
| "texturePowerOfTwo"
|
|
21
|
+
| "textureUpload"
|
|
22
|
+
| "range"
|
|
23
|
+
| "operation"
|
|
24
|
+
| "unsupported"
|
|
25
|
+
| "agalLength"
|
|
26
|
+
| "agalMagic"
|
|
27
|
+
| "agalVersion"
|
|
28
|
+
| "agalStageMarker"
|
|
29
|
+
| "agalVertexStage"
|
|
30
|
+
| "agalFragmentStage"
|
|
31
|
+
| "agalStage"
|
|
32
|
+
| "agalTokens"
|
|
33
|
+
| "agalOpcode"
|
|
34
|
+
| "agalUnusedSource"
|
|
35
|
+
| "agalAttributeStage"
|
|
36
|
+
| "agalAttributeWrite"
|
|
37
|
+
| "agalAttributeIndex"
|
|
38
|
+
| "agalConstantWrite"
|
|
39
|
+
| "agalConstantIndex"
|
|
40
|
+
| "agalTemporaryIndex"
|
|
41
|
+
| "agalTemporaryRead"
|
|
42
|
+
| "agalTemporaryComponent"
|
|
43
|
+
| "agalOutputRead"
|
|
44
|
+
| "agalOutputIndex"
|
|
45
|
+
| "agalVertexOutput"
|
|
46
|
+
| "agalFragmentOutput"
|
|
47
|
+
| "agalColorMask"
|
|
48
|
+
| "agalVaryingIndex"
|
|
49
|
+
| "agalVaryingRead"
|
|
50
|
+
| "agalVaryingWrite"
|
|
51
|
+
| "agalVaryingMissing"
|
|
52
|
+
| "agalVaryingPartial"
|
|
53
|
+
| "agalRegister"
|
|
54
|
+
| "agalIndirect"
|
|
55
|
+
| "agalSourceEncoding"
|
|
56
|
+
| "agalDiscard"
|
|
57
|
+
| "agalMask"
|
|
58
|
+
| "agalReserved"
|
|
59
|
+
| "agalSampler";
|
|
60
|
+
|
|
61
|
+
export class Context3DError extends Error {
|
|
62
|
+
readonly reason: Context3DErrorReason;
|
|
63
|
+
|
|
64
|
+
constructor(reason: Context3DErrorReason, message: string) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.reason = reason;
|
|
67
|
+
}
|
|
68
|
+
}
|