@godot-scene-web/canvas 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 +21 -0
- package/dist/glyph-pass-DfQlp_IH.d.ts +882 -0
- package/dist/glyph-pass-DfQlp_IH.d.ts.map +1 -0
- package/dist/glyph-pass-hbgpu.d.ts +227 -0
- package/dist/glyph-pass-hbgpu.d.ts.map +1 -0
- package/dist/glyph-pass-hbgpu.js +387 -0
- package/dist/glyph-pass-hbgpu.js.map +1 -0
- package/dist/index.d.ts +1085 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4763 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
import { $ as DamageRect, A as DrawListPatchView, B as ScreenEffectDrawContext, C as DRAW_TEXTURED_MESH, D as DrawListFragment, E as DrawList, F as GlyphsView, G as createDrawListPatchView, H as createClipRectView, I as NinePatchView, J as createPolylineView, K as createGlyphsView, L as PolylineView, M as ExternalEffectDrawContext, N as FLIP_H, O as DrawListFragmentPatch, P as FLIP_V, Q as CommandBounds, R as QuadView, S as DRAW_SCREEN_EFFECT, T as DrawCommandName, U as createDrawList, V as TexturedMeshView, W as createDrawListFragment, X as createTexturedMeshView, Y as createQuadView, Z as setViewColorMatrix, _ as DRAW_EXTERNAL_EFFECT, a as StageCanvas, at as RETAINED_MAX_DAMAGE_COVERAGE, b as DRAW_POLYLINE, c as BLEND_ADD, ct as createDamageTiles, d as BLEND_SUB, dt as outsetDamageRect, et as DamageTile, f as BlendMode, ft as transformDamageRect, g as DRAW_COMMAND_NAMES, h as DRAW_CLIP_PUSH, i as STAGE_CONTEXT_ATTRIBUTES, it as RETAINED_DAMAGE_TILE_SIZE, j as ExternalEffectDrawCommand, k as DrawListOptions, l as BLEND_MIX, lt as damageIntersects, m as DRAW_CLIP_POP, n as CanvasStage, nt as DamageTransform, o as StageProjection, ot as commandDamageBounds, p as ClipRectView, pt as unionDamageRect, q as createNinePatchView, r as CanvasStageOptions, rt as DirtyRect, s as createCanvasStage, st as createDamageRect, t as GlyphPass, tt as DamageTiles, u as BLEND_MUL, ut as isDamageEmpty, v as DRAW_GLYPHS, w as DrawCommandKind, x as DRAW_QUAD, y as DRAW_NINE_PATCH, z as ScreenEffectDrawCommand } from "./glyph-pass-DfQlp_IH.js";
|
|
2
|
+
import { HeadlessGodotParticleDirectPass, HeadlessGodotParticleDirectRenderInput, HeadlessShaderProducer, HeadlessShaderRenderInput } from "@godot-scene-web/canvas-effects/webgl";
|
|
3
|
+
|
|
4
|
+
//#region src/batcher.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The quad batcher: everything the executor knows about MERGING draws, with no
|
|
7
|
+
* GL in it, so the flush decisions — the thing that decides whether a frame is 20
|
|
8
|
+
* draw calls or 200 — are unit-testable rather than inferred from a profiler.
|
|
9
|
+
*
|
|
10
|
+
* WHY MULTI-TEXTURE BATCHING IS THE WHOLE POINT. Probing recorded scenes for
|
|
11
|
+
* batch runs (a run = a maximal span of consecutive painting nodes sharing
|
|
12
|
+
* texture + blend + clip + colour-matrix state) found combat at 152 runs over 183
|
|
13
|
+
* painting nodes — 1.2 nodes per batch, i.e. essentially no batching — and 151 of
|
|
14
|
+
* those 152 breaks were the TEXTURE alone. The map is the same story: 174 runs
|
|
15
|
+
* over 825 nodes, 173 texture breaks. But the number of DISTINCT textures per
|
|
16
|
+
* screen is only 24-65. So a batch that can hold many textures at once collapses
|
|
17
|
+
* the run count towards the number of times the state that a batch CANNOT hold
|
|
18
|
+
* changes — blend (0-16 per screen) and clip (0-26) — plus one flush per
|
|
19
|
+
* texture-table refill. That is the difference between "tens of draws" and "one
|
|
20
|
+
* draw per node", and it is why the slot table below is not an optimization to
|
|
21
|
+
* add later.
|
|
22
|
+
*
|
|
23
|
+
* HOW A TEXTURE STOPS BREAKING A BATCH. Each batch binds up to
|
|
24
|
+
* `maxTextureSlots` textures to consecutive texture units and each quad carries
|
|
25
|
+
* the INDEX of the one it samples. The fragment shader turns that index back into
|
|
26
|
+
* a sampler with a compiled `if` ladder (GLSL ES 3.00 forbids indexing a sampler
|
|
27
|
+
* array with anything but a constant). A quad whose texture is not in the table
|
|
28
|
+
* takes the next free slot; when the table is full the batch flushes and starts a
|
|
29
|
+
* new table.
|
|
30
|
+
*
|
|
31
|
+
* COLOUR MATRICES GET THE SAME TREATMENT, for the same reason at a smaller scale:
|
|
32
|
+
* the card screens carry 30-48 HSV-transformed quads, and one draw call each
|
|
33
|
+
* would undo the texture win. A batch holds up to `maxColorMatrices` matrices in
|
|
34
|
+
* a uniform array with the IDENTITY pinned at slot 0, so "no matrix" costs
|
|
35
|
+
* nothing and needs no separate program. Identical matrices share a slot (the
|
|
36
|
+
* table is deduped by value), which matters because those 30-48 quads are usually
|
|
37
|
+
* a handful of distinct tints applied to many cards.
|
|
38
|
+
*
|
|
39
|
+
* ORDER IS NEVER REORDERED. Instances are drawn in the order they were pushed —
|
|
40
|
+
* `drawArraysInstanced` rasterizes instance N before instance N+1, which is the
|
|
41
|
+
* property a painter's-algorithm 2D renderer with no depth buffer depends on. So
|
|
42
|
+
* this batcher only ever MERGES CONSECUTIVE commands; it never sorts, and it can
|
|
43
|
+
* therefore be dropped in front of any draw list without changing what the frame
|
|
44
|
+
* looks like. Bucketing non-adjacent commands by texture is a separate,
|
|
45
|
+
* order-unsafe transformation that belongs to whoever BUILDS the list and knows
|
|
46
|
+
* which spans are safe to permute.
|
|
47
|
+
*
|
|
48
|
+
* FOUR EXPLICIT CORNERS, NOT AN AFFINE BASIS. An instance carries `p0..p3`
|
|
49
|
+
* outright (8 floats) rather than a 2x3 transform (6). The two extra floats buy
|
|
50
|
+
* arbitrary quadrilaterals, which is what lets `./polyline`'s stroke segments AND
|
|
51
|
+
* its join wedges (a triangle spelled as a quad with two coincident corners) ride
|
|
52
|
+
* in the same buffer, the same shader and the same batch as every sprite. The
|
|
53
|
+
* alternative — a second program and a mid-frame break for every polyline — costs
|
|
54
|
+
* far more than 8 bytes per quad. The consequence to know about: UV interpolates
|
|
55
|
+
* affinely per triangle, so a NON-parallelogram textured quad would show a seam
|
|
56
|
+
* along the split diagonal. Nothing produces one (sprites and nine-patch bands
|
|
57
|
+
* are affine images of a rect; the non-affine quads are untextured stroke
|
|
58
|
+
* geometry), and if something ever does, the fix is to split it rather than to
|
|
59
|
+
* make every quad pay for projective interpolation.
|
|
60
|
+
*/
|
|
61
|
+
/** Floats per instance. See the field offsets below for the layout. */
|
|
62
|
+
declare const INSTANCE_FLOATS = 18;
|
|
63
|
+
/** Offset of `p0.x` — four `(x, y)` corners, in the unit-square order
|
|
64
|
+
* `(0,0)`, `(1,0)`, `(1,1)`, `(0,1)`. Design space. */
|
|
65
|
+
declare const INSTANCE_CORNERS_OFFSET = 0;
|
|
66
|
+
/** Offset of the normalized source rect `(u0, v0, uSpan, vSpan)`. A span is
|
|
67
|
+
* NEGATIVE for a flipped axis, which is how `FLIP_H`/`FLIP_V` are carried. */
|
|
68
|
+
declare const INSTANCE_UV_OFFSET = 8;
|
|
69
|
+
/** Offset of the PREMULTIPLIED tint, `(r, g, b, a)`. */
|
|
70
|
+
declare const INSTANCE_COLOR_OFFSET = 12;
|
|
71
|
+
/** Offset of `(textureSlot, colorMatrixSlot)`. Matrix slot 0 is the identity. */
|
|
72
|
+
declare const INSTANCE_SLOTS_OFFSET = 16;
|
|
73
|
+
/** Floats per colour-matrix slot. */
|
|
74
|
+
declare const COLOR_MATRIX_FLOATS = 9;
|
|
75
|
+
/** The most texture units this batcher will ever ask for, whatever the GPU
|
|
76
|
+
* reports. Sixteen is the WebGL2 (GLES 3.0) guaranteed minimum for
|
|
77
|
+
* `MAX_TEXTURE_IMAGE_UNITS`, so asking for more buys a shader that some
|
|
78
|
+
* conformant device cannot link, in exchange for a batch boundary the measured
|
|
79
|
+
* key counts (24-65 distinct textures per screen) would still hit. */
|
|
80
|
+
declare const MAX_TEXTURE_SLOTS = 16;
|
|
81
|
+
/** Default colour-matrix table size, including the identity at slot 0. */
|
|
82
|
+
declare const DEFAULT_COLOR_MATRIX_SLOTS = 16;
|
|
83
|
+
/** What a batch binds to a texture unit. Structural on purpose: the batcher only
|
|
84
|
+
* needs the handle's IDENTITY to slot it, and a test can hand it a stand-in. */
|
|
85
|
+
interface BatchTexture {
|
|
86
|
+
readonly texture: WebGLTexture;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The staging view a caller fills before {@link QuadBatcher.push}. Owned by the
|
|
90
|
+
* batcher and reused, so a frame's worth of quads allocates nothing.
|
|
91
|
+
*/
|
|
92
|
+
interface QuadInstance {
|
|
93
|
+
/** Corner at unit-square `(0, 0)`, design space. */
|
|
94
|
+
x0: number;
|
|
95
|
+
y0: number;
|
|
96
|
+
/** Corner at `(1, 0)`. */
|
|
97
|
+
x1: number;
|
|
98
|
+
y1: number;
|
|
99
|
+
/** Corner at `(1, 1)`. */
|
|
100
|
+
x2: number;
|
|
101
|
+
y2: number;
|
|
102
|
+
/** Corner at `(0, 1)`. */
|
|
103
|
+
x3: number;
|
|
104
|
+
y3: number;
|
|
105
|
+
/** Normalized source origin (the texel under corner `(0, 0)`). */
|
|
106
|
+
u0: number;
|
|
107
|
+
v0: number;
|
|
108
|
+
/** Normalized source span; negative mirrors the axis. */
|
|
109
|
+
uSpan: number;
|
|
110
|
+
vSpan: number;
|
|
111
|
+
/** PREMULTIPLIED tint. */
|
|
112
|
+
r: number;
|
|
113
|
+
g: number;
|
|
114
|
+
b: number;
|
|
115
|
+
a: number;
|
|
116
|
+
}
|
|
117
|
+
declare function createQuadInstance(): QuadInstance;
|
|
118
|
+
type BatchFlushReason = /** The texture table was full and the next quad wanted a texture not in it. */"textureSlots" /** The colour-matrix table was full and the next quad wanted a new matrix. */ | "colorMatrices" /** The blend mode changed; GL blend state is per-draw, not per-instance. */ | "blend" /** The clip scope changed; the scissor box is per-draw too. */ | "clip"
|
|
119
|
+
/**
|
|
120
|
+
* A glyph run interrupted the quads.
|
|
121
|
+
*
|
|
122
|
+
* Not a state change this batcher could hold: the glyph pass replaces the program and the vertex
|
|
123
|
+
* array outright, so the pending batch has to be DRAWN under the executor's state before the
|
|
124
|
+
* pass runs. Counted separately from `blend` and `clip` because it is the one break a consumer
|
|
125
|
+
* can remove by moving text, and a frame whose batching regressed should say so by axis.
|
|
126
|
+
*/
|
|
127
|
+
| "glyphs" /** A screen-dependent effect interrupted the instanced-quad program. */ | "effects" /** An indexed mesh interrupted the instanced-quad program. */ | "meshes" /** A retained compiled GPU run takes over after direct quad staging. */ | "compiled" /** End of the draw list (or an explicit flush by the caller). */ | "end";
|
|
128
|
+
/**
|
|
129
|
+
* One batch, handed to the sink. Every array is the batcher's own live storage —
|
|
130
|
+
* valid only for the duration of the call, and only over the stated prefix.
|
|
131
|
+
*/
|
|
132
|
+
interface Batch {
|
|
133
|
+
/** Instance data: `quadCount * INSTANCE_FLOATS` floats from index 0. */
|
|
134
|
+
readonly instances: Float32Array;
|
|
135
|
+
readonly quadCount: number;
|
|
136
|
+
/** Texture table. Entries `0 .. textureCount - 1` are the units to bind;
|
|
137
|
+
* everything past that is a stale `null` slot and must not be read. */
|
|
138
|
+
readonly textures: readonly (BatchTexture | null)[];
|
|
139
|
+
readonly textureCount: number;
|
|
140
|
+
/** `colorMatrixCount * COLOR_MATRIX_FLOATS` floats; slot 0 is the identity. */
|
|
141
|
+
readonly colorMatrices: Float32Array;
|
|
142
|
+
readonly colorMatrixCount: number;
|
|
143
|
+
readonly blend: BlendMode;
|
|
144
|
+
/** The clip scope this batch was accumulated under (see `./clip-stack`). */
|
|
145
|
+
readonly clipEpoch: number;
|
|
146
|
+
readonly reason: BatchFlushReason;
|
|
147
|
+
}
|
|
148
|
+
interface BatcherStats {
|
|
149
|
+
/** Batches emitted, i.e. draw calls the executor issues for quads. */
|
|
150
|
+
batches: number;
|
|
151
|
+
/** Instances pushed. */
|
|
152
|
+
quads: number;
|
|
153
|
+
/** Texture-unit binds — `textureCount` summed over batches. */
|
|
154
|
+
textureBinds: number;
|
|
155
|
+
/** Largest single batch, in instances. */
|
|
156
|
+
maxBatchQuads: number;
|
|
157
|
+
/** Times the instance arena had to grow (should settle at zero). */
|
|
158
|
+
arenaGrowths: number;
|
|
159
|
+
flushes: Record<BatchFlushReason, number>;
|
|
160
|
+
}
|
|
161
|
+
interface QuadBatcherOptions {
|
|
162
|
+
/** Where a finished batch goes. Called synchronously from `push`/`flush`. */
|
|
163
|
+
draw(batch: Batch): void;
|
|
164
|
+
/** Texture units per batch; clamped to `[1, MAX_TEXTURE_SLOTS]`. */
|
|
165
|
+
maxTextureSlots?: number;
|
|
166
|
+
/** Colour-matrix slots per batch INCLUDING the identity; at least 1. */
|
|
167
|
+
maxColorMatrices?: number;
|
|
168
|
+
/** Initial instance-arena capacity, in quads. */
|
|
169
|
+
quadCapacity?: number;
|
|
170
|
+
}
|
|
171
|
+
interface QuadBatcher {
|
|
172
|
+
readonly maxTextureSlots: number;
|
|
173
|
+
readonly maxColorMatrices: number;
|
|
174
|
+
/** Instances accumulated in the OPEN batch. */
|
|
175
|
+
readonly quadCount: number;
|
|
176
|
+
/** Texture slots taken in the open batch. */
|
|
177
|
+
readonly textureCount: number;
|
|
178
|
+
/** Colour-matrix slots taken in the open batch, including the identity. */
|
|
179
|
+
readonly colorMatrixCount: number;
|
|
180
|
+
readonly blend: BlendMode;
|
|
181
|
+
readonly stats: BatcherStats;
|
|
182
|
+
/** The reusable staging instance; fill it, then call {@link QuadBatcher.push}. */
|
|
183
|
+
readonly quad: QuadInstance;
|
|
184
|
+
/** Start a frame: drop any open batch WITHOUT drawing it, and zero the stats. */
|
|
185
|
+
reset(): void;
|
|
186
|
+
/** Flush first if the mode differs, then adopt it. */
|
|
187
|
+
setBlend(blend: BlendMode): void;
|
|
188
|
+
/** Flush first if the scope differs, then adopt it. */
|
|
189
|
+
setClipEpoch(epoch: number): void;
|
|
190
|
+
/**
|
|
191
|
+
* Commit {@link QuadBatcher.quad}. `colorMatrix` is 9 row-major floats at
|
|
192
|
+
* `colorMatrixOffset`, or `null` for the identity (which costs no slot).
|
|
193
|
+
*/
|
|
194
|
+
push(texture: BatchTexture, colorMatrix?: ArrayLike<number> | null, colorMatrixOffset?: number): void;
|
|
195
|
+
/** Emit the open batch, if it has anything in it. */
|
|
196
|
+
flush(reason?: BatchFlushReason): void;
|
|
197
|
+
}
|
|
198
|
+
declare function createQuadBatcher(options: QuadBatcherOptions): QuadBatcher;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/clip-stack.d.ts
|
|
201
|
+
/**
|
|
202
|
+
* The executor's clip scope: a stack of design-space rects, resolved to a GL
|
|
203
|
+
* scissor box and (for the rare rounded clip) to a pair of fragment uniforms.
|
|
204
|
+
*
|
|
205
|
+
* WHY DESIGN SPACE, INTERSECTED BEFORE INTEGERIZING. Each level's rect is
|
|
206
|
+
* intersected with its parent as FLOATS, in the scene's own coordinates, and only
|
|
207
|
+
* the final result is mapped to framebuffer pixels and snapped. Integerizing at
|
|
208
|
+
* every level instead would round the same edge repeatedly, and rounding OUTWARDS
|
|
209
|
+
* (which is what a clip must do — see below) compounds: three nested clips on the
|
|
210
|
+
* same edge would leak up to three pixels.
|
|
211
|
+
*
|
|
212
|
+
* WHY `floor(min)` / `ceil(max)`. A scissor box is whole pixels; the clip it
|
|
213
|
+
* approximates is not. Rounding outwards keeps every pixel the clip PARTIALLY
|
|
214
|
+
* covers, so content is never sheared off by a sub-pixel; the cost is that up to
|
|
215
|
+
* one pixel of overdraw survives on each edge. The other choice (round inwards)
|
|
216
|
+
* eats a visible line off the edge of every scrolling list, which is the failure
|
|
217
|
+
* a reader will actually notice.
|
|
218
|
+
*
|
|
219
|
+
* THE FLIPPED SCISSOR ORIGIN. `gl.scissor` measures Y from the BOTTOM of the
|
|
220
|
+
* drawing buffer; the design space here — like every 2D scene — measures it from
|
|
221
|
+
* the top. So the box's Y is `framebufferHeight - bottomEdge`, not `topEdge`, and
|
|
222
|
+
* getting it wrong produces a clip that is correct in size, correct in X, and
|
|
223
|
+
* mirrored about the middle of the screen — which looks like a layout bug rather
|
|
224
|
+
* than a scissor bug. `clip-stack.test.ts` pins the algebra and the pixel test
|
|
225
|
+
* `nested clips` pins it on a real GPU.
|
|
226
|
+
*
|
|
227
|
+
* ROUNDED CORNERS ARE FRAGMENT WORK, and only for the INNERMOST rounded clip. A
|
|
228
|
+
* scissor cannot express a radius, and a stencil pass per rounded scope would
|
|
229
|
+
* cost more than the feature is worth at the measured population (rounded clips
|
|
230
|
+
* are a handful per screen, nested rounded clips none). So the stack tracks the
|
|
231
|
+
* deepest rounded rect currently open and hands it to the shader as a rounded-rect
|
|
232
|
+
* distance test; every ancestor still clips squarely through the scissor, which is
|
|
233
|
+
* exact for all of them except an outer rounded one's four corners.
|
|
234
|
+
*
|
|
235
|
+
* NON-AXIS-ALIGNED CLIPS FALL BACK TO THEIR AABB. A scissor box is axis-aligned,
|
|
236
|
+
* so a clip can only be exact while the design->framebuffer transform is a scale
|
|
237
|
+
* and a translate. It always is today (see `./present`), and the measured
|
|
238
|
+
* population of rotated clips across the recorded scenes is zero — so rather than
|
|
239
|
+
* carry a stencil path for a case that does not occur, a transform with rotation
|
|
240
|
+
* or skew clips to the transformed rect's bounding box and increments
|
|
241
|
+
* {@link ClipStack.rotatedFallbacks}. A caller that ever sees that counter move
|
|
242
|
+
* has found the case that justifies the stencil.
|
|
243
|
+
*/
|
|
244
|
+
/** A resolved clip rect in DESIGN space. */
|
|
245
|
+
interface ClipBounds {
|
|
246
|
+
minX: number;
|
|
247
|
+
minY: number;
|
|
248
|
+
maxX: number;
|
|
249
|
+
maxY: number;
|
|
250
|
+
}
|
|
251
|
+
/** The innermost rounded clip, in DESIGN space; `radius <= 0` means none is open. */
|
|
252
|
+
interface RoundedClip {
|
|
253
|
+
centerX: number;
|
|
254
|
+
centerY: number;
|
|
255
|
+
halfWidth: number;
|
|
256
|
+
halfHeight: number;
|
|
257
|
+
radius: number;
|
|
258
|
+
}
|
|
259
|
+
/** A GL scissor box: framebuffer pixels, origin BOTTOM-LEFT. */
|
|
260
|
+
interface ScissorBox {
|
|
261
|
+
x: number;
|
|
262
|
+
y: number;
|
|
263
|
+
width: number;
|
|
264
|
+
height: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* A design->framebuffer-pixel affine, in the draw-list's `Transform2D` order
|
|
268
|
+
* `[xx, xy, yx, yy, originX, originY]`: `px = xx*x + yx*y + ox`, `py = xy*x +
|
|
269
|
+
* yy*y + oy`, with `py` measured DOWN from the top of the buffer.
|
|
270
|
+
*/
|
|
271
|
+
type PixelTransform = ArrayLike<number>;
|
|
272
|
+
interface ClipStack {
|
|
273
|
+
/** Open clip scopes. */
|
|
274
|
+
readonly depth: number;
|
|
275
|
+
/** Bumped by every push/pop/reset: the batcher's cheap "did the scope change". */
|
|
276
|
+
readonly epoch: number;
|
|
277
|
+
/** Clips that could not be expressed as an axis-aligned scissor (see the module note). */
|
|
278
|
+
readonly rotatedFallbacks: number;
|
|
279
|
+
/** The intersected clip in design space, or `null` when nothing is clipped. */
|
|
280
|
+
bounds(): ClipBounds | null;
|
|
281
|
+
/** The innermost rounded clip, or `null`. */
|
|
282
|
+
rounded(): RoundedClip | null;
|
|
283
|
+
push(clip: ClipRectView): void;
|
|
284
|
+
pop(): void;
|
|
285
|
+
/** Start a frame: drop every scope (a list may end unbalanced) AND the frame's
|
|
286
|
+
* {@link ClipStack.rotatedFallbacks} count, which is per-frame like every
|
|
287
|
+
* other executor statistic. */
|
|
288
|
+
reset(): void;
|
|
289
|
+
/**
|
|
290
|
+
* The current clip as a scissor box against a `width`x`height` framebuffer,
|
|
291
|
+
* written into `out`. With nothing clipped this is the whole framebuffer.
|
|
292
|
+
*/
|
|
293
|
+
scissor(transform: PixelTransform, width: number, height: number, out: ScissorBox): ScissorBox;
|
|
294
|
+
}
|
|
295
|
+
declare function createScissorBox(): ScissorBox;
|
|
296
|
+
/** True when `transform` is a pure scale + translate, i.e. a scissor can be exact. */
|
|
297
|
+
declare function isAxisAligned(transform: PixelTransform): boolean;
|
|
298
|
+
declare function createClipStack(): ClipStack;
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/color.d.ts
|
|
301
|
+
/**
|
|
302
|
+
* Premultiplied colour composition — the arithmetic the batcher writes into an
|
|
303
|
+
* instance and the fragment shader repeats on the GPU, in one place so the two
|
|
304
|
+
* can be checked against each other (and against `html`'s CPU tint bake).
|
|
305
|
+
*
|
|
306
|
+
* TWO CONVENTIONS MEET HERE, and mixing them up is the classic renderer bug:
|
|
307
|
+
*
|
|
308
|
+
* - a **premultiplied** colour carries `(r·a, g·a, b·a, a)`. That is what the
|
|
309
|
+
* draw-list's tints are, what every texture in {@link ./textures} is uploaded
|
|
310
|
+
* as, what the fragment emits, and what the canvas is declared as. Composing
|
|
311
|
+
* two premultiplied colours is a plain componentwise multiply.
|
|
312
|
+
* - a **straight** colour carries `(r, g, b, a)` with the channels independent.
|
|
313
|
+
* The only thing that wants straight colour is the 3x3 colour matrix, because
|
|
314
|
+
* the matrix is defined on the texture's own RGB — multiply a premultiplied
|
|
315
|
+
* colour by it and a 50%-alpha pixel is transformed as if it were half as
|
|
316
|
+
* bright.
|
|
317
|
+
*
|
|
318
|
+
* Everything below is sRGB-domain: no linearization anywhere. That is not a
|
|
319
|
+
* shortcut, it is the contract — the matrices come from Godot HSV materials that
|
|
320
|
+
* Godot itself applies to sRGB texture bytes, and `html`'s
|
|
321
|
+
* `applyColorMatrixToPixels` (the CPU bake of the same transform, used by the DOM
|
|
322
|
+
* renderer) applies them to sRGB bytes too. Linearizing here would make the GPU
|
|
323
|
+
* path disagree with the DOM path it is meant to replace.
|
|
324
|
+
*/
|
|
325
|
+
/** Row-major 3x3 identity, the value slot 0 of a batch's matrix table holds. */
|
|
326
|
+
declare const IDENTITY_COLOR_MATRIX: readonly number[];
|
|
327
|
+
/** A straight or premultiplied RGBA colour, 0..1 per channel. */
|
|
328
|
+
interface Rgba {
|
|
329
|
+
r: number;
|
|
330
|
+
g: number;
|
|
331
|
+
b: number;
|
|
332
|
+
a: number;
|
|
333
|
+
}
|
|
334
|
+
declare function createRgba(): Rgba;
|
|
335
|
+
declare function clamp01(value: number): number;
|
|
336
|
+
/**
|
|
337
|
+
* Straight `(r,g,b,a)` -> premultiplied, clamped. The shape a producer has (a
|
|
338
|
+
* Godot `modulate` is straight) turned into the shape {@link QuadView} wants.
|
|
339
|
+
*/
|
|
340
|
+
declare function premultiply(r: number, g: number, b: number, a: number, out: Rgba): Rgba;
|
|
341
|
+
/**
|
|
342
|
+
* Premultiplied -> straight, with the `a === 0` hole filled with black. The
|
|
343
|
+
* inverse of {@link premultiply} up to that hole (a fully transparent
|
|
344
|
+
* premultiplied pixel has forgotten its colour, so nothing can recover it).
|
|
345
|
+
*/
|
|
346
|
+
declare function unpremultiply(colour: Rgba, out: Rgba): Rgba;
|
|
347
|
+
/**
|
|
348
|
+
* Compose two PREMULTIPLIED colours — a texel times its quad's tint. A plain
|
|
349
|
+
* componentwise multiply, which is the whole reason the premultiplied form is
|
|
350
|
+
* worth keeping: with straight colours this would need the alpha handled apart
|
|
351
|
+
* from the channels it has already scaled.
|
|
352
|
+
*/
|
|
353
|
+
declare function modulatePremultiplied(source: Rgba, tint: Rgba, out: Rgba): Rgba;
|
|
354
|
+
/**
|
|
355
|
+
* Apply a row-major 3x3 to STRAIGHT sRGB channels in 0..1, clamped — the float
|
|
356
|
+
* twin of `html`'s `applyColorMatrixToPixels` (which works in 0..255 bytes and
|
|
357
|
+
* clamps because it writes a `Uint8ClampedArray`) and of the colour-matrix branch
|
|
358
|
+
* in the executor's fragment shader. All three must agree; `colour.test.ts`
|
|
359
|
+
* asserts the first two against each other pixel for pixel.
|
|
360
|
+
*
|
|
361
|
+
* Alpha is untouched, exactly as `feColorMatrix` with only the RGB rows set.
|
|
362
|
+
*/
|
|
363
|
+
declare function applyColorMatrix01(colour: Rgba, matrix: ArrayLike<number>, offset: number, out: Rgba): Rgba;
|
|
364
|
+
/**
|
|
365
|
+
* The full per-fragment colour law, on the CPU: a PREMULTIPLIED texel, its
|
|
366
|
+
* optional colour matrix, and the quad's PREMULTIPLIED tint, in the order the
|
|
367
|
+
* shader applies them (un-premultiply, transform, re-premultiply, modulate).
|
|
368
|
+
*
|
|
369
|
+
* This exists so a pixel test can state the number it expects from the same
|
|
370
|
+
* expression the GPU evaluates rather than from a second, hand-derived one.
|
|
371
|
+
*/
|
|
372
|
+
declare function shadeQuadPixel(texel: Rgba, matrix: ArrayLike<number> | null, matrixOffset: number, tint: Rgba, out: Rgba): Rgba;
|
|
373
|
+
/** True when the 9 floats at `offset` are the identity, i.e. a no-op slot. */
|
|
374
|
+
declare function isIdentityColorMatrix(matrix: ArrayLike<number>, offset?: number): boolean;
|
|
375
|
+
/**
|
|
376
|
+
* True when the 9 floats at `a`/`b` are equal — the batcher's matrix-table
|
|
377
|
+
* dedupe. EXACT equality, deliberately: the table's job is to notice that many
|
|
378
|
+
* cards carry the same computed tint, and an epsilon would merge two tints a
|
|
379
|
+
* scene meant to differ. The corollary is that a `Float32Array` and a plain
|
|
380
|
+
* `number[]` holding "the same" value do not match (`0.3` and its f32 round-trip
|
|
381
|
+
* are different numbers), so a caller that wants dedupe should keep one storage
|
|
382
|
+
* width — which the draw-list and this package both do.
|
|
383
|
+
*/
|
|
384
|
+
declare function colorMatricesEqual(a: ArrayLike<number>, aOffset: number, b: ArrayLike<number>, bOffset: number): boolean;
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/replay.d.ts
|
|
387
|
+
/** A sparse ordered command selection. The executor walks the original list in
|
|
388
|
+
* order, so batching and painter ordering remain exactly the direct path's. */
|
|
389
|
+
interface CommandMask {
|
|
390
|
+
readonly count: number;
|
|
391
|
+
/** A screen-dependent command exists; partial retained replay must decline. */
|
|
392
|
+
readonly requiresFullReplay?: boolean;
|
|
393
|
+
includes(index: number): boolean;
|
|
394
|
+
indices(): readonly number[];
|
|
395
|
+
}
|
|
396
|
+
/** Retained replay must stay below this fraction of the complete painter list. */
|
|
397
|
+
declare const RETAINED_MAX_REPLAY_FRACTION = 0.4;
|
|
398
|
+
/**
|
|
399
|
+
* Largest command count that is strictly below the retained replay threshold.
|
|
400
|
+
* Clip pushes/pops are commands too: omitting them would not restore the
|
|
401
|
+
* original painter state, so they count against the same budget.
|
|
402
|
+
*/
|
|
403
|
+
declare function maxPartialReplayCommands(commandCount: number): number;
|
|
404
|
+
declare const REPLAY_MASK_SCRATCH: unique symbol;
|
|
405
|
+
interface ReplaySelectionOptions {
|
|
406
|
+
/** Map design-space command bounds into the damage rectangle's space. */
|
|
407
|
+
transform?: DamageTransform;
|
|
408
|
+
/**
|
|
409
|
+
* Final-damage-coordinate raster/effect reach added after `transform`.
|
|
410
|
+
* Defaults to one final pixel so antialiasing/filtering still selects a
|
|
411
|
+
* command whose geometry falls just beyond a tile edge. Use this for effects
|
|
412
|
+
* whose reach is known in final coordinates; producer-local effects belong
|
|
413
|
+
* in the command's own local bounds, not both places.
|
|
414
|
+
*/
|
|
415
|
+
rasterOutset?: number;
|
|
416
|
+
/** Optional cached bounds provider. `null` takes the unknown-bounds policy. */
|
|
417
|
+
boundsAt?: (index: number, out: CommandBounds) => CommandBounds | null;
|
|
418
|
+
/** Decline instead of returning an unboundedly large partial replay. */
|
|
419
|
+
maxCommands?: number;
|
|
420
|
+
/** Unknown visual bounds cannot safely seed a damaged tile. The default is a
|
|
421
|
+
* full/direct fallback; `select` is available for callers that knowingly
|
|
422
|
+
* prefer conservative overdraw. */
|
|
423
|
+
unknownBounds?: "fullReplay" | "select";
|
|
424
|
+
}
|
|
425
|
+
/** Reusable storage for a replay selection. Its mask and `indices()` array keep
|
|
426
|
+
* identity across calls; after high-water growth selecting a tile allocates no
|
|
427
|
+
* arrays. A threshold/screen-effect decline clears the selection. */
|
|
428
|
+
interface ReplayMaskScratch extends CommandMask {
|
|
429
|
+
readonly thresholdExceeded: boolean;
|
|
430
|
+
/** False until `select()` has validated this frame's complete list. */
|
|
431
|
+
readonly selected: boolean;
|
|
432
|
+
/** Internal nominal marker: only a validated selection may drive an FBO replay. */
|
|
433
|
+
readonly [REPLAY_MASK_SCRATCH]: true;
|
|
434
|
+
select<TTexture>(list: DrawList<TTexture>, damage: DamageRect, options?: ReplaySelectionOptions): ReplayMaskScratch;
|
|
435
|
+
}
|
|
436
|
+
/** True only for masks created by this module and safe for a partial FBO replay. */
|
|
437
|
+
declare function isPartialReplayMask(mask: ReplayMaskScratch, list?: DrawList<unknown>): mask is ReplayMaskScratch;
|
|
438
|
+
declare function createReplayMaskScratch(capacity?: number): ReplayMaskScratch;
|
|
439
|
+
/**
|
|
440
|
+
* Select just the commands that can change `damage`, preserving all clip pushes
|
|
441
|
+
* and pops needed to replay them from an empty clip stack. Unknown bounds are
|
|
442
|
+
* selected conservatively. This is deliberately a mask rather than a copied
|
|
443
|
+
* command list: all reads stay in the original retained arenas.
|
|
444
|
+
*/
|
|
445
|
+
declare function createReplayMask<TTexture>(list: DrawList<TTexture>, damage: DamageRect, options?: ReplaySelectionOptions): ReplayMaskScratch;
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/compiled-draw-list.d.ts
|
|
448
|
+
/** A maximal run that can stay in the quad executor without a painter-order
|
|
449
|
+
* barrier. Texture/matrix slots deliberately are not included: those limits are
|
|
450
|
+
* context-specific and the live batcher remains their authority. */
|
|
451
|
+
interface CompiledBatchDescriptor {
|
|
452
|
+
readonly start: number;
|
|
453
|
+
readonly end: number;
|
|
454
|
+
readonly blend: BlendMode;
|
|
455
|
+
readonly clipDepth: number;
|
|
456
|
+
}
|
|
457
|
+
interface CompiledDrawListDiagnostics {
|
|
458
|
+
planBuilds: number;
|
|
459
|
+
structuralInvalidations: number;
|
|
460
|
+
planReuses: number;
|
|
461
|
+
/** CPU template ranges refreshed after safe command patches. */
|
|
462
|
+
templateRangeUpdates: number;
|
|
463
|
+
reusedSelections: number;
|
|
464
|
+
reusedBatches: number;
|
|
465
|
+
}
|
|
466
|
+
interface CompiledRefreshResult {
|
|
467
|
+
readonly rebuilt: boolean;
|
|
468
|
+
readonly rangeUpdates: number;
|
|
469
|
+
/** Reused storage listing only commands patched since the prior refresh. */
|
|
470
|
+
readonly changedCommands: readonly number[];
|
|
471
|
+
/** Increments on every structural/overflow/context plan rebuild. */
|
|
472
|
+
readonly planGeneration: number;
|
|
473
|
+
/** Current DrawList content revision represented by this plan. */
|
|
474
|
+
readonly contentRevision: number;
|
|
475
|
+
/** Revision a cached consumer must match before applying this delta. */
|
|
476
|
+
readonly deltaBaseRevision: number;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* A retained, allocation-free planning view of a stable draw list. It never
|
|
480
|
+
* owns GL objects: the executor's grow-only instance buffer remains the single
|
|
481
|
+
* GPU allocation authority. The WebGL executor may cache these templates in
|
|
482
|
+
* plan-keyed GPU buffers. Quad templates remove transform/source/tint decoding from replay;
|
|
483
|
+
* all non-quad commands deliberately take the direct executor path.
|
|
484
|
+
*/
|
|
485
|
+
interface CompiledDrawList<TTexture = unknown> {
|
|
486
|
+
readonly list: DrawList<TTexture>;
|
|
487
|
+
readonly batches: readonly CompiledBatchDescriptor[];
|
|
488
|
+
readonly diagnostics: CompiledDrawListDiagnostics;
|
|
489
|
+
refresh(): CompiledRefreshResult;
|
|
490
|
+
invalidate(): void;
|
|
491
|
+
/** Fill the batcher's reusable staging instance from a cached quad template. */
|
|
492
|
+
fillQuad(index: number, textureWidth: number, textureHeight: number, out: QuadInstance): boolean;
|
|
493
|
+
/** Fill `out` with a cached conservative bound, or return null for unknown. */
|
|
494
|
+
commandBounds(index: number, out: DamageRect): DamageRect | null;
|
|
495
|
+
/** Reuse caller-owned selection storage and cached bounds. */
|
|
496
|
+
select(damage: DamageRect, scratch: ReplayMaskScratch, options?: ReplaySelectionOptions): ReplayMaskScratch;
|
|
497
|
+
}
|
|
498
|
+
declare function compileDrawList<TTexture>(list: DrawList<TTexture>): CompiledDrawList<TTexture>;
|
|
499
|
+
/** Convenience for consumers with no existing scratch. Retained loops should
|
|
500
|
+
* create one scratch once and call `plan.select` instead. */
|
|
501
|
+
declare function createCompiledReplayMask<TTexture>(plan: CompiledDrawList<TTexture>, damage: DamageRect, options?: ReplaySelectionOptions): ReplayMaskScratch;
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/textures.d.ts
|
|
504
|
+
/**
|
|
505
|
+
* The executor's texture cache — PER CONTEXT, and its own rather than a
|
|
506
|
+
* generalization of `@godot-scene-web/html`'s.
|
|
507
|
+
*
|
|
508
|
+
* WHY NOT REUSE `html/webgl/shared-gl`'s CACHE. That module opens with a note
|
|
509
|
+
* saying its single context and its single module-scoped texture map are
|
|
510
|
+
* load-bearing: both live runtimes (shaders and particles) must share ONE
|
|
511
|
+
* WebGL2 context, because a page with many cards would otherwise walk into the
|
|
512
|
+
* browser's ~16-live-context limit, and ONE cache, so identical urls upload once.
|
|
513
|
+
* Threading a context parameter through it would turn a documented singleton into
|
|
514
|
+
* a keyed registry — a real change to an invariant two shipping runtimes rest on,
|
|
515
|
+
* to serve a consumer that wants DIFFERENT pixels anyway:
|
|
516
|
+
*
|
|
517
|
+
* - a `WebGLTexture` belongs to the context that made it and cannot be bound in
|
|
518
|
+
* another, so the stage's own context could not use those entries even if it
|
|
519
|
+
* could see them;
|
|
520
|
+
* - shared-gl uploads STRAIGHT alpha (`UNPACK_PREMULTIPLY_ALPHA_WEBGL` false),
|
|
521
|
+
* because its shaders do Godot's multiply themselves; this cache uploads
|
|
522
|
+
* PREMULTIPLIED (see below), which is a different byte in every texel;
|
|
523
|
+
* - shared-gl's entries are immortal and url-keyed with a load listener; a stage
|
|
524
|
+
* that paints hundreds of megabytes of card atlas needs refcounts and a
|
|
525
|
+
* `reset()` for context loss.
|
|
526
|
+
*
|
|
527
|
+
* So this is option (b) from the brief: a small cache of its own, ~150 lines, no
|
|
528
|
+
* change to a package two runtimes depend on.
|
|
529
|
+
*
|
|
530
|
+
* PREMULTIPLIED UPLOAD IS THE POINT, not a detail. `LINEAR` filtering blends
|
|
531
|
+
* texels; blending STRAIGHT colour weights a fully transparent texel's (usually
|
|
532
|
+
* black, or garbage) RGB equally with its opaque neighbour's, so every sprite
|
|
533
|
+
* edge gets a dark fringe and every atlas region bleeds its padding. Blending
|
|
534
|
+
* PREMULTIPLIED colour weights each texel's contribution by its own alpha, which
|
|
535
|
+
* is the arithmetic filtering is supposed to be doing. It also means a texel and
|
|
536
|
+
* the quad's tint compose with one componentwise multiply, and it is the same
|
|
537
|
+
* convention the stage canvas is declared with — one statement of the rule from
|
|
538
|
+
* the PNG to the compositor.
|
|
539
|
+
*
|
|
540
|
+
* SOURCES MUST BE READY. This module never decodes: it uploads what it is handed.
|
|
541
|
+
* A half-loaded `HTMLImageElement` uploads as nothing useful, so the caller
|
|
542
|
+
* decodes (`decode()`, `createImageBitmap`, a canvas it drew itself) and hands
|
|
543
|
+
* over a finished source. That keeps the whole package clear of DOM lifecycle —
|
|
544
|
+
* reading `.width` off an object someone else created is the only thing here that
|
|
545
|
+
* touches a DOM-shaped value.
|
|
546
|
+
*/
|
|
547
|
+
/** Anything WebGL can upload directly. */
|
|
548
|
+
type CanvasTextureSource = TexImageSource;
|
|
549
|
+
/** A cached texture. `width`/`height` are the UPLOADED pixel dimensions, which is
|
|
550
|
+
* what normalizes a draw-list's page-pixel source rect into UVs. */
|
|
551
|
+
interface CanvasTextureHandle {
|
|
552
|
+
readonly texture: WebGLTexture;
|
|
553
|
+
readonly width: number;
|
|
554
|
+
readonly height: number;
|
|
555
|
+
}
|
|
556
|
+
interface TextureCacheStats {
|
|
557
|
+
/** Live entries. */
|
|
558
|
+
entries: number;
|
|
559
|
+
/** Pixel uploads made: `texImage2D` calls plus the `texSubImage2D` re-uploads
|
|
560
|
+
* {@link CanvasTextureCache.update} does into storage that already fits. */
|
|
561
|
+
uploads: number;
|
|
562
|
+
/** Of those, the ones that had to RE-SPECIFY the texture's storage — a first
|
|
563
|
+
* upload, or an `update` whose source changed size. The difference between
|
|
564
|
+
* this and `uploads` is how often the re-upload fast path was taken, which is
|
|
565
|
+
* the number a consumer that streams into one key wants to watch. */
|
|
566
|
+
respecs: number;
|
|
567
|
+
/** Entries deleted because their last reference was released. */
|
|
568
|
+
evictions: number;
|
|
569
|
+
/** Resident RGBA bytes, including every generated mip level. */
|
|
570
|
+
bytes: number;
|
|
571
|
+
}
|
|
572
|
+
/** Sampling and source-alpha options shared by texture acquisition methods.
|
|
573
|
+
*
|
|
574
|
+
* Mipmaps make minification stable, but their filtered levels can sample pixels
|
|
575
|
+
* outside an atlas rect. Use them only for standalone images or padded atlases.
|
|
576
|
+
*/
|
|
577
|
+
interface CanvasTextureOptions {
|
|
578
|
+
/** `true` when the source already contains `rgb * a`. Defaults to false. */
|
|
579
|
+
premultiplied?: boolean;
|
|
580
|
+
/** Generate a complete mip chain. Not suitable for unpadded atlas pages. */
|
|
581
|
+
mipmap?: boolean;
|
|
582
|
+
/** Minification filter. Defaults to LINEAR, or LINEAR_MIPMAP_LINEAR with mips. */
|
|
583
|
+
minFilter?: number;
|
|
584
|
+
/** Magnification filter. Defaults to LINEAR; NEAREST is supported. */
|
|
585
|
+
magFilter?: number;
|
|
586
|
+
}
|
|
587
|
+
interface CanvasTextureCache {
|
|
588
|
+
readonly stats: TextureCacheStats;
|
|
589
|
+
/** The 1x1 opaque-white texel every untextured quad samples, so a solid fill
|
|
590
|
+
* needs no second shader and no special slot. */
|
|
591
|
+
white(): CanvasTextureHandle;
|
|
592
|
+
/** Look up without uploading or retaining. */
|
|
593
|
+
peek(key: string): CanvasTextureHandle | undefined;
|
|
594
|
+
/**
|
|
595
|
+
* Upload `source` under `key` and take a reference. A key already present is
|
|
596
|
+
* NOT re-uploaded (that is the decode-once guarantee) — only referenced again;
|
|
597
|
+
* use {@link CanvasTextureCache.update} to replace its pixels.
|
|
598
|
+
*/
|
|
599
|
+
acquire(key: string, source: CanvasTextureSource, options?: CanvasTextureOptions): CanvasTextureHandle;
|
|
600
|
+
/**
|
|
601
|
+
* Same, from raw RGBA bytes. `premultiplied` says whether `pixels` already
|
|
602
|
+
* carries `rgb*a`; when it does not, the multiply happens HERE in JS rather
|
|
603
|
+
* than through `UNPACK_PREMULTIPLY_ALPHA_WEBGL`, whose behaviour over an
|
|
604
|
+
* `ArrayBufferView` is not worth depending on.
|
|
605
|
+
*/
|
|
606
|
+
acquireBytes(key: string, pixels: Uint8Array | Uint8ClampedArray, width: number, height: number, options?: CanvasTextureOptions): CanvasTextureHandle;
|
|
607
|
+
/** Take another reference to an existing key. Throws when it is not present. */
|
|
608
|
+
retain(key: string): CanvasTextureHandle;
|
|
609
|
+
/** Drop a reference; the entry is deleted when the last one goes. */
|
|
610
|
+
release(key: string): void;
|
|
611
|
+
/**
|
|
612
|
+
* Replace an existing (or create a new) entry's pixels, keeping its refcount.
|
|
613
|
+
*
|
|
614
|
+
* A source the same size as what the entry already holds is re-uploaded with
|
|
615
|
+
* `texSubImage2D`, INTO the storage that is already there; only a size change
|
|
616
|
+
* re-specifies it with `texImage2D`. That matters for a consumer streaming a
|
|
617
|
+
* live surface into one key every frame, which is the shape this method exists
|
|
618
|
+
* for: a re-spec frees the old mip level and allocates a new one on every call,
|
|
619
|
+
* so a same-size stream would churn a few megabytes of driver allocation per
|
|
620
|
+
* frame to write the same number of texels. See `stats.respecs`.
|
|
621
|
+
*/
|
|
622
|
+
update(key: string, source: CanvasTextureSource): CanvasTextureHandle;
|
|
623
|
+
/**
|
|
624
|
+
* Write `source` into an EXISTING entry's storage at `(x, y)`, leaving the rest
|
|
625
|
+
* of it untouched. Nothing is allocated, nothing is re-specified, the refcount
|
|
626
|
+
* does not move, and the sampler parameters are not re-set.
|
|
627
|
+
*
|
|
628
|
+
* This is the ATLAS PAGE case, and it is why {@link CanvasTextureCache.update}
|
|
629
|
+
* is not enough for it. A page is one texture that many small sources are
|
|
630
|
+
* written into over time; `update` can only replace the whole thing, so a
|
|
631
|
+
* consumer holding a 1024x1024 page would have to re-upload all four megabytes
|
|
632
|
+
* every time one 40x18 label changed — which on a phone is tens of milliseconds
|
|
633
|
+
* for a few kilobytes of new pixels. Writing just the region is proportional to
|
|
634
|
+
* what actually changed.
|
|
635
|
+
*
|
|
636
|
+
* REFUSED, with `null` and no GL call at all, when the key is unknown or when
|
|
637
|
+
* the rect does not lie wholly inside the entry's real storage (see
|
|
638
|
+
* `Entry.storageW`, which is not always the entry's claimed size). Both would
|
|
639
|
+
* otherwise be a silent `INVALID_VALUE` on the context — an error the caller
|
|
640
|
+
* cannot see and the next draw cannot explain. A refusal is a fact the caller
|
|
641
|
+
* is expected to handle (re-allocate, or fall back), not an exception.
|
|
642
|
+
*
|
|
643
|
+
* Counted as one `uploads` and never a `respec`, which is the distinction the
|
|
644
|
+
* stat exists to make.
|
|
645
|
+
*/
|
|
646
|
+
updateRegion(key: string, source: CanvasTextureSource, x: number, y: number): CanvasTextureHandle | null;
|
|
647
|
+
/** The context was lost: forget every entry WITHOUT touching the dead driver. */
|
|
648
|
+
reset(): void;
|
|
649
|
+
/** Delete every texture and empty the cache. */
|
|
650
|
+
dispose(): void;
|
|
651
|
+
}
|
|
652
|
+
declare function createTextureCache(gl: WebGL2RenderingContext): CanvasTextureCache;
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/executor-webgl.d.ts
|
|
655
|
+
/**
|
|
656
|
+
* The WebGL2 executor: a draw list in, GL draws out.
|
|
657
|
+
*
|
|
658
|
+
* ONE PROGRAM, ONE VERTEX FORMAT, ONE BUFFER for the whole scene. Every command
|
|
659
|
+
* kind is reduced to the same instanced quad — a sprite is one, a nine-patch is
|
|
660
|
+
* up to nine (`./nine-patch`), a polyline is one per segment plus one per join
|
|
661
|
+
* (`./polyline`), a solid fill is one sampling a 1x1 white texel. That is what
|
|
662
|
+
* lets `./batcher` merge across command kinds instead of only within them, and it
|
|
663
|
+
* is why there is no "solid" shader, no "line" shader and no per-kind draw path
|
|
664
|
+
* to keep in sync.
|
|
665
|
+
*
|
|
666
|
+
* WHAT BREAKS A BATCH, and what deliberately does not. GL state that lives on the
|
|
667
|
+
* DRAW rather than on the instance has to break one: the blend mode, the scissor
|
|
668
|
+
* box, and the rounded-clip uniforms. Everything else is per-instance data —
|
|
669
|
+
* transform, source rect, tint, texture (an index into the batch's slot table),
|
|
670
|
+
* colour matrix (an index into the batch's uniform table) — so it costs a few
|
|
671
|
+
* floats instead of a draw call. See `./batcher`'s note for the measurement that
|
|
672
|
+
* decided this.
|
|
673
|
+
*
|
|
674
|
+
* ORDER OF OPERATIONS AROUND A STATE CHANGE, which is the one genuinely subtle
|
|
675
|
+
* thing in this file. A flush DRAWS with whatever GL state is currently set, so
|
|
676
|
+
* the pending batch must be flushed BEFORE the new state is applied, never after.
|
|
677
|
+
* Every state change here therefore reads: tell the batcher (which flushes the
|
|
678
|
+
* old batch under the old GL state), then touch GL. Doing it the other way round
|
|
679
|
+
* is invisible in a screenshot of a static scene and produces a one-frame-late
|
|
680
|
+
* clip the moment anything moves.
|
|
681
|
+
*
|
|
682
|
+
* THE ONE COMMAND THAT IS NOT A QUAD. `glyphs` runs are outlines evaluated per fragment, which no
|
|
683
|
+
* amount of instancing turns into the program above, so they are delegated to an injected
|
|
684
|
+
* {@link GlyphPass} (`./glyph-pass`) and cost one draw call each. That makes them the only place
|
|
685
|
+
* this file hands the context to somebody else mid-frame, and the order-of-operations rule above is
|
|
686
|
+
* exactly what governs it: flush, then the pass, then rebind. `emitGlyphsCommand` is where that is
|
|
687
|
+
* written down.
|
|
688
|
+
*
|
|
689
|
+
* THE ALPHA CONTRACT, end to end: textures upload premultiplied (`./textures`),
|
|
690
|
+
* tints in the draw list are premultiplied, the fragment emits `vec4(rgb*a, a)`,
|
|
691
|
+
* the MIX blend is `(ONE, ONE_MINUS_SRC_ALPHA)`, and the canvas is declared
|
|
692
|
+
* `premultipliedAlpha: true` (`./present`). Any one of those five flipped on its
|
|
693
|
+
* own is silent — nothing errors, the picture is just wrong — which is why they
|
|
694
|
+
* are named together here.
|
|
695
|
+
*/
|
|
696
|
+
/** A texture handle the executor can draw: the GL object and the size its source
|
|
697
|
+
* rects are measured against. `CanvasTextureHandle` satisfies it. */
|
|
698
|
+
type ExecutorTexture = CanvasTextureHandle;
|
|
699
|
+
/** GL blend state for a Godot blend mode, as ENUM NAMES so the mapping is pure
|
|
700
|
+
* and unit-testable (the same trick `html`'s `blendFactorsFor` uses). */
|
|
701
|
+
interface BlendState {
|
|
702
|
+
equationRgb: "FUNC_ADD" | "FUNC_REVERSE_SUBTRACT";
|
|
703
|
+
equationAlpha: "FUNC_ADD";
|
|
704
|
+
srcRgb: "ONE" | "DST_COLOR";
|
|
705
|
+
dstRgb: "ONE" | "ZERO" | "ONE_MINUS_SRC_ALPHA";
|
|
706
|
+
srcAlpha: "ONE" | "DST_ALPHA";
|
|
707
|
+
dstAlpha: "ONE" | "ZERO" | "ONE_MINUS_SRC_ALPHA";
|
|
708
|
+
}
|
|
709
|
+
/** The GL blend state a Godot `CanvasItemMaterial.BlendMode` maps to. */
|
|
710
|
+
declare function blendStateFor(blend: BlendMode): BlendState;
|
|
711
|
+
interface ExecutorStats {
|
|
712
|
+
/** Draw-list commands read. */
|
|
713
|
+
commands: number;
|
|
714
|
+
/** Quad instances pushed, including expanded nine-patch bands and stroke quads. */
|
|
715
|
+
quads: number;
|
|
716
|
+
/** `drawArraysInstanced` calls — the number this executor exists to keep small. */
|
|
717
|
+
batches: number;
|
|
718
|
+
/** Texture-unit binds summed over batches. */
|
|
719
|
+
textureBinds: number;
|
|
720
|
+
/** Times the scissor box actually changed. */
|
|
721
|
+
scissorChanges: number;
|
|
722
|
+
/** Times GL blend state actually changed. */
|
|
723
|
+
blendChanges: number;
|
|
724
|
+
ninePatches: number;
|
|
725
|
+
ninePatchQuads: number;
|
|
726
|
+
polylines: number;
|
|
727
|
+
polylineQuads: number;
|
|
728
|
+
/** Indexed textured-mesh commands executed. */
|
|
729
|
+
texturedMeshes: number;
|
|
730
|
+
/** Triangle-list primitives submitted by textured meshes. */
|
|
731
|
+
texturedMeshTriangles: number;
|
|
732
|
+
/** `drawElements` calls issued by textured meshes. */
|
|
733
|
+
texturedMeshDrawCalls: number;
|
|
734
|
+
/** `glyphs` commands handed to the installed {@link CanvasExecutorOptions.glyphs} pass. */
|
|
735
|
+
glyphRuns: number;
|
|
736
|
+
/** Glyphs the pass reported drawing, summed over runs. Excludes ones it skipped. */
|
|
737
|
+
glyphs: number;
|
|
738
|
+
/** Draw calls the pass reported. One per run, for the reason {@link GlyphPass.drawRun} gives. */
|
|
739
|
+
glyphDrawCalls: number;
|
|
740
|
+
glyphRunBatches: number;
|
|
741
|
+
glyphRunBatchFallbacks: number;
|
|
742
|
+
/**
|
|
743
|
+
* `glyphs` commands seen with NO pass installed.
|
|
744
|
+
*
|
|
745
|
+
* A NAMED NO-OP RATHER THAN A SILENT ONE. A list carrying text into an executor that cannot draw
|
|
746
|
+
* text is a wiring mistake — the consumer forgot to pass `glyphs` — and its symptom is a page
|
|
747
|
+
* that renders perfectly except for having no words on it. That is exactly the kind of failure
|
|
748
|
+
* a screenshot review passes and a counter catches.
|
|
749
|
+
*/
|
|
750
|
+
glyphRunsDropped: number;
|
|
751
|
+
/** Screen-dependent passes executed at their recorded painter position. */
|
|
752
|
+
screenEffects: number;
|
|
753
|
+
/** Required screen passes that refused or failed at runtime. */
|
|
754
|
+
screenEffectFailures: number;
|
|
755
|
+
/** Direct external passes executed at their recorded painter position. */
|
|
756
|
+
externalEffects: number;
|
|
757
|
+
/** Required direct external passes that refused or failed at runtime. */
|
|
758
|
+
externalEffectFailures: number;
|
|
759
|
+
/** Clip rects that could not be an exact scissor (see `./clip-stack`). */
|
|
760
|
+
rotatedClipFallbacks: number;
|
|
761
|
+
/** `clipPop`s with nothing open — a malformed list, survived rather than thrown. */
|
|
762
|
+
unbalancedClipPops: number;
|
|
763
|
+
/**
|
|
764
|
+
* Commands whose kind this executor does not handle.
|
|
765
|
+
*
|
|
766
|
+
* The dispatch switch had no `default` for its first five kinds, so a sixth added to the IR fell
|
|
767
|
+
* through it silently while still counting in {@link ExecutorStats.commands} — a frame missing
|
|
768
|
+
* every command of the new kind, reported as a frame that drew everything.
|
|
769
|
+
*/
|
|
770
|
+
unknownCommands: number;
|
|
771
|
+
/** Largest single batch, in instances. */
|
|
772
|
+
maxBatchQuads: number;
|
|
773
|
+
/** Why batches ended, so a regression in batching says which axis moved. */
|
|
774
|
+
flushes: Record<BatchFlushReason, number>;
|
|
775
|
+
/** Per-execution compiled-plan diagnostics; direct frames leave these zero. */
|
|
776
|
+
compiledPlanBuilds: number;
|
|
777
|
+
compiledPlanReuses: number;
|
|
778
|
+
compiledTemplateRangeUpdates: number;
|
|
779
|
+
reusedSelections: number;
|
|
780
|
+
reusedBatches: number;
|
|
781
|
+
compiledGpuFullUploads: number;
|
|
782
|
+
compiledGpuRangeUploads: number;
|
|
783
|
+
compiledCachedDrawCalls: number;
|
|
784
|
+
}
|
|
785
|
+
interface CanvasExecutorOptions {
|
|
786
|
+
gl: WebGL2RenderingContext;
|
|
787
|
+
/** The 1x1 white texel untextured quads sample. Defaults to one the executor
|
|
788
|
+
* makes and owns; pass `CanvasTextureCache.white()` to share the cache's. */
|
|
789
|
+
white?: ExecutorTexture;
|
|
790
|
+
/**
|
|
791
|
+
* Who draws `glyphs` commands. Omitted means the executor cannot draw text.
|
|
792
|
+
*
|
|
793
|
+
* INJECTED, exactly like {@link CanvasExecutorOptions.white}, and for the reason
|
|
794
|
+
* {@link GlyphPass} states: the only implementation is backed by a glyph renderer that a scene
|
|
795
|
+
* with no text should not have to load, and the main barrel's export surface is mirrored by hand
|
|
796
|
+
* downstream. Import `@godot-scene-web/canvas/glyphs` and pass one when the scene has text.
|
|
797
|
+
*
|
|
798
|
+
* Leaving it out is not an error — plenty of draw lists have no glyph runs at all — but a list
|
|
799
|
+
* that DOES carry one then counts it in {@link ExecutorStats.glyphRunsDropped} rather than
|
|
800
|
+
* skipping it in silence.
|
|
801
|
+
*/
|
|
802
|
+
glyphs?: GlyphPass;
|
|
803
|
+
/**
|
|
804
|
+
* Opt-in only: combine physically adjacent glyph commands when the injected pass exposes
|
|
805
|
+
* `drawRuns`. This never crosses a clip, a command-mask gap, or any non-glyph command.
|
|
806
|
+
*/
|
|
807
|
+
batchAdjacentGlyphRuns?: boolean;
|
|
808
|
+
/** Texture units per batch. Clamped to the context's `MAX_TEXTURE_IMAGE_UNITS`
|
|
809
|
+
* and to {@link MAX_TEXTURE_SLOTS}. */
|
|
810
|
+
maxTextureSlots?: number;
|
|
811
|
+
/** Colour-matrix slots per batch, including the identity at slot 0. */
|
|
812
|
+
maxColorMatrices?: number;
|
|
813
|
+
/** Initial instance-arena capacity, in quads. */
|
|
814
|
+
quadCapacity?: number;
|
|
815
|
+
}
|
|
816
|
+
interface ExecuteOptions {
|
|
817
|
+
/** Clear the framebuffer to transparent black before drawing. Default true. */
|
|
818
|
+
clear?: boolean;
|
|
819
|
+
/**
|
|
820
|
+
* Replacement clear colour for a full-frame stage pass. It is intentionally
|
|
821
|
+
* an execute option rather than a global GL mutation: retained FBO replays
|
|
822
|
+
* keep their transparent clear while an opaque presenter can establish its
|
|
823
|
+
* base pixels without a second fullscreen draw.
|
|
824
|
+
*/
|
|
825
|
+
clearColor?: readonly [number, number, number, number];
|
|
826
|
+
/**
|
|
827
|
+
* Restrict both clearing and drawing to this top-left framebuffer-pixel
|
|
828
|
+
* rectangle. It is the retained-surface seam, but is also useful to any
|
|
829
|
+
* caller rendering into its own FBO; omitted keeps the direct full-frame
|
|
830
|
+
* executor path byte-for-byte in shape.
|
|
831
|
+
*/
|
|
832
|
+
damage?: DamageRect;
|
|
833
|
+
/** Ordered replay selection. Clip closures are supplied by `createReplayMask`.
|
|
834
|
+
* Omitted means every command is executed, as direct frames always have. */
|
|
835
|
+
commandMask?: CommandMask;
|
|
836
|
+
/** Opt-in retained plan. Omitted direct frames do no compilation work. */
|
|
837
|
+
compiled?: CompiledDrawList<ExecutorTexture | null>;
|
|
838
|
+
}
|
|
839
|
+
interface CanvasExecutor {
|
|
840
|
+
readonly gl: WebGL2RenderingContext;
|
|
841
|
+
readonly stats: ExecutorStats;
|
|
842
|
+
/** Texture units a batch can hold on THIS context. */
|
|
843
|
+
readonly maxTextureSlots: number;
|
|
844
|
+
/** Build the program and buffers now, off whatever critical path the caller
|
|
845
|
+
* cares about. `execute` does it lazily otherwise — and reading a shader's
|
|
846
|
+
* compile status BLOCKS on the driver (~100-250 ms on a phone), so paying it
|
|
847
|
+
* during the first frame is a visible hitch. */
|
|
848
|
+
warmUp(): boolean;
|
|
849
|
+
/** Draw one frame. Returns false when the program could not be built. */
|
|
850
|
+
execute(list: DrawList<ExecutorTexture | null>, projection: StageProjection, options?: ExecuteOptions): boolean;
|
|
851
|
+
/** Release executor-owned cached GPU buffers for one compiled plan. */
|
|
852
|
+
releaseCompiled(plan: CompiledDrawList<ExecutorTexture | null>): void;
|
|
853
|
+
/** The context is GONE: drop every GL object without calling into GL. The next
|
|
854
|
+
* `execute` rebuilds them. */
|
|
855
|
+
invalidate(): void;
|
|
856
|
+
/** Delete every GL object this executor owns. */
|
|
857
|
+
dispose(): void;
|
|
858
|
+
}
|
|
859
|
+
declare function createCanvasExecutor(options: CanvasExecutorOptions): CanvasExecutor;
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/headless-effects.d.ts
|
|
862
|
+
/** Bind a GPU screen producer at one DrawList painter position. */
|
|
863
|
+
declare function createHeadlessScreenEffectCommand(producer: HeadlessShaderProducer, input: HeadlessShaderRenderInput): ScreenEffectDrawCommand;
|
|
864
|
+
/** Bind a direct GPU particle pass at one DrawList painter position. */
|
|
865
|
+
declare function createHeadlessGodotParticleDirectEffect(pass: HeadlessGodotParticleDirectPass, input: HeadlessGodotParticleDirectRenderInput): ExternalEffectDrawCommand;
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region src/nine-patch.d.ts
|
|
868
|
+
/**
|
|
869
|
+
* Nine-patch band algebra: one `DRAW_NINE_PATCH` command -> up to nine plain
|
|
870
|
+
* quads. Pure (no GL, no state), so the geometry can be unit-tested on its own —
|
|
871
|
+
* it is the part of the executor most likely to be wrong by half a pixel and the
|
|
872
|
+
* part least able to say so on screen.
|
|
873
|
+
*
|
|
874
|
+
* WHERE THE RULES COME FROM. Godot does not expand a nine-patch into quads at
|
|
875
|
+
* all: it draws ONE quad and remaps each fragment's coordinate with
|
|
876
|
+
* `map_ninepatch_axis` (`drivers/gles3/shaders/canvas.glsl`). That function is
|
|
877
|
+
* this module's specification, per axis:
|
|
878
|
+
*
|
|
879
|
+
* - `pixel < margin_begin` -> source coordinate `pixel`, i.e. the leading corner
|
|
880
|
+
* band is copied 1:1 at its native pixel size;
|
|
881
|
+
* - `pixel >= draw_size - margin_end` -> source `tex_size - (draw_size - pixel)`,
|
|
882
|
+
* the trailing corner band, also 1:1;
|
|
883
|
+
* - otherwise the centre band, stretched from `[margin_begin, tex_size -
|
|
884
|
+
* margin_end]` onto `[margin_begin, draw_size - margin_end]`.
|
|
885
|
+
*
|
|
886
|
+
* Expanding that into rects gives identical pixels for the STRETCH axis mode and
|
|
887
|
+
* costs a handful of extra quads that batch with everything else — much cheaper
|
|
888
|
+
* than the branchy per-fragment remap, and it keeps one shader for every command
|
|
889
|
+
* kind. Godot's TILE / TILE_FIT modes are NOT reachable from the draw-list IR
|
|
890
|
+
* (which carries no axis-stretch mode), so STRETCH — Godot's default — is what
|
|
891
|
+
* this implements.
|
|
892
|
+
*
|
|
893
|
+
* DEGENERATE MARGINS FOLLOW THE SAME SPECIFICATION rather than a clamp. Note the
|
|
894
|
+
* order of the branches above: when the two margins together exceed the
|
|
895
|
+
* destination, the LEADING band wins the overlap and the trailing band keeps only
|
|
896
|
+
* what is left. So this module truncates rather than rescaling the corners, which
|
|
897
|
+
* is what the shader does. Bands that come out empty are dropped, so a patch
|
|
898
|
+
* squeezed below its own margins expands to fewer than nine quads (down to one,
|
|
899
|
+
* or to none at all when it has no area).
|
|
900
|
+
*
|
|
901
|
+
* A SOURCE centre that is empty or inverted (the margins meet or cross inside the
|
|
902
|
+
* texture region) is dropped too. Godot's remap would produce a reversed source
|
|
903
|
+
* range there — a mirrored smear — which is nobody's intent.
|
|
904
|
+
*/
|
|
905
|
+
/** One expanded band: a destination rect in the command's LOCAL space (before the
|
|
906
|
+
* quad's affine `m`), and the source rect it samples, in page pixels. */
|
|
907
|
+
interface NinePatchBand {
|
|
908
|
+
/** Destination, local space: x in `[0, w]`, y in `[0, h]`. */
|
|
909
|
+
dstX: number;
|
|
910
|
+
dstY: number;
|
|
911
|
+
dstW: number;
|
|
912
|
+
dstH: number;
|
|
913
|
+
/** Source, page pixels — an absolute rect on the page, not relative to the region. */
|
|
914
|
+
srcX: number;
|
|
915
|
+
srcY: number;
|
|
916
|
+
srcW: number;
|
|
917
|
+
srcH: number;
|
|
918
|
+
}
|
|
919
|
+
/** The nine-patch inputs, matching `NinePatchView`'s fields. */
|
|
920
|
+
interface NinePatchGeometry {
|
|
921
|
+
/** Destination size in local units. */
|
|
922
|
+
w: number;
|
|
923
|
+
h: number;
|
|
924
|
+
/** The patch REGION on the page, in page pixels. */
|
|
925
|
+
srcX: number;
|
|
926
|
+
srcY: number;
|
|
927
|
+
srcW: number;
|
|
928
|
+
srcH: number;
|
|
929
|
+
/** Insets into the region, page pixels (Godot `patch_margin_*`). */
|
|
930
|
+
marginLeft: number;
|
|
931
|
+
marginTop: number;
|
|
932
|
+
marginRight: number;
|
|
933
|
+
marginBottom: number;
|
|
934
|
+
}
|
|
935
|
+
declare function createNinePatchBand(): NinePatchBand;
|
|
936
|
+
/** A reusable output buffer: nine bands is the hard maximum, so it never grows. */
|
|
937
|
+
declare function createNinePatchBands(): NinePatchBand[];
|
|
938
|
+
/**
|
|
939
|
+
* Expand a nine-patch into its bands, filling `out` (use
|
|
940
|
+
* {@link createNinePatchBands}, which is always big enough) and returning how
|
|
941
|
+
* many are live. `out` entries past the return value are stale and must not be
|
|
942
|
+
* read.
|
|
943
|
+
*
|
|
944
|
+
* Returns 0 for a patch with no area — a zero-size destination or a zero-size
|
|
945
|
+
* region draws nothing at all, which is not the same as drawing one empty band.
|
|
946
|
+
*/
|
|
947
|
+
declare function expandNinePatch(patch: NinePatchGeometry, out: NinePatchBand[]): number;
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/polyline.d.ts
|
|
950
|
+
/**
|
|
951
|
+
* Constant-width polyline -> quads, so a `DRAW_POLYLINE` goes through the SAME
|
|
952
|
+
* instance buffer, the same shader and the same batch as everything else instead
|
|
953
|
+
* of forcing a second program and a mid-frame draw break.
|
|
954
|
+
*
|
|
955
|
+
* That is possible because the batcher's instance carries four EXPLICIT corners
|
|
956
|
+
* rather than an affine basis (see `./batcher`): a quad instance is any
|
|
957
|
+
* quadrilateral, and a quadrilateral with its last two corners coincident is a
|
|
958
|
+
* triangle. So a stroke is emitted as
|
|
959
|
+
*
|
|
960
|
+
* - one parallelogram per segment (the segment offset by ±width/2 along its
|
|
961
|
+
* normal), and
|
|
962
|
+
* - one triangle per interior vertex, filling the notch on the OUTSIDE of the
|
|
963
|
+
* turn.
|
|
964
|
+
*
|
|
965
|
+
* JOINS ARE BEVEL, CAPS ARE BUTT — the first pass the wave-1 brief allows, and a
|
|
966
|
+
* deliberate choice rather than an oversight:
|
|
967
|
+
*
|
|
968
|
+
* - a MITER join needs the two segment edges extended to their intersection,
|
|
969
|
+
* which runs away to infinity as the turn approaches a reversal and therefore
|
|
970
|
+
* needs a miter limit that itself falls back to... a bevel. Two code paths for
|
|
971
|
+
* a shape that differs from the bevel only inside a `width/2` disc.
|
|
972
|
+
* - a ROUND join needs an arc, i.e. a fan of triangles whose count depends on the
|
|
973
|
+
* turn angle and the on-screen width — the one thing in this file that would
|
|
974
|
+
* make its output size unpredictable.
|
|
975
|
+
*
|
|
976
|
+
* The bevel differs from both only within half a stroke width of a vertex, and
|
|
977
|
+
* the measured population of polylines in the recorded scenes this executor was
|
|
978
|
+
* sized against is ZERO (the paint-source mix is texture/text/particles/spine/
|
|
979
|
+
* solid). Upgrading to round joins is local to this file: emit a fan instead of
|
|
980
|
+
* the single wedge triangle in the interior-vertex loop.
|
|
981
|
+
*
|
|
982
|
+
* Self-overlap: at a sharp turn the two segment parallelograms overlap near the
|
|
983
|
+
* vertex, so a translucent stroke double-composites there. Godot's own
|
|
984
|
+
* `draw_polyline` has the same artefact; fixing it needs a stencil or a
|
|
985
|
+
* single-pass SDF, neither of which belongs in wave 1.
|
|
986
|
+
*/
|
|
987
|
+
/** Floats per emitted quad: four `(x, y)` corners in draw-list local space. */
|
|
988
|
+
declare const POLYLINE_QUAD_FLOATS = 8;
|
|
989
|
+
/**
|
|
990
|
+
* The most quads a `pointCount`-point stroke can produce: one per segment plus
|
|
991
|
+
* one per interior vertex. Sizes a caller's output buffer exactly.
|
|
992
|
+
*/
|
|
993
|
+
declare function polylineQuadCapacity(pointCount: number): number;
|
|
994
|
+
/**
|
|
995
|
+
* Tessellate `points` (flattened `x, y, x, y, …`, the draw-list's own layout)
|
|
996
|
+
* into quads, writing `POLYLINE_QUAD_FLOATS` floats per quad into `out` from
|
|
997
|
+
* `outOffset`. Returns the number of quads written.
|
|
998
|
+
*
|
|
999
|
+
* Corners are written in the batcher's unit-square order — `(0,0)`, `(1,0)`,
|
|
1000
|
+
* `(1,1)`, `(0,1)` — so a triangle is spelled by repeating the last corner.
|
|
1001
|
+
*
|
|
1002
|
+
* Zero-length segments are skipped (they have no direction to offset along, and
|
|
1003
|
+
* a duplicated point is a common artefact of a resampled path); a vertex whose
|
|
1004
|
+
* incoming or outgoing segment was skipped gets no join wedge, because there is
|
|
1005
|
+
* no notch to fill.
|
|
1006
|
+
*/
|
|
1007
|
+
declare function expandPolyline(points: ArrayLike<number>, pointCount: number, width: number, out: Float32Array, outOffset?: number): number;
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region src/retained-surface.d.ts
|
|
1010
|
+
/** An RGBA8 framebuffer that retains scene pixels between frames. */
|
|
1011
|
+
interface RetainedSurface {
|
|
1012
|
+
readonly gl: WebGL2RenderingContext;
|
|
1013
|
+
/** The achieved, integer backing dimensions. Zero means unallocated. */
|
|
1014
|
+
readonly width: number;
|
|
1015
|
+
readonly height: number;
|
|
1016
|
+
/** GPU allocation is live. It says nothing about whether it contains a frame. */
|
|
1017
|
+
readonly allocated: boolean;
|
|
1018
|
+
/** True only after a full replay; `present` refuses unseeded pixels. */
|
|
1019
|
+
readonly contentValid: boolean;
|
|
1020
|
+
/**
|
|
1021
|
+
* Allocate an RGBA8 texture/FBO at the exact snapped requested size. A size
|
|
1022
|
+
* change discards old pixels, so callers must replay a full frame afterwards.
|
|
1023
|
+
*/
|
|
1024
|
+
resize(width: number, height: number): boolean;
|
|
1025
|
+
/**
|
|
1026
|
+
* Replay into the retained target. `damage` is top-left framebuffer pixels;
|
|
1027
|
+
* passing it restricts both clear and draw to that region. This operation is
|
|
1028
|
+
* explicit: direct `CanvasExecutor.execute` calls can neither seed this FBO
|
|
1029
|
+
* nor present it.
|
|
1030
|
+
*/
|
|
1031
|
+
replay(executor: CanvasExecutor, list: DrawList<ExecutorTexture | null>, projection: StageProjection, options?: RetainedReplayOptions): boolean;
|
|
1032
|
+
/**
|
|
1033
|
+
* Replay a prevalidated exact cover into one retained target binding. Every
|
|
1034
|
+
* region keeps its own damage scissor and clip-complete mask, so painter
|
|
1035
|
+
* order, clips and blend modes are identical to individual partial replays.
|
|
1036
|
+
* The whole set is validated before its first clear; any failure invalidates
|
|
1037
|
+
* retained content and a caller must seed rather than present it.
|
|
1038
|
+
*/
|
|
1039
|
+
replayRegions(executor: CanvasExecutor, list: DrawList<ExecutorTexture | null>, projection: StageProjection, regions: readonly RetainedReplayRegion[], options?: RetainedReplayRegionsOptions): boolean;
|
|
1040
|
+
/** Blit the retained texture once, 1:1, to the default framebuffer. */
|
|
1041
|
+
present(): boolean;
|
|
1042
|
+
/** Forget retained pixels while keeping the allocated FBO for the next seed. */
|
|
1043
|
+
invalidateContent(): void;
|
|
1044
|
+
/** Drop dead context handles without calling GL after a context loss. */
|
|
1045
|
+
invalidate(): void;
|
|
1046
|
+
/** Delete owned GL resources while the context is live. */
|
|
1047
|
+
dispose(): void;
|
|
1048
|
+
}
|
|
1049
|
+
interface RetainedReplayOptions {
|
|
1050
|
+
/**
|
|
1051
|
+
* A physical-pixel damage region. Partial retained replay deliberately
|
|
1052
|
+
* requires one; an unbounded replay is a seed and therefore renders the
|
|
1053
|
+
* complete list without a mask.
|
|
1054
|
+
*/
|
|
1055
|
+
damage?: DamageRect;
|
|
1056
|
+
/**
|
|
1057
|
+
* A validated, clip-complete selection from `createReplayMaskScratch` or a
|
|
1058
|
+
* compiled draw-list. A generic `CommandMask` is intentionally not accepted:
|
|
1059
|
+
* it could omit an unknown or screen-dependent command and leave stale FBO
|
|
1060
|
+
* pixels that `present()` would otherwise expose.
|
|
1061
|
+
*/
|
|
1062
|
+
mask?: ReplayMaskScratch;
|
|
1063
|
+
/** Forwarded only for rare custom executor behaviour; clear defaults to true. */
|
|
1064
|
+
execute?: Omit<ExecuteOptions, "clear" | "damage" | "commandMask">;
|
|
1065
|
+
}
|
|
1066
|
+
/** One independently scissored, clip-complete member of a retained replay set. */
|
|
1067
|
+
interface RetainedReplayRegion {
|
|
1068
|
+
damage: DamageRect;
|
|
1069
|
+
mask: ReplayMaskScratch;
|
|
1070
|
+
}
|
|
1071
|
+
/** Shared executor options for every region in one retained replay set. */
|
|
1072
|
+
interface RetainedReplayRegionsOptions {
|
|
1073
|
+
execute?: Omit<ExecuteOptions, "clear" | "damage" | "commandMask">;
|
|
1074
|
+
}
|
|
1075
|
+
/** Snap a CSS/device calculation once at the allocation boundary. */
|
|
1076
|
+
declare function snapRetainedSize(value: number): number;
|
|
1077
|
+
/**
|
|
1078
|
+
* Create a retained RGBA8 texture/FBO. It deliberately does not create a
|
|
1079
|
+
* canvas or context — a stage owns that DOM-facing concern — and it does not
|
|
1080
|
+
* monkey-patch an executor. Retention is opt-in per replay/present call.
|
|
1081
|
+
*/
|
|
1082
|
+
declare function createRetainedSurface(gl: WebGL2RenderingContext): RetainedSurface;
|
|
1083
|
+
//#endregion
|
|
1084
|
+
export { BLEND_ADD, BLEND_MIX, BLEND_MUL, BLEND_SUB, type Batch, type BatchFlushReason, type BatchTexture, type BatcherStats, type BlendMode, type BlendState, COLOR_MATRIX_FLOATS, type CanvasExecutor, type CanvasExecutorOptions, type CanvasStage, type CanvasStageOptions, type CanvasTextureCache, type CanvasTextureHandle, type CanvasTextureOptions, type CanvasTextureSource, type ClipBounds, type ClipRectView, type ClipStack, type CommandBounds, type CommandMask, type CompiledBatchDescriptor, type CompiledDrawList, type CompiledDrawListDiagnostics, type CompiledRefreshResult, DEFAULT_COLOR_MATRIX_SLOTS, DRAW_CLIP_POP, DRAW_CLIP_PUSH, DRAW_COMMAND_NAMES, DRAW_EXTERNAL_EFFECT, DRAW_GLYPHS, DRAW_NINE_PATCH, DRAW_POLYLINE, DRAW_QUAD, DRAW_SCREEN_EFFECT, DRAW_TEXTURED_MESH, type DamageRect, type DamageTile, type DamageTiles, type DamageTransform, type DirtyRect, type DrawCommandKind, type DrawCommandName, type DrawList, type DrawListFragment, type DrawListFragmentPatch, type DrawListOptions, type DrawListPatchView, type ExecuteOptions, type ExecutorStats, type ExecutorTexture, type ExternalEffectDrawCommand, type ExternalEffectDrawContext, FLIP_H, FLIP_V, type GlyphPass, type GlyphsView, IDENTITY_COLOR_MATRIX, INSTANCE_COLOR_OFFSET, INSTANCE_CORNERS_OFFSET, INSTANCE_FLOATS, INSTANCE_SLOTS_OFFSET, INSTANCE_UV_OFFSET, MAX_TEXTURE_SLOTS, type NinePatchBand, type NinePatchGeometry, type NinePatchView, POLYLINE_QUAD_FLOATS, type PixelTransform, type PolylineView, type QuadBatcher, type QuadBatcherOptions, type QuadInstance, type QuadView, RETAINED_DAMAGE_TILE_SIZE, RETAINED_MAX_DAMAGE_COVERAGE, RETAINED_MAX_REPLAY_FRACTION, type ReplayMaskScratch, type ReplaySelectionOptions, type RetainedReplayOptions, type RetainedReplayRegion, type RetainedReplayRegionsOptions, type RetainedSurface, type Rgba, type RoundedClip, STAGE_CONTEXT_ATTRIBUTES, type ScissorBox, type ScreenEffectDrawCommand, type ScreenEffectDrawContext, type StageCanvas, type StageProjection, type TextureCacheStats, type TexturedMeshView, applyColorMatrix01, blendStateFor, clamp01, colorMatricesEqual, commandDamageBounds, compileDrawList, createCanvasExecutor, createCanvasStage, createClipRectView, createClipStack, createCompiledReplayMask, createDamageRect, createDamageTiles, createDrawList, createDrawListFragment, createDrawListPatchView, createGlyphsView, createHeadlessGodotParticleDirectEffect, createHeadlessScreenEffectCommand, createNinePatchBand, createNinePatchBands, createNinePatchView, createPolylineView, createQuadBatcher, createQuadInstance, createQuadView, createReplayMask, createReplayMaskScratch, createRetainedSurface, createRgba, createScissorBox, createTextureCache, createTexturedMeshView, damageIntersects, expandNinePatch, expandPolyline, isAxisAligned, isDamageEmpty, isIdentityColorMatrix, isPartialReplayMask, maxPartialReplayCommands, modulatePremultiplied, outsetDamageRect, polylineQuadCapacity, premultiply, setViewColorMatrix, shadeQuadPixel, snapRetainedSize, transformDamageRect, unionDamageRect, unpremultiply };
|
|
1085
|
+
//# sourceMappingURL=index.d.ts.map
|