@solidrt/2d 0.0.71 → 0.0.73
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/AGENTS.md +211 -42
- package/README.md +7 -3
- package/examples/README.md +1 -1
- package/examples/camera-probe.tsx +4 -4
- package/examples/markers.tsx +3 -3
- package/package.json +6 -5
- package/src/animation.ts +26 -5
- package/src/atlas.ts +8 -4
- package/src/camera.ts +2 -2
- package/src/components/camera2d.tsx +14 -5
- package/src/components/group.tsx +15 -3
- package/src/components/shots.tsx +2 -2
- package/src/components/sprite-layer.tsx +44 -12
- package/src/components/sprite.tsx +31 -3
- package/src/components/text2d.tsx +2 -2
- package/src/components/tile-layer.tsx +21 -7
- package/src/components/view2d.tsx +2 -2
- package/src/dispatch.ts +10 -6
- package/src/frames.ts +1 -1
- package/src/glsl.ts +399 -0
- package/src/index.ts +14 -6
- package/src/layer.ts +240 -101
- package/src/material.ts +368 -0
- package/src/records.ts +135 -32
- package/src/style.ts +98 -0
- package/src/tiles.ts +119 -24
- package/src/views.ts +17 -9
- package/src/shaders.ts +0 -323
package/src/material.ts
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// Materials: GLSL paired with pipeline state, attached to a LAYER - the
|
|
2
|
+
// layer is the batch (its atlases bound together, N quads in one draw per
|
|
3
|
+
// view), so the material sits on the thing that is one draw entry, as
|
|
4
|
+
// @solidrt/3d's sits on the mesh. Two 2d sprites with different materials
|
|
5
|
+
// are two layers, which is also how you order them (an additive glow
|
|
6
|
+
// layer draws over the scene layer). Deduped hard, the 3d way: one
|
|
7
|
+
// program per material CLASS and atlas list met (the fragment set is
|
|
8
|
+
// generated per atlas list, spriteSource), one render pipeline per
|
|
9
|
+
// instance-layout list met on top (the node layer binds a pose and a
|
|
10
|
+
// sprite buffer, the records and tile layers one record buffer; a
|
|
11
|
+
// pipeline is program + attribute list, so the program never recompiles
|
|
12
|
+
// for another layer kind), created lazily at first use and kept for the
|
|
13
|
+
// class's lifetime. A material INSTANCE is the params and sampler
|
|
14
|
+
// bindings it contributes to every view and chunk target of its layer.
|
|
15
|
+
//
|
|
16
|
+
// Three tiers of custom look, the same three as @solidrt/3d's: a custom
|
|
17
|
+
// vertex stage under the stock fragment (shaderMaterialClass with
|
|
18
|
+
// unlitFragment from ./glsl), a `surface` function inside the stock
|
|
19
|
+
// material (unlit's prelude/surface slot over the Sprite struct), or a
|
|
20
|
+
// fragment of your own over the layer's set (shaderMaterialClass). Per-
|
|
21
|
+
// sprite data for any of them is the material's `instanceBuffers`: an
|
|
22
|
+
// app-owned style record per sprite in any vertex format, written with
|
|
23
|
+
// setInstanceStyle / instanceAttribute / records(layer, stream), the 3d
|
|
24
|
+
// vocabulary unchanged.
|
|
25
|
+
//
|
|
26
|
+
// `blend` is pipeline state and lives here, as in 3d; the deliberate
|
|
27
|
+
// asymmetry is that 3d's `blend` implies `transparent` and the scene's
|
|
28
|
+
// back-to-front sort, while a layer has no depth and draws in record
|
|
29
|
+
// order, so nothing sorts.
|
|
30
|
+
import { compileShader, createBuffer, createRenderPipeline, destroyBuffer, destroyProgram, destroyRenderPipeline, destroyShader, encodeRecord, checkLayout, layoutComponents, layoutKey, linkProgram } from "@solidrt/core/gpu"
|
|
31
|
+
import type { BlendMode, BufferId, ProgramId, RenderPipelineId, ShaderParams, TextureBindings, VertexAttribute, VertexBufferLayout } from "@solidrt/core/gpu"
|
|
32
|
+
import { forwardedName, LAYER_ATTRIBUTE_NAMES, spriteSource, unlitFragment, unlitVertex, VERTEX_UNIFORMS } from "./glsl.ts"
|
|
33
|
+
import type { AtlasShading, UnlitSourceOptions } from "./glsl.ts"
|
|
34
|
+
|
|
35
|
+
/** What a layer draws with once its material met its atlases and its
|
|
36
|
+
* instance layouts: the pipeline and the unit quad every instance
|
|
37
|
+
* reuses (the class's, shared by every layer of the class). */
|
|
38
|
+
export type SpritePipeline = { pipeline: RenderPipelineId; quad: BufferId }
|
|
39
|
+
|
|
40
|
+
export type Material = {
|
|
41
|
+
/**
|
|
42
|
+
* The pipeline this material draws a layer with, for the layer's
|
|
43
|
+
* atlases (the fragment set is generated for them: one sampler per
|
|
44
|
+
* atlas, the distance-field decodes) and its instance layouts (the
|
|
45
|
+
* layer's own records, the material's instance buffers after them) -
|
|
46
|
+
* lazily created, one program per atlas list and one pipeline per
|
|
47
|
+
* layout list met. The layers call it at creation.
|
|
48
|
+
*/
|
|
49
|
+
pipeline(atlases: readonly AtlasShading[], layouts: readonly VertexAttribute[][], label: string): SpritePipeline
|
|
50
|
+
/** The uniform values this material seeds on every target of its
|
|
51
|
+
* layer (a view, a tile chunk); the layer's own params apply over them. */
|
|
52
|
+
params: ShaderParams
|
|
53
|
+
/** Sampler bindings beyond the atlases (a palette LUT, a noise
|
|
54
|
+
* texture), bound on every target of the layer. They share the
|
|
55
|
+
* device's sampler budget with the atlases (limits.maxTextureUnits). */
|
|
56
|
+
textures?: TextureBindings
|
|
57
|
+
/** How the layer's sprites blend into its targets: pipeline state,
|
|
58
|
+
* fixed with the material. */
|
|
59
|
+
blend: BlendMode
|
|
60
|
+
/** The instance buffers the pipeline declares after the layer's own
|
|
61
|
+
* (shaderMaterialClass's `instanceBuffers`): one per-sprite style
|
|
62
|
+
* record stream each, app-owned, in any vertex format. */
|
|
63
|
+
instanceBuffers?: VertexBufferLayout[]
|
|
64
|
+
/** The style record a fresh sprite starts with (one value per
|
|
65
|
+
* component of the FIRST instance buffer's attributes, in order);
|
|
66
|
+
* absent = zeros. */
|
|
67
|
+
instanceStyle?: ArrayLike<number>
|
|
68
|
+
/** Present on materials that own their pipeline (shaderMaterial). */
|
|
69
|
+
dispose?(): void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ShaderMaterialClassOptions = {
|
|
73
|
+
/**
|
|
74
|
+
* Vertex stage GLSL. MUST declare and use `uniform vec4 uCamera`,
|
|
75
|
+
* `uniform vec4 uCameraRot` and `uniform vec2 uViewport` (the view's
|
|
76
|
+
* camera mapping, written per view) - a source mentioning any of them
|
|
77
|
+
* not at all throws right here - and write the varyings the fragment
|
|
78
|
+
* set reads (SPRITE_VARYINGS from `@solidrt/2d/glsl`, SPRITE_VERTEX_BODY
|
|
79
|
+
* writes them). Declare any of the layer's record attributes by name
|
|
80
|
+
* (iPos, iRot, iScale, iUv, iTint, iScreenPx, iAtlas, iOutline - the
|
|
81
|
+
* same names on every layer kind) and your own instance buffers';
|
|
82
|
+
* undeclared ones are skipped. SPRITE_VERTEX is the stock stage.
|
|
83
|
+
*/
|
|
84
|
+
vertex: string
|
|
85
|
+
/**
|
|
86
|
+
* Fragment stage GLSL over the layer's SET, which the layer prepends:
|
|
87
|
+
* the varyings, the atlas samplers, `uTint`, the `Sprite` struct,
|
|
88
|
+
* `spriteOf()` and `spriteSample(uv)` (spriteSource in `./glsl`) are
|
|
89
|
+
* declared for you - declare none of them. Start main with `Sprite s
|
|
90
|
+
* = spriteOf();` (the derivatives every tap uses are taken there);
|
|
91
|
+
* `unlitFragment(options)` is the stock one.
|
|
92
|
+
*/
|
|
93
|
+
fragment: string
|
|
94
|
+
/**
|
|
95
|
+
* Instance buffers: one layout per per-sprite buffer (`{ attributes
|
|
96
|
+
* }`: any vertex format, tightly packed in list order; stepMode is
|
|
97
|
+
* "instance" by definition here), bound after the layer's own records.
|
|
98
|
+
* The vertex stage reads the attributes as `in` variables beside the
|
|
99
|
+
* record's, and each drawn sprite gets one record from each buffer:
|
|
100
|
+
* the material's STYLE record(s), written with setInstanceStyle (the
|
|
101
|
+
* first buffer), instanceAttribute + updateRecords (any), or
|
|
102
|
+
* records(layer, stream) in bulk. A palette index is a `uint8x4` at
|
|
103
|
+
* four bytes, a dissolve amount a `float16x2`. Names may not reuse the
|
|
104
|
+
* layer's own (LAYER_ATTRIBUTE_NAMES).
|
|
105
|
+
*/
|
|
106
|
+
instanceBuffers?: VertexBufferLayout[]
|
|
107
|
+
/** The style record a fresh sprite starts with (one value per
|
|
108
|
+
* component of the first instance buffer's attributes, in order);
|
|
109
|
+
* default zeros. The identity of whatever the stage does with the
|
|
110
|
+
* record - white for a multiplied tint. */
|
|
111
|
+
instanceStyle?: ArrayLike<number>
|
|
112
|
+
/**
|
|
113
|
+
* How the sprites blend into the layer's targets, core gpu's
|
|
114
|
+
* BlendMode; default "alpha" (premultiplied source-over in draw order,
|
|
115
|
+
* the fragment writing premultiplied output). "add" for glows,
|
|
116
|
+
* explosions and additive particles, "multiply" to darken, "none" to
|
|
117
|
+
* overwrite.
|
|
118
|
+
*/
|
|
119
|
+
blend?: BlendMode
|
|
120
|
+
label?: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The instance half of a shader material: uniform seeds and sampler
|
|
124
|
+
* bindings for one parameterisation of a class's program. */
|
|
125
|
+
export type ShaderMaterialInstanceOptions = {
|
|
126
|
+
/** Uniform seeds beyond the standard set (a prelude's uniforms);
|
|
127
|
+
* update per layer later with setLayerParams. */
|
|
128
|
+
params?: ShaderParams
|
|
129
|
+
/** Sampler bindings beyond the atlases; names may not be an atlas
|
|
130
|
+
* sampler's (uAtlasN). */
|
|
131
|
+
textures?: TextureBindings
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type ShaderMaterialOptions = ShaderMaterialClassOptions & ShaderMaterialInstanceOptions
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* One program and pipeline, many parameterisations: the class/instance
|
|
138
|
+
* split unlit has internally, for your own GLSL. `instance()` returns a
|
|
139
|
+
* Material sharing the class's pipelines with its own params/textures -
|
|
140
|
+
* the class compiles once per atlas list met, and dispose() is on the
|
|
141
|
+
* class alone (instances hold nothing of their own).
|
|
142
|
+
*/
|
|
143
|
+
export type ShaderMaterialClass = {
|
|
144
|
+
instance(opts?: ShaderMaterialInstanceOptions): Material
|
|
145
|
+
/** Destroy the shared programs, pipelines and quad. Layers still
|
|
146
|
+
* drawing with an instance draw nothing valid afterwards. */
|
|
147
|
+
dispose(): void
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// The atlas sampler names a material texture may not take.
|
|
151
|
+
const ATLAS_UNIFORM = /^uAtlas\d+$/
|
|
152
|
+
|
|
153
|
+
/** The key of an atlas list for the program cache: the shading of each
|
|
154
|
+
* atlas in order (a colour atlas, or a field with its range), which is
|
|
155
|
+
* exactly what spriteSource varies on. */
|
|
156
|
+
function atlasKey(atlases: readonly AtlasShading[]): string {
|
|
157
|
+
return atlases.map(a => (a.sdf === undefined ? "rgb" : "sdf" + a.sdf.range)).join(",")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The texture bindings' validation, shared by the instance forms:
|
|
161
|
+
* a name an atlas sampler takes is a throw. */
|
|
162
|
+
function checkTextures(site: string, textures: TextureBindings | undefined): void {
|
|
163
|
+
if (textures === undefined) return
|
|
164
|
+
for (let name of Object.keys(textures)) {
|
|
165
|
+
if (ATLAS_UNIFORM.test(name)) throw new Error(`${site}: texture '${name}' is an atlas sampler's name; the layer binds those itself`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A material class from your own GLSL: sources without a `#version` line
|
|
171
|
+
* get the standard pipeline preamble (`fragColor`, `iResolution`), the
|
|
172
|
+
* fragment behind the layer's generated set. Two calls with identical
|
|
173
|
+
* sources compile two programs - there is no dedupe by source value (a
|
|
174
|
+
* hidden cache keyed by content is the anti-pattern the GPU layer avoids
|
|
175
|
+
* throughout); the class IS the app-owned split. Create one per program
|
|
176
|
+
* at app scope, `instance()` per look, and `dispose()` the class when
|
|
177
|
+
* the app is done with the look for good.
|
|
178
|
+
*/
|
|
179
|
+
export function shaderMaterialClass(opts: ShaderMaterialClassOptions): ShaderMaterialClass {
|
|
180
|
+
// The camera contract, checked where the mistake is made: a vertex
|
|
181
|
+
// stage that never mentions the mapping uniforms cannot place sprites,
|
|
182
|
+
// and with shared params skipping undeclared names the omission would
|
|
183
|
+
// otherwise surface as a blank view, not an error.
|
|
184
|
+
for (let name of VERTEX_UNIFORMS) {
|
|
185
|
+
if (!new RegExp("\\b" + name + "\\b").test(opts.vertex)) {
|
|
186
|
+
throw new Error(`shaderMaterial vertex stage must declare and use '${name}' (the camera mapping; see SPRITE_VARYINGS in @solidrt/2d/glsl)`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Records are tightly packed like the layer's own; explicit strides and
|
|
190
|
+
// offsets are the engine's, reachable through core's own pipeline API.
|
|
191
|
+
// An unknown format would otherwise make a NaN stride at the layer.
|
|
192
|
+
for (let layout of opts.instanceBuffers ?? []) {
|
|
193
|
+
if (layout.stepMode === "vertex") throw new Error("shaderMaterial instanceBuffers are instance-step by definition; drop stepMode")
|
|
194
|
+
if (layout.arrayStride !== undefined || layout.attributes.some(a => a.offset !== undefined)) {
|
|
195
|
+
throw new Error("shaderMaterial instanceBuffers are tightly packed records; drop arrayStride and offset")
|
|
196
|
+
}
|
|
197
|
+
checkLayout(layout.attributes, "shaderMaterial instanceBuffers")
|
|
198
|
+
for (let attr of layout.attributes) {
|
|
199
|
+
if (LAYER_ATTRIBUTE_NAMES.includes(attr.name)) throw new Error(`shaderMaterial instance attribute '${attr.name}' is one of the layer's own record attributes; pick another name`)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// An empty list declares nothing - same as absent.
|
|
203
|
+
let instanceBuffers = opts.instanceBuffers?.length ? opts.instanceBuffers.map(b => ({ stepMode: "instance" as const, attributes: b.attributes.map(a => ({ ...a })) })) : undefined
|
|
204
|
+
if (opts.instanceStyle !== undefined) {
|
|
205
|
+
if (instanceBuffers === undefined) throw new Error("shaderMaterial instanceStyle needs an instance buffer to describe")
|
|
206
|
+
// Encoded once here so a wrong length throws at creation, not at the
|
|
207
|
+
// first addSprite.
|
|
208
|
+
encodeRecord(instanceBuffers[0]!.attributes, opts.instanceStyle, "shaderMaterial instanceStyle")
|
|
209
|
+
}
|
|
210
|
+
let blend = opts.blend ?? "alpha"
|
|
211
|
+
let label = opts.label ?? "shader-material"
|
|
212
|
+
let quad: BufferId | undefined
|
|
213
|
+
let programs = new Map<string, ProgramId>()
|
|
214
|
+
let pipelines = new Map<string, RenderPipelineId>()
|
|
215
|
+
let quadFor = (): BufferId => {
|
|
216
|
+
// One unit quad (triangle strip), reused by every instance of every
|
|
217
|
+
// layer of the class.
|
|
218
|
+
if (quad === undefined) quad = createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]), { label: `${label}-quad`, autoFree: false })
|
|
219
|
+
return quad
|
|
220
|
+
}
|
|
221
|
+
let programFor = (atlases: readonly AtlasShading[]): ProgramId => {
|
|
222
|
+
let key = atlasKey(atlases)
|
|
223
|
+
let program = programs.get(key)
|
|
224
|
+
if (program === undefined) {
|
|
225
|
+
let vs = compileShader("vertex", opts.vertex, { header: true })
|
|
226
|
+
let fs = compileShader("fragment", spriteSource(atlases) + opts.fragment, { header: true })
|
|
227
|
+
program = linkProgram(vs, fs, { label })
|
|
228
|
+
destroyShader(vs)
|
|
229
|
+
destroyShader(fs)
|
|
230
|
+
programs.set(key, program)
|
|
231
|
+
}
|
|
232
|
+
return program
|
|
233
|
+
}
|
|
234
|
+
let pipelineFor = (atlases: readonly AtlasShading[], layouts: readonly VertexAttribute[][], layerLabel: string): SpritePipeline => {
|
|
235
|
+
let key = atlasKey(atlases) + "|" + layouts.map(l => layoutKey(l)).join("|")
|
|
236
|
+
let pipeline = pipelines.get(key)
|
|
237
|
+
if (pipeline === undefined) {
|
|
238
|
+
pipeline = createRenderPipeline(programFor(atlases), {
|
|
239
|
+
label: `${label}-${layerLabel}`,
|
|
240
|
+
topology: "triangle-strip",
|
|
241
|
+
buffers: [{ attributes: [{ name: "aPos", format: "float32x2" }] }, ...layouts.map(attributes => ({ stepMode: "instance" as const, attributes })), ...(instanceBuffers ?? [])],
|
|
242
|
+
blend,
|
|
243
|
+
})
|
|
244
|
+
pipelines.set(key, pipeline)
|
|
245
|
+
}
|
|
246
|
+
return { pipeline, quad: quadFor() }
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
instance(inst = {}) {
|
|
250
|
+
checkTextures("shaderMaterial instance", inst.textures)
|
|
251
|
+
return {
|
|
252
|
+
pipeline: pipelineFor,
|
|
253
|
+
params: inst.params ?? {},
|
|
254
|
+
textures: inst.textures,
|
|
255
|
+
blend,
|
|
256
|
+
instanceBuffers,
|
|
257
|
+
instanceStyle: opts.instanceStyle,
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
dispose() {
|
|
261
|
+
for (let pipeline of pipelines.values()) destroyRenderPipeline(pipeline)
|
|
262
|
+
pipelines.clear()
|
|
263
|
+
for (let program of programs.values()) destroyProgram(program)
|
|
264
|
+
programs.clear()
|
|
265
|
+
if (quad !== undefined) {
|
|
266
|
+
destroyBuffer(quad)
|
|
267
|
+
quad = undefined
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A material from your own GLSL: the custom-look escape hatch, first-class
|
|
275
|
+
* next to unlit. A class with a single instance - `shaderMaterialClass()`
|
|
276
|
+
* is the form for one program with many parameterisations. The INSTANCE
|
|
277
|
+
* is the pipeline handle: two calls with identical sources compile two
|
|
278
|
+
* programs. Create one per look at app scope, share it across layers,
|
|
279
|
+
* and `dispose()` it if the app is done with the look for good.
|
|
280
|
+
*/
|
|
281
|
+
export function shaderMaterial(opts: ShaderMaterialOptions): Material {
|
|
282
|
+
let cls = shaderMaterialClass(opts)
|
|
283
|
+
let material = cls.instance(opts)
|
|
284
|
+
material.dispose = cls.dispose
|
|
285
|
+
return material
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export type UnlitOptions = Omit<UnlitSourceOptions, "forwards"> & {
|
|
289
|
+
/** See ShaderMaterialClassOptions.blend; default "alpha". */
|
|
290
|
+
blend?: BlendMode
|
|
291
|
+
/** Uniform seeds for the prelude's uniforms (the layer's params apply
|
|
292
|
+
* over them; update later with `layer.setParams`). */
|
|
293
|
+
params?: ShaderParams
|
|
294
|
+
/** Sampler bindings the prelude declares (a palette LUT, a noise
|
|
295
|
+
* texture), beyond the atlases. */
|
|
296
|
+
textures?: TextureBindings
|
|
297
|
+
/**
|
|
298
|
+
* Per-sprite style record(s) the surface reads, see
|
|
299
|
+
* ShaderMaterialClassOptions.instanceBuffers. The stock vertex stage
|
|
300
|
+
* forwards every attribute unchanged as a flat varying - `iPalette`
|
|
301
|
+
* reaches the prelude and the surface as `vPalette` (forwardedName),
|
|
302
|
+
* so the names follow the layer's own `i` + capital convention - and
|
|
303
|
+
* the app writes the records with setInstanceStyle, instanceAttribute
|
|
304
|
+
* or records(layer, stream). A palette index per sprite, a flash
|
|
305
|
+
* amount, a dissolve threshold: the data a look varies on.
|
|
306
|
+
*/
|
|
307
|
+
instanceBuffers?: VertexBufferLayout[]
|
|
308
|
+
/** See ShaderMaterialClassOptions.instanceStyle. */
|
|
309
|
+
instanceStyle?: ArrayLike<number>
|
|
310
|
+
label?: string
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// One shaderMaterialClass per unlit option combination (blend x prelude x
|
|
314
|
+
// surface x instance layouts), cached for the app's lifetime like 3d's;
|
|
315
|
+
// one program per atlas list and one pipeline per layout list inside
|
|
316
|
+
// each.
|
|
317
|
+
let unlitClasses = new Map<string, ShaderMaterialClass>()
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The stock material, the one every layer draws with unless told
|
|
321
|
+
* otherwise: the atlas texel times the sprite's tint (a distance-field
|
|
322
|
+
* atlas decoded to its fill and outline), times the layer tint, blended
|
|
323
|
+
* with `blend`. `prelude`/`surface` is the tier-2 slot: `void
|
|
324
|
+
* surface(inout Sprite s)` called with the struct filled and before the
|
|
325
|
+
* layer tint - a palette lookup, a hit flash, a dissolve, a scrolling
|
|
326
|
+
* `spriteSample` - with the prelude's uniforms as layer params and the
|
|
327
|
+
* `instanceBuffers` records as flat varyings. One program per distinct
|
|
328
|
+
* source, keyed like every option; the name says the shading model,
|
|
329
|
+
* Unity's Sprite-Unlit, which a 2d `lit` would differ on.
|
|
330
|
+
*/
|
|
331
|
+
export function unlit(opts: UnlitOptions = {}): Material {
|
|
332
|
+
let blend = opts.blend ?? "alpha"
|
|
333
|
+
let prelude = opts.prelude ?? ""
|
|
334
|
+
let surface = opts.surface ?? ""
|
|
335
|
+
let instanceBuffers = opts.instanceBuffers?.length ? opts.instanceBuffers : undefined
|
|
336
|
+
let forwards = instanceBuffers?.flatMap(b => b.attributes) ?? []
|
|
337
|
+
for (let attr of forwards) forwardedName(attr.name)
|
|
338
|
+
let key = [blend, prelude, surface, forwards.map(a => a.name + ":" + a.format).join(","), opts.instanceStyle === undefined ? "" : Array.from(opts.instanceStyle).join(" ")].join("|")
|
|
339
|
+
let cls = unlitClasses.get(key)
|
|
340
|
+
if (cls === undefined) {
|
|
341
|
+
cls = shaderMaterialClass({
|
|
342
|
+
vertex: unlitVertex(forwards),
|
|
343
|
+
fragment: unlitFragment({ prelude, surface, forwards }),
|
|
344
|
+
instanceBuffers,
|
|
345
|
+
instanceStyle: opts.instanceStyle,
|
|
346
|
+
blend,
|
|
347
|
+
label: opts.label ?? "unlit",
|
|
348
|
+
})
|
|
349
|
+
unlitClasses.set(key, cls)
|
|
350
|
+
}
|
|
351
|
+
return cls.instance({ params: opts.params, textures: opts.textures })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The sampler count a material binds beyond the atlases: what
|
|
355
|
+
* checkAtlases subtracts from the device's budget. Internal. */
|
|
356
|
+
export function materialSamplers(material: Material): number {
|
|
357
|
+
return material.textures === undefined ? 0 : Object.keys(material.textures).length
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** The style record a fresh sprite of `material` starts with in its
|
|
361
|
+
* instance buffer `i`: the material's instanceStyle encoded for the
|
|
362
|
+
* first buffer, zeros for every other. Internal - the layers copy it
|
|
363
|
+
* into every new slot. */
|
|
364
|
+
export function materialBlank(material: Material, i: number): Uint8Array {
|
|
365
|
+
let layout = material.instanceBuffers![i]!.attributes
|
|
366
|
+
let values = i === 0 && material.instanceStyle !== undefined ? material.instanceStyle : new Array<number>(layoutComponents(layout)).fill(0)
|
|
367
|
+
return encodeRecord(layout, values, "instanceStyle")
|
|
368
|
+
}
|