@swf2es/context3d 0.1.19 → 0.1.21
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 +120 -27
- 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/back-buffer.d.ts +6 -0
- package/dist/webgl/back-buffer.d.ts.map +1 -0
- package/dist/webgl/back-buffer.js +197 -0
- package/dist/webgl/back-buffer.js.map +1 -0
- package/dist/webgl/buffers.d.ts +2 -2
- package/dist/webgl/buffers.d.ts.map +1 -1
- package/dist/webgl/buffers.js +105 -2
- package/dist/webgl/buffers.js.map +1 -1
- package/dist/webgl/context.d.ts +29 -48
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +227 -364
- 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/readback.d.ts +3 -0
- package/dist/webgl/readback.d.ts.map +1 -0
- package/dist/webgl/readback.js +40 -0
- package/dist/webgl/readback.js.map +1 -0
- 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/back-buffer.ts +287 -0
- package/src/webgl/buffers.ts +114 -2
- package/src/webgl/context.ts +291 -489
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/readback.ts +54 -0
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +183 -9
package/src/backend.ts
CHANGED
|
@@ -73,10 +73,104 @@ export type SamplerFilter = (typeof SAMPLER_FILTERS)[number];
|
|
|
73
73
|
export const SAMPLER_MIPS = ["mipnone", "mipnearest", "miplinear"] as const;
|
|
74
74
|
export type SamplerMip = (typeof SAMPLER_MIPS)[number];
|
|
75
75
|
|
|
76
|
-
/**
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The texture formats a backend stores: BGRA, S3TC's DXT1 and DXT5 as Flash calls them,
|
|
78
|
+
* 16-bit 5:6:5 colour and 4:4:4:4 colour with alpha, and RGBA in half floats.
|
|
79
|
+
*/
|
|
80
|
+
export const TEXTURE_FORMATS = [
|
|
81
|
+
"bgra",
|
|
82
|
+
"compressed",
|
|
83
|
+
"compressedAlpha",
|
|
84
|
+
"bgrPacked565",
|
|
85
|
+
"bgraPacked4444",
|
|
86
|
+
"rgbaHalfFloat",
|
|
87
|
+
] as const;
|
|
78
88
|
export type TextureFormat = (typeof TEXTURE_FORMATS)[number];
|
|
79
89
|
|
|
90
|
+
/** Bytes a texel of each uncompressed format takes, as its byte uploads give it. */
|
|
91
|
+
export const TEXEL_BYTES: Partial<Record<TextureFormat, number>> = {
|
|
92
|
+
bgra: 4,
|
|
93
|
+
bgrPacked565: 2,
|
|
94
|
+
bgraPacked4444: 2,
|
|
95
|
+
rgbaHalfFloat: 8,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Whether a format holds S3TC blocks, which no draw renders into. */
|
|
99
|
+
export function compressedFormat(format: TextureFormat): boolean {
|
|
100
|
+
return format === "compressed" || format === "compressedAlpha";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Stage3D's profiles, from what the least GPU does to the most. */
|
|
104
|
+
export const PROFILES = [
|
|
105
|
+
"baselineConstrained",
|
|
106
|
+
"baseline",
|
|
107
|
+
"baselineExtended",
|
|
108
|
+
"standardConstrained",
|
|
109
|
+
"standard",
|
|
110
|
+
"standardExtended",
|
|
111
|
+
] as const;
|
|
112
|
+
export type Profile = (typeof PROFILES)[number];
|
|
113
|
+
|
|
114
|
+
/** What a profile allows, as AIR 51 grants it on a desktop GPU (docs/architecture/rendering.md). */
|
|
115
|
+
export interface ProfileLimits {
|
|
116
|
+
/** The newest AGAL version its programs may be. */
|
|
117
|
+
readonly agal: number;
|
|
118
|
+
/** The widest and tallest Texture. */
|
|
119
|
+
readonly textureSize: number;
|
|
120
|
+
/** The widest and tallest RectangleTexture, 0 where the profile has none. */
|
|
121
|
+
readonly rectangleSize: number;
|
|
122
|
+
/** Whether it creates rgbaHalfFloat textures. */
|
|
123
|
+
readonly halfFloat: boolean;
|
|
124
|
+
/** How many textures a draw renders into at once. */
|
|
125
|
+
readonly colorOutputs: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const LIMITS: Record<Profile, ProfileLimits> = {
|
|
129
|
+
baselineConstrained: {
|
|
130
|
+
agal: 1,
|
|
131
|
+
textureSize: 2048,
|
|
132
|
+
rectangleSize: 0,
|
|
133
|
+
halfFloat: false,
|
|
134
|
+
colorOutputs: 1,
|
|
135
|
+
},
|
|
136
|
+
baseline: { agal: 1, textureSize: 2048, rectangleSize: 2048, halfFloat: false, colorOutputs: 1 },
|
|
137
|
+
baselineExtended: {
|
|
138
|
+
agal: 1,
|
|
139
|
+
textureSize: 4096,
|
|
140
|
+
rectangleSize: 4096,
|
|
141
|
+
halfFloat: false,
|
|
142
|
+
colorOutputs: 1,
|
|
143
|
+
},
|
|
144
|
+
standardConstrained: {
|
|
145
|
+
agal: 2,
|
|
146
|
+
textureSize: 4096,
|
|
147
|
+
rectangleSize: 4096,
|
|
148
|
+
halfFloat: true,
|
|
149
|
+
colorOutputs: 1,
|
|
150
|
+
},
|
|
151
|
+
standard: { agal: 2, textureSize: 4096, rectangleSize: 4096, halfFloat: true, colorOutputs: 4 },
|
|
152
|
+
standardExtended: {
|
|
153
|
+
agal: 3,
|
|
154
|
+
textureSize: 4096,
|
|
155
|
+
rectangleSize: 4096,
|
|
156
|
+
halfFloat: true,
|
|
157
|
+
colorOutputs: 4,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export function profileLimits(profile: Profile): ProfileLimits {
|
|
162
|
+
return LIMITS[profile];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Cube faces are at most this wide in every profile. */
|
|
166
|
+
export const MOST_CUBE = 1024;
|
|
167
|
+
|
|
168
|
+
/** Sampler, vertex attribute and constant registers a context holds in every profile. */
|
|
169
|
+
export const SAMPLER_SLOTS = 16;
|
|
170
|
+
export const VERTEX_SLOTS = 16;
|
|
171
|
+
export const VERTEX_CONSTANTS = 250;
|
|
172
|
+
export const FRAGMENT_CONSTANTS = 200;
|
|
173
|
+
|
|
80
174
|
export const VERTEX_FORMATS = ["float1", "float2", "float3", "float4", "bytes4"] as const;
|
|
81
175
|
export type VertexFormat = (typeof VERTEX_FORMATS)[number];
|
|
82
176
|
|
|
@@ -121,6 +215,10 @@ export interface Buffer3D {
|
|
|
121
215
|
}
|
|
122
216
|
|
|
123
217
|
export interface Program3D {
|
|
218
|
+
/** The colour outputs its fragment program writes, 1 to 4; 1 before an upload. */
|
|
219
|
+
readonly outputs: number;
|
|
220
|
+
/** The vertex attributes it reads, a bit each; 0 before an upload. */
|
|
221
|
+
readonly attributes: number;
|
|
124
222
|
upload(vertex: Uint8Array, fragment: Uint8Array): void;
|
|
125
223
|
dispose(): void;
|
|
126
224
|
}
|
|
@@ -166,6 +264,11 @@ export interface Context3DLifecycle {
|
|
|
166
264
|
/** Loss callbacks are asynchronous. Keep the lost surface until remove(), including
|
|
167
265
|
* its restoration listener; remove() must detach both listeners before releasing it. */
|
|
168
266
|
export interface Stage3DHost {
|
|
267
|
+
/**
|
|
268
|
+
* What a request waits for before create(): a host that loads its backend at the first
|
|
269
|
+
* request gives the load, null once loaded. A rejection makes the context unavailable (#3702).
|
|
270
|
+
*/
|
|
271
|
+
ready?(): Promise<unknown> | null;
|
|
169
272
|
create(slot: number, lifecycle: Context3DLifecycle): Context3D;
|
|
170
273
|
place(slot: number, x: number, y: number, visible: boolean): void;
|
|
171
274
|
remove(slot: number): void;
|
|
@@ -180,6 +283,8 @@ export interface Context3D {
|
|
|
180
283
|
* format (TextureBindingError "format"): AIR checks from SWF 17 on.
|
|
181
284
|
*/
|
|
182
285
|
samplerFormats: boolean;
|
|
286
|
+
/** The profile it was made for: what its programs, textures and targets may be. */
|
|
287
|
+
profile: Profile;
|
|
183
288
|
/** Optional acceleration for pixels uploaded to compressed textures. */
|
|
184
289
|
setDxtEncoder?(encoder: DxtEncoder): void;
|
|
185
290
|
/**
|
|
@@ -230,7 +335,7 @@ export interface Context3D {
|
|
|
230
335
|
format?: TextureFormat,
|
|
231
336
|
streaming?: number,
|
|
232
337
|
): Texture3D;
|
|
233
|
-
createRectangleTexture(width: number, height: number): Texture3D;
|
|
338
|
+
createRectangleTexture(width: number, height: number, format?: TextureFormat): Texture3D;
|
|
234
339
|
/** Level-zero target; face selects a cube surface. Null resumes the back buffer. */
|
|
235
340
|
setRenderTarget(
|
|
236
341
|
texture: Texture3D | null,
|
|
@@ -238,6 +343,11 @@ export interface Context3D {
|
|
|
238
343
|
face?: number,
|
|
239
344
|
antiAlias?: number,
|
|
240
345
|
): void;
|
|
346
|
+
/**
|
|
347
|
+
* Colour output `index`, 1 to 3, draws into `texture` (`face` of a cube) beside
|
|
348
|
+
* setRenderTarget's; null stops it. The texture's size and format are the target's.
|
|
349
|
+
*/
|
|
350
|
+
setColorOutput(index: number, texture: Texture3D | null, face?: number): void;
|
|
241
351
|
setSampler(index: number, wrap: SamplerWrap, filter: SamplerFilter, mip: SamplerMip): void;
|
|
242
352
|
setTexture(index: number, texture: Texture3D | null): void;
|
|
243
353
|
setProgram(program: Program3D): void;
|
package/src/errors.ts
CHANGED
|
@@ -56,7 +56,33 @@ export type Context3DErrorReason =
|
|
|
56
56
|
| "agalDiscard"
|
|
57
57
|
| "agalMask"
|
|
58
58
|
| "agalReserved"
|
|
59
|
-
| "agalSampler"
|
|
59
|
+
| "agalSampler"
|
|
60
|
+
| "agalProfileVersion"
|
|
61
|
+
| "agalOpcodeRange"
|
|
62
|
+
| "agalOpcodeVersion"
|
|
63
|
+
| "agalFragmentOnly"
|
|
64
|
+
| "agalSamplerStage"
|
|
65
|
+
| "agalElse"
|
|
66
|
+
| "agalEndIf"
|
|
67
|
+
| "agalEmptyBranch"
|
|
68
|
+
| "agalConditionDestination"
|
|
69
|
+
| "agalScalar"
|
|
70
|
+
| "agalNesting"
|
|
71
|
+
| "agalConditionOutput"
|
|
72
|
+
| "agalConditionTexture"
|
|
73
|
+
| "agalDepthStage"
|
|
74
|
+
| "agalDepthIndex"
|
|
75
|
+
| "agalDepthMask"
|
|
76
|
+
| "agalInstanceStage"
|
|
77
|
+
| "agalOutputTwice"
|
|
78
|
+
| "agalOutputSlots"
|
|
79
|
+
| "agalConditionDerivative"
|
|
80
|
+
| "agalConstants"
|
|
81
|
+
| "agalNativeLength"
|
|
82
|
+
| "agalSamplerIndex"
|
|
83
|
+
| "agalSamplerDimension"
|
|
84
|
+
| "agalSamplerMip"
|
|
85
|
+
| "agalSamplerFilter";
|
|
60
86
|
|
|
61
87
|
export class Context3DError extends Error {
|
|
62
88
|
readonly reason: Context3DErrorReason;
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Stage3D's renderer contracts and AGAL translation, without browser dependencies. */
|
|
2
2
|
|
|
3
3
|
export { type ProgramType, type SamplerSource, type ShaderSource, translate } from "./agal.js";
|
|
4
4
|
export {
|
|
@@ -11,10 +11,18 @@ export {
|
|
|
11
11
|
type Context3DLifecycle,
|
|
12
12
|
CULL_FACES,
|
|
13
13
|
type CullFace,
|
|
14
|
+
compressedFormat,
|
|
15
|
+
FRAGMENT_CONSTANTS,
|
|
14
16
|
MOST_BACK_BUFFER,
|
|
17
|
+
MOST_CUBE,
|
|
18
|
+
PROFILES,
|
|
19
|
+
type Profile,
|
|
20
|
+
type ProfileLimits,
|
|
15
21
|
type Program3D,
|
|
22
|
+
profileLimits,
|
|
16
23
|
SAMPLER_FILTERS,
|
|
17
24
|
SAMPLER_MIPS,
|
|
25
|
+
SAMPLER_SLOTS,
|
|
18
26
|
SAMPLER_WRAPS,
|
|
19
27
|
SamplerBiasError,
|
|
20
28
|
type SamplerFilter,
|
|
@@ -25,11 +33,14 @@ export {
|
|
|
25
33
|
type Stage3DHost,
|
|
26
34
|
type StencilAction,
|
|
27
35
|
screenPixels,
|
|
36
|
+
TEXEL_BYTES,
|
|
28
37
|
TEXTURE_FORMATS,
|
|
29
38
|
type Texture3D,
|
|
30
39
|
TextureBindingError,
|
|
31
40
|
type TextureFormat,
|
|
41
|
+
VERTEX_CONSTANTS,
|
|
32
42
|
VERTEX_FORMATS,
|
|
43
|
+
VERTEX_SLOTS,
|
|
33
44
|
type VertexFormat,
|
|
34
45
|
VertexOffsetError,
|
|
35
46
|
} from "./backend.js";
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// The back buffer: its storage, as large as the GPU gives, its multisample resolve and its
|
|
2
|
+
// presentation to the canvas.
|
|
3
|
+
|
|
4
|
+
import { MOST_BACK_BUFFER } from "../backend.js";
|
|
5
|
+
import { Context3DError } from "../errors.js";
|
|
6
|
+
|
|
7
|
+
import type { WebGLContext3D } from "./context.js";
|
|
8
|
+
import { range } from "./state.js";
|
|
9
|
+
import { selectSamples } from "./targets.js";
|
|
10
|
+
|
|
11
|
+
export function configureBackBuffer(
|
|
12
|
+
ctx: WebGLContext3D,
|
|
13
|
+
width: number,
|
|
14
|
+
height: number,
|
|
15
|
+
depth: boolean,
|
|
16
|
+
antiAlias: number,
|
|
17
|
+
scale: number,
|
|
18
|
+
): void {
|
|
19
|
+
ctx.alive();
|
|
20
|
+
range(32, width - 32, MOST_BACK_BUFFER);
|
|
21
|
+
range(32, height - 32, MOST_BACK_BUFFER);
|
|
22
|
+
if (ctx.renderTexture) {
|
|
23
|
+
throw new Context3DError(
|
|
24
|
+
"operation",
|
|
25
|
+
"Context3D experiment: resize while rendering to texture",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// AIR preserves color, depth and bindings when the request is unchanged.
|
|
30
|
+
// Shadow passes can issue this every frame when returning to the back buffer.
|
|
31
|
+
if (
|
|
32
|
+
ctx.framebuffer &&
|
|
33
|
+
width === ctx.width &&
|
|
34
|
+
height === ctx.height &&
|
|
35
|
+
depth === (ctx.depthBuffer !== null) &&
|
|
36
|
+
antiAlias === ctx.antiAlias
|
|
37
|
+
) {
|
|
38
|
+
if (scale !== ctx.scale) {
|
|
39
|
+
ctx.scale = scale;
|
|
40
|
+
showAt(ctx);
|
|
41
|
+
ctx.applyScissor();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const samples = selectSamples(ctx.gl, antiAlias, depth);
|
|
48
|
+
const gl = ctx.gl;
|
|
49
|
+
const most = storageLimit(ctx);
|
|
50
|
+
const drawnWidth = Math.min(width, most[0]);
|
|
51
|
+
const drawnHeight = Math.min(height, most[1]);
|
|
52
|
+
const texture = gl.createTexture();
|
|
53
|
+
const framebuffer = gl.createFramebuffer();
|
|
54
|
+
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
55
|
+
const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
|
|
56
|
+
const colorBuffer = samples ? gl.createRenderbuffer() : null;
|
|
57
|
+
try {
|
|
58
|
+
if (
|
|
59
|
+
!texture ||
|
|
60
|
+
!framebuffer ||
|
|
61
|
+
(depth && !depthBuffer) ||
|
|
62
|
+
(samples && (!resolveFramebuffer || !colorBuffer))
|
|
63
|
+
) {
|
|
64
|
+
throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx.texturesApplied[0] = 0;
|
|
68
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
69
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
70
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
71
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
72
|
+
gl.texImage2D(
|
|
73
|
+
gl.TEXTURE_2D,
|
|
74
|
+
0,
|
|
75
|
+
gl.RGBA8,
|
|
76
|
+
drawnWidth,
|
|
77
|
+
drawnHeight,
|
|
78
|
+
0,
|
|
79
|
+
gl.RGBA,
|
|
80
|
+
gl.UNSIGNED_BYTE,
|
|
81
|
+
null,
|
|
82
|
+
);
|
|
83
|
+
ctx.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
84
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
85
|
+
if (resolveFramebuffer) {
|
|
86
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
87
|
+
throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ctx.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
91
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
92
|
+
gl.renderbufferStorageMultisample(
|
|
93
|
+
gl.RENDERBUFFER,
|
|
94
|
+
samples,
|
|
95
|
+
gl.RGBA8,
|
|
96
|
+
drawnWidth,
|
|
97
|
+
drawnHeight,
|
|
98
|
+
);
|
|
99
|
+
gl.framebufferRenderbuffer(
|
|
100
|
+
gl.FRAMEBUFFER,
|
|
101
|
+
gl.COLOR_ATTACHMENT0,
|
|
102
|
+
gl.RENDERBUFFER,
|
|
103
|
+
colorBuffer,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (depthBuffer) {
|
|
107
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
108
|
+
if (samples) {
|
|
109
|
+
gl.renderbufferStorageMultisample(
|
|
110
|
+
gl.RENDERBUFFER,
|
|
111
|
+
samples,
|
|
112
|
+
gl.DEPTH24_STENCIL8,
|
|
113
|
+
drawnWidth,
|
|
114
|
+
drawnHeight,
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
118
|
+
}
|
|
119
|
+
gl.framebufferRenderbuffer(
|
|
120
|
+
gl.FRAMEBUFFER,
|
|
121
|
+
gl.DEPTH_STENCIL_ATTACHMENT,
|
|
122
|
+
gl.RENDERBUFFER,
|
|
123
|
+
depthBuffer,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
127
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
128
|
+
throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
gl.deleteTexture(texture);
|
|
132
|
+
gl.deleteFramebuffer(framebuffer);
|
|
133
|
+
gl.deleteFramebuffer(resolveFramebuffer);
|
|
134
|
+
gl.deleteRenderbuffer(depthBuffer);
|
|
135
|
+
gl.deleteRenderbuffer(colorBuffer);
|
|
136
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
137
|
+
ctx.bindFramebuffer(gl.FRAMEBUFFER, ctx.framebuffer);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
gl.deleteTexture(ctx.texture);
|
|
142
|
+
gl.deleteFramebuffer(ctx.framebuffer);
|
|
143
|
+
gl.deleteFramebuffer(ctx.resolveFramebuffer);
|
|
144
|
+
gl.deleteRenderbuffer(ctx.depthBuffer);
|
|
145
|
+
gl.deleteRenderbuffer(ctx.colorBuffer);
|
|
146
|
+
ctx.texture = texture;
|
|
147
|
+
ctx.framebuffer = framebuffer;
|
|
148
|
+
ctx.resolveFramebuffer = resolveFramebuffer;
|
|
149
|
+
ctx.colorBuffer = colorBuffer;
|
|
150
|
+
ctx.depthBuffer = depthBuffer;
|
|
151
|
+
ctx.antiAlias = antiAlias;
|
|
152
|
+
ctx.width = width;
|
|
153
|
+
ctx.height = height;
|
|
154
|
+
ctx.drawnWidth = drawnWidth;
|
|
155
|
+
ctx.drawnHeight = drawnHeight;
|
|
156
|
+
ctx.scale = scale;
|
|
157
|
+
gl.canvas.width = drawnWidth;
|
|
158
|
+
gl.canvas.height = drawnHeight;
|
|
159
|
+
showAt(ctx);
|
|
160
|
+
// Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
|
|
161
|
+
ctx.pixels = new Uint8Array(0);
|
|
162
|
+
ctx.topDown = new Uint8Array(0);
|
|
163
|
+
ctx.applyScissor();
|
|
164
|
+
ctx.clear(0, 0, 0, 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
|
|
169
|
+
* divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
|
|
170
|
+
*/
|
|
171
|
+
function showAt(ctx: WebGLContext3D): void {
|
|
172
|
+
const style = (ctx.gl.canvas as Partial<HTMLCanvasElement>).style;
|
|
173
|
+
if (!style) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const plain = ctx.scale === 1;
|
|
178
|
+
style.width = plain && ctx.drawnWidth === ctx.width ? "" : `${ctx.width / ctx.scale}px`;
|
|
179
|
+
style.height = plain && ctx.drawnHeight === ctx.height ? "" : `${ctx.height / ctx.scale}px`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
|
|
183
|
+
function storageLimit(ctx: WebGLContext3D): Int32Array {
|
|
184
|
+
if (!ctx.gpuMost) {
|
|
185
|
+
const gl = ctx.gl;
|
|
186
|
+
const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
|
|
187
|
+
const side = Math.min(
|
|
188
|
+
gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
|
|
189
|
+
gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
|
|
190
|
+
);
|
|
191
|
+
ctx.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return ctx.gpuMost;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function unconfigureBackBuffer(ctx: WebGLContext3D): void {
|
|
198
|
+
ctx.alive();
|
|
199
|
+
if (ctx.renderTexture) {
|
|
200
|
+
throw new Context3DError(
|
|
201
|
+
"operation",
|
|
202
|
+
"Context3D experiment: resize while rendering to texture",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const gl = ctx.gl;
|
|
207
|
+
gl.deleteTexture(ctx.texture);
|
|
208
|
+
gl.deleteFramebuffer(ctx.framebuffer);
|
|
209
|
+
gl.deleteFramebuffer(ctx.resolveFramebuffer);
|
|
210
|
+
gl.deleteRenderbuffer(ctx.depthBuffer);
|
|
211
|
+
gl.deleteRenderbuffer(ctx.colorBuffer);
|
|
212
|
+
ctx.drawState.forget();
|
|
213
|
+
ctx.texture = null;
|
|
214
|
+
ctx.framebuffer = null;
|
|
215
|
+
ctx.resolveFramebuffer = null;
|
|
216
|
+
ctx.colorBuffer = null;
|
|
217
|
+
ctx.depthBuffer = null;
|
|
218
|
+
ctx.resolveDirty = false;
|
|
219
|
+
ctx.antiAlias = 0;
|
|
220
|
+
ctx.scale = 1;
|
|
221
|
+
ctx.width = 0;
|
|
222
|
+
ctx.height = 0;
|
|
223
|
+
ctx.drawnWidth = 0;
|
|
224
|
+
ctx.drawnHeight = 0;
|
|
225
|
+
ctx.pixels = new Uint8Array(0);
|
|
226
|
+
ctx.topDown = new Uint8Array(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function resolveBackBuffer(ctx: WebGLContext3D): void {
|
|
230
|
+
if (!ctx.resolveFramebuffer) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const gl = ctx.gl;
|
|
235
|
+
if (ctx.resolveDirty) {
|
|
236
|
+
ctx.bindFramebuffer(gl.READ_FRAMEBUFFER, ctx.framebuffer);
|
|
237
|
+
ctx.bindFramebuffer(gl.DRAW_FRAMEBUFFER, ctx.resolveFramebuffer);
|
|
238
|
+
if (ctx.scissor) {
|
|
239
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
240
|
+
}
|
|
241
|
+
gl.blitFramebuffer(
|
|
242
|
+
0,
|
|
243
|
+
0,
|
|
244
|
+
ctx.drawnWidth,
|
|
245
|
+
ctx.drawnHeight,
|
|
246
|
+
0,
|
|
247
|
+
0,
|
|
248
|
+
ctx.drawnWidth,
|
|
249
|
+
ctx.drawnHeight,
|
|
250
|
+
gl.COLOR_BUFFER_BIT,
|
|
251
|
+
gl.NEAREST,
|
|
252
|
+
);
|
|
253
|
+
if (ctx.scissor) {
|
|
254
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
255
|
+
}
|
|
256
|
+
ctx.resolveDirty = false;
|
|
257
|
+
}
|
|
258
|
+
ctx.bindFramebuffer(gl.READ_FRAMEBUFFER, ctx.resolveFramebuffer);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function presentBackBuffer(ctx: WebGLContext3D): void {
|
|
262
|
+
ctx.setRenderTarget(null);
|
|
263
|
+
ctx.target();
|
|
264
|
+
resolveBackBuffer(ctx);
|
|
265
|
+
const gl = ctx.gl;
|
|
266
|
+
ctx.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
|
|
267
|
+
if (ctx.scissor) {
|
|
268
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
269
|
+
}
|
|
270
|
+
// To the drawing buffer the browser gave, which it may have made smaller than the canvas.
|
|
271
|
+
gl.blitFramebuffer(
|
|
272
|
+
0,
|
|
273
|
+
0,
|
|
274
|
+
ctx.drawnWidth,
|
|
275
|
+
ctx.drawnHeight,
|
|
276
|
+
0,
|
|
277
|
+
0,
|
|
278
|
+
gl.drawingBufferWidth,
|
|
279
|
+
gl.drawingBufferHeight,
|
|
280
|
+
gl.COLOR_BUFFER_BIT,
|
|
281
|
+
gl.NEAREST,
|
|
282
|
+
);
|
|
283
|
+
if (ctx.scissor) {
|
|
284
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
285
|
+
}
|
|
286
|
+
gl.flush();
|
|
287
|
+
}
|
package/src/webgl/buffers.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Buffer3D } from "../backend.js";
|
|
2
|
+
import { Context3DError } from "../errors.js";
|
|
3
|
+
|
|
4
|
+
import type { WebGLContext3D } from "./context.js";
|
|
2
5
|
import { range } from "./state.js";
|
|
3
6
|
|
|
4
7
|
export interface BufferState {
|
|
@@ -42,7 +45,7 @@ function uploaded(state: BufferState, start: number, amount: number): void {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
function uploadBuffer(
|
|
46
49
|
gl: WebGL2RenderingContext,
|
|
47
50
|
vao: WebGLVertexArrayObject,
|
|
48
51
|
buffer: Buffer3D,
|
|
@@ -69,7 +72,7 @@ export function uploadBuffer(
|
|
|
69
72
|
gl.bufferSubData(target, start * size * (stride === 0 ? 2 : 4), data);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
function uploadBufferBytes(
|
|
73
76
|
gl: WebGL2RenderingContext,
|
|
74
77
|
vao: WebGLVertexArrayObject,
|
|
75
78
|
buffer: Buffer3D,
|
|
@@ -104,3 +107,112 @@ export function uploadBufferBytes(
|
|
|
104
107
|
gl.bindBuffer(target, handle);
|
|
105
108
|
gl.bufferSubData(target, start * bytesPerEntry, data, offset, length);
|
|
106
109
|
}
|
|
110
|
+
|
|
111
|
+
export function allocateBuffer(
|
|
112
|
+
ctx: WebGLContext3D,
|
|
113
|
+
count: number,
|
|
114
|
+
stride: number,
|
|
115
|
+
dynamic: boolean,
|
|
116
|
+
): Buffer3D {
|
|
117
|
+
ctx.alive();
|
|
118
|
+
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
119
|
+
range(0, stride, 64);
|
|
120
|
+
const countLimit = stride === 0 ? ctx.limits.maxIndexBuffers : ctx.limits.maxVertexBuffers;
|
|
121
|
+
const used = stride === 0 ? ctx.buffers.size - ctx.vertexBuffers : ctx.vertexBuffers;
|
|
122
|
+
if (ctx.buffers.size >= ctx.limits.maxBuffers || used >= countLimit) {
|
|
123
|
+
throw new Context3DError("limit", "Context3D experiment: buffer limit");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const bytes = count * (stride === 0 ? 2 : stride * 4);
|
|
127
|
+
const usedBytes = stride === 0 ? ctx.bufferBytes - ctx.vertexBufferBytes : ctx.vertexBufferBytes;
|
|
128
|
+
const byteLimit = stride === 0 ? ctx.limits.maxIndexBufferBytes : ctx.limits.maxVertexBufferBytes;
|
|
129
|
+
if (ctx.bufferBytes + bytes > ctx.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
|
|
130
|
+
throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const gl = ctx.gl;
|
|
134
|
+
const handle = gl.createBuffer();
|
|
135
|
+
if (!handle) {
|
|
136
|
+
throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
|
|
140
|
+
if (stride === 0) {
|
|
141
|
+
ctx.indexBuffer = null;
|
|
142
|
+
}
|
|
143
|
+
gl.bindVertexArray(ctx.vao);
|
|
144
|
+
gl.bindBuffer(target, handle);
|
|
145
|
+
gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
|
|
146
|
+
const state: BufferState = {
|
|
147
|
+
handle,
|
|
148
|
+
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
149
|
+
uploaded: new Uint8Array(count),
|
|
150
|
+
prefix: 0,
|
|
151
|
+
version: 0,
|
|
152
|
+
checkedVersion: -1,
|
|
153
|
+
checkedFirst: 0,
|
|
154
|
+
checkedCount: 0,
|
|
155
|
+
maximum: -1,
|
|
156
|
+
ranges: null,
|
|
157
|
+
};
|
|
158
|
+
const buffer: Buffer3D = {
|
|
159
|
+
count,
|
|
160
|
+
stride,
|
|
161
|
+
upload: (values, start, amount) => {
|
|
162
|
+
ctx.alive();
|
|
163
|
+
ctx.buffer(buffer);
|
|
164
|
+
try {
|
|
165
|
+
uploadBuffer(gl, ctx.vao, buffer, state, values, start, amount);
|
|
166
|
+
} finally {
|
|
167
|
+
// Source getters can draw before the upload replaces the VAO's index binding.
|
|
168
|
+
if (stride === 0) {
|
|
169
|
+
ctx.indexBuffer = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
uploadBytes: (data, offset, start, amount) => {
|
|
174
|
+
ctx.alive();
|
|
175
|
+
ctx.buffer(buffer);
|
|
176
|
+
try {
|
|
177
|
+
uploadBufferBytes(gl, ctx.vao, buffer, state, data, offset, start, amount);
|
|
178
|
+
} finally {
|
|
179
|
+
if (stride === 0) {
|
|
180
|
+
ctx.indexBuffer = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
dispose: () => {
|
|
185
|
+
if (!ctx.buffers.delete(buffer)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (ctx.indexBuffer === handle) {
|
|
190
|
+
ctx.indexBuffer = null;
|
|
191
|
+
}
|
|
192
|
+
gl.deleteBuffer(handle);
|
|
193
|
+
ctx.bufferBytes -= bytes;
|
|
194
|
+
if (stride !== 0) {
|
|
195
|
+
ctx.vertexBuffers--;
|
|
196
|
+
ctx.vertexBufferBytes -= bytes;
|
|
197
|
+
}
|
|
198
|
+
state.indices = null;
|
|
199
|
+
state.ranges = null;
|
|
200
|
+
state.uploaded = new Uint8Array(0);
|
|
201
|
+
for (let i = 0; i < ctx.vertices.length; i++) {
|
|
202
|
+
const binding = ctx.vertices[i];
|
|
203
|
+
if (binding?.buffer === buffer) {
|
|
204
|
+
ctx.vertices[i] = null;
|
|
205
|
+
} else if (binding?.checkedIndex === state) {
|
|
206
|
+
binding.checkedIndex = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
ctx.buffers.set(buffer, state);
|
|
212
|
+
if (stride !== 0) {
|
|
213
|
+
ctx.vertexBuffers++;
|
|
214
|
+
ctx.vertexBufferBytes += bytes;
|
|
215
|
+
}
|
|
216
|
+
ctx.bufferBytes += bytes;
|
|
217
|
+
return buffer;
|
|
218
|
+
}
|