-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathMaterial.ts
713 lines (617 loc) · 25.1 KB
/
Material.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
import { isRenderer, Renderer } from '../renderers/utils'
import { BindGroup } from '../bindGroups/BindGroup'
import { TextureBindGroup } from '../bindGroups/TextureBindGroup'
import { Sampler } from '../samplers/Sampler'
import { AllowedPipelineEntries } from '../pipelines/PipelineManager'
import { BufferBinding, BufferBindingInput } from '../bindings/BufferBinding'
import { AllowedBindGroups, BindGroupBindingElement, BindGroupBufferBindingElement } from '../../types/BindGroups'
import { Texture } from '../textures/Texture'
import { FullShadersType, MaterialOptions, MaterialParams, ShaderOptions } from '../../types/Materials'
import { GPUCurtains } from '../../curtains/GPUCurtains'
import { RenderTexture } from '../textures/RenderTexture'
import { Binding } from '../bindings/Binding'
import { generateUUID } from '../../utils/utils'
import { BufferElement } from '../bindings/bufferElements/BufferElement'
/**
* Used as a base to create a {@link Material}.<br>
* The purpose of {@link Material} is to create and update the {@link BindGroup | bind groups} and their bindings (GPU buffers, textures and samplers), create a {@link core/pipelines/PipelineEntry.PipelineEntry | PipelineEntry} and use them to {@link Material#render | render}.
*
* ## Bind groups
*
* A {@link Material} automatically creates a {@link TextureBindGroup}, but it is actually added to the active {@link Material#bindGroups | bind groups array} only if necessary, which means if your shaders use a {@link GPUSampler}, a {@link GPUTexture} or a {@link GPUExternalTexture}.
*
* Another {@link BindGroup} will be created if you pass any {@link MaterialParams#uniforms | uniforms} or {@link MaterialParams#storages | storages} parameters.
*
* Finally, you can also pass already created {@link BindGroup} to a {@link Material} via the {@link MaterialParams#bindGroups | bindGroups} parameter.
*
* ----
*
* Note that this class is not intended to be used as is, but as a base for {@link core/materials/ComputeMaterial.ComputeMaterial | ComputeMaterial} and {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial} classes.
*/
export class Material {
/** The type of the {@link Material} */
type: string
/** The universal unique id of the {@link Material} */
uuid: string
/** The {@link Renderer} used */
renderer: Renderer
/** Options used to create this {@link Material} */
options: MaterialOptions
/** Pipeline entry used by this {@link Material} */
pipelineEntry: AllowedPipelineEntries
/**
* Array of {@link BindGroup | bind groups} used by this {@link Material}
* This array respects a specific order:
* 1. The {@link texturesBindGroup | textures bind groups}
* 2. The {@link BindGroup | bind group} created using {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters if any
* 3. Additional {@link MaterialParams#bindGroups | bind groups} parameters if any
*/
bindGroups: AllowedBindGroups[]
/** Array of {@link TextureBindGroup | texture bind groups} used by this {@link Material} */
texturesBindGroups: TextureBindGroup[]
/** Array of {@link BindGroup | bind groups} created using the {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters when instancing this {@link Material} */
inputsBindGroups: BindGroup[]
/** Array of {@link BindGroup | cloned bind groups} created by this {@link Material} */
clonedBindGroups: AllowedBindGroups[]
/** Object containing all uniforms inputs handled by this {@link Material} */
uniforms: Record<string, Record<string, BufferBindingInput>>
/** Object containing all read only or read/write storages inputs handled by this {@link Material} */
storages: Record<string, Record<string, BufferBindingInput>>
/** Array of {@link Binding | bindings} created using the {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters when instancing this {@link Material} */
inputsBindings: BindGroupBindingElement[]
/** Array of {@link Texture} handled by this {@link Material} */
textures: Texture[]
/** Array of {@link RenderTexture} handled by this {@link Material} */
renderTextures: RenderTexture[]
/** Array of {@link Sampler} handled by this {@link Material} */
samplers: Sampler[]
/**
* Material constructor
* @param renderer - our renderer class object
* @param parameters - {@link types/Materials.MaterialParams | parameters} used to create our Material
*/
constructor(renderer: Renderer | GPUCurtains, parameters: MaterialParams) {
this.type = 'Material'
// we could pass our curtains object OR our curtains renderer object
renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)
isRenderer(renderer, this.type)
this.renderer = renderer
this.uuid = generateUUID()
const {
shaders,
label,
useAsyncPipeline,
uniforms,
storages,
bindings,
bindGroups,
samplers,
textures,
renderTextures,
} = parameters
this.options = {
shaders,
label,
useAsyncPipeline: useAsyncPipeline === undefined ? true : useAsyncPipeline,
...(uniforms !== undefined && { uniforms }),
...(storages !== undefined && { storages }),
...(bindings !== undefined && { bindings }),
...(bindGroups !== undefined && { bindGroups }),
...(samplers !== undefined && { samplers }),
...(textures !== undefined && { textures }),
...(renderTextures !== undefined && { renderTextures }),
}
this.bindGroups = []
this.texturesBindGroups = []
this.clonedBindGroups = []
this.setBindGroups()
this.setTextures()
this.setSamplers()
}
/**
* Check if all bind groups are ready, and create them if needed
*/
compileMaterial() {
const texturesBindGroupLength = this.texturesBindGroup.bindings.length ? 1 : 0
const bindGroupsReady = this.bindGroups.length >= this.inputsBindGroups.length + texturesBindGroupLength
if (!bindGroupsReady) {
this.createBindGroups()
}
}
/**
* Get whether the renderer is ready, our pipeline entry and pipeline have been created and successfully compiled
* @readonly
*/
get ready(): boolean {
return !!(this.renderer.ready && this.pipelineEntry && this.pipelineEntry.pipeline && this.pipelineEntry.ready)
}
/**
* Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration.
* Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render
*/
loseContext() {
// start with the textures
this.textures.forEach((texture) => {
texture.texture = null
texture.sourceUploaded = false
})
this.renderTextures.forEach((texture) => {
texture.texture = null
})
// then bind groups and struct
;[...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach((bindGroup) =>
bindGroup.loseContext()
)
// reset pipeline as well
this.pipelineEntry.pipeline = null
}
/**
* Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored to recreate our bind groups.
*/
restoreContext() {
// start with the samplers and textures
this.samplers.forEach((sampler) => {
// the samplers have all been recreated by the renderer, just update the reference
sampler.createSampler()
sampler.binding.resource = sampler.sampler
})
// recreate the textures and resize them
this.textures.forEach((texture) => {
texture.createTexture()
texture.resize()
})
this.renderTextures.forEach((texture) => {
texture.resize(texture.size)
})
// now the bind groups
;[...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach((bindGroup) => {
if (bindGroup.shouldCreateBindGroup) {
bindGroup.createBindGroup()
}
// finally re-write all our buffers
bindGroup.bufferBindings.forEach((bufferBinding) => (bufferBinding.shouldUpdate = true))
})
}
/**
* Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline
* @param [shaderType="full"] - shader to get the code from
* @returns - The corresponding shader code
*/
getShaderCode(shaderType: FullShadersType = 'full'): string {
if (!this.pipelineEntry) return ''
shaderType = (() => {
switch (shaderType) {
case 'vertex':
case 'fragment':
case 'compute':
case 'full':
return shaderType
default:
return 'full'
}
})()
return this.pipelineEntry.shaders[shaderType].code
}
/**
* Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline
* @param [shaderType="vertex"] - shader to get the code from
* @returns - The corresponding shader code
*/
getAddedShaderCode(shaderType: FullShadersType = 'vertex'): string {
if (!this.pipelineEntry) return ''
shaderType = (() => {
switch (shaderType) {
case 'vertex':
case 'fragment':
case 'compute':
return shaderType
default:
return 'vertex'
}
})()
return this.pipelineEntry.shaders[shaderType].head
}
/* BIND GROUPS */
/**
* Prepare and set our bind groups based on inputs and bindGroups Material parameters
*/
setBindGroups() {
this.uniforms = {}
this.storages = {}
this.inputsBindGroups = []
this.inputsBindings = []
if (this.options.uniforms || this.options.storages || this.options.bindings) {
const inputsBindGroup = new BindGroup(this.renderer, {
label: this.options.label + ': Bindings bind group',
uniforms: this.options.uniforms,
storages: this.options.storages,
bindings: this.options.bindings,
})
this.processBindGroupBindings(inputsBindGroup)
this.inputsBindGroups.push(inputsBindGroup)
}
this.options.bindGroups?.forEach((bindGroup) => {
this.processBindGroupBindings(bindGroup)
this.inputsBindGroups.push(bindGroup)
})
}
/**
* Get the main {@link TextureBindGroup | texture bind group} created by this {@link Material} to manage all textures related struct
* @readonly
*/
get texturesBindGroup(): TextureBindGroup {
return this.texturesBindGroups[0]
}
/**
* Process all {@link BindGroup} struct and add them to the corresponding objects based on their binding types. Also store them in a inputsBindings array to facilitate further access to struct.
* @param bindGroup - The {@link BindGroup} to process
*/
processBindGroupBindings(bindGroup: BindGroup) {
bindGroup.bindings.forEach((inputBinding) => {
if (inputBinding.bindingType === 'uniform')
this.uniforms = {
...this.uniforms,
[inputBinding.name]: (inputBinding as BindGroupBufferBindingElement).inputs,
}
if (inputBinding.bindingType === 'storage')
this.storages = {
...this.storages,
[inputBinding.name]: (inputBinding as BindGroupBufferBindingElement).inputs,
}
this.inputsBindings.push(inputBinding)
})
}
/**
* Create the bind groups if they need to be created
*/
createBindGroups() {
// textures first
if (this.texturesBindGroup.shouldCreateBindGroup) {
this.texturesBindGroup.setIndex(this.bindGroups.length)
this.texturesBindGroup.createBindGroup()
this.bindGroups.push(this.texturesBindGroup)
}
// then uniforms/storages inputs
this.inputsBindGroups.forEach((bindGroup) => {
if (bindGroup.shouldCreateBindGroup) {
bindGroup.setIndex(this.bindGroups.length)
bindGroup.createBindGroup()
this.bindGroups.push(bindGroup)
}
})
// finally, bindGroups inputs
this.options.bindGroups?.forEach((bindGroup) => {
// it has been created but not been added yet? add it!
if (!bindGroup.shouldCreateBindGroup && !this.bindGroups.find((bG) => bG.uuid === bindGroup.uuid)) {
bindGroup.setIndex(this.bindGroups.length)
this.bindGroups.push(bindGroup)
}
// add it to our textures bind groups as well if needed
if (bindGroup instanceof TextureBindGroup && !this.texturesBindGroups.find((bG) => bG.uuid === bindGroup.uuid)) {
this.texturesBindGroups.push(bindGroup)
// also add the textures?
bindGroup.textures.forEach((texture) => {
if (texture instanceof Texture && !this.textures.find((t) => t.uuid === texture.uuid)) {
this.textures.push(texture)
} else if (texture instanceof RenderTexture && !this.renderTextures.find((t) => t.uuid === texture.uuid)) {
this.renderTextures.push(texture)
}
})
}
})
}
/**
* Clones a {@link BindGroup} from a list of buffers
* Useful to create a new bind group with already created buffers, but swapped
* @param parameters - parameters used to clone the {@link BindGroup | bind group}
* @param parameters.bindGroup - the BindGroup to clone
* @param parameters.bindings - our input binding buffers
* @param parameters.keepLayout - whether we should keep original bind group layout or not
* @returns - the cloned BindGroup
*/
cloneBindGroup({
bindGroup,
bindings = [],
keepLayout = true,
}: {
bindGroup?: AllowedBindGroups
bindings?: BindGroupBindingElement[]
keepLayout?: boolean
}): AllowedBindGroups | null {
if (!bindGroup) return null
const clone = bindGroup.clone({ bindings, keepLayout })
this.clonedBindGroups.push(clone)
return clone
}
/**
* Get a corresponding {@link BindGroup} or {@link TextureBindGroup} from one of its binding name/key
* @param bindingName - the binding name/key to look for
* @returns - bind group found or null if not found
*/
getBindGroupByBindingName(bindingName: BufferBinding['name'] = ''): AllowedBindGroups | null {
return (this.ready ? this.bindGroups : this.inputsBindGroups).find((bindGroup) => {
return bindGroup.bindings.find((binding) => binding.name === bindingName)
})
}
/**
* Destroy a bind group, only if it is not used by another object
* @param bindGroup - bind group to eventually destroy
*/
destroyBindGroup(bindGroup: AllowedBindGroups) {
// check if this bind group is used by another object before actually destroying it
const objectsUsingBindGroup = this.renderer.getObjectsByBindGroup(bindGroup)
const shouldDestroy =
!objectsUsingBindGroup || !objectsUsingBindGroup.find((object) => object.material.uuid !== this.uuid)
if (shouldDestroy) {
bindGroup.destroy()
}
}
/**
* Destroy all bind groups
*/
destroyBindGroups() {
this.bindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))
this.clonedBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))
this.texturesBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))
this.texturesBindGroups = []
this.inputsBindGroups = []
this.bindGroups = []
this.clonedBindGroups = []
}
/**
* {@link BindGroup#update | Update} all bind groups:
* - Update all {@link texturesBindGroups | textures bind groups} textures
* - Update its {@link BindGroup#bufferBindings | buffer bindings}
* - Check if it eventually needs a {@link BindGroup#resetBindGroup | reset}
* - Check if we need to flush the pipeline
*/
updateBindGroups() {
// now update all bind groups in use and check if they need to flush the pipeline
this.bindGroups.forEach((bindGroup) => {
bindGroup.update()
// if a bind group needs to flush the pipeline
// usually happens if one of the struct bindingType has changed,
// which means the shader should be re-patched and recreated
if (bindGroup.needsPipelineFlush && this.pipelineEntry.ready) {
this.pipelineEntry.flushPipelineEntry(this.bindGroups)
bindGroup.needsPipelineFlush = false
}
})
}
/* INPUTS */
/**
* Look for a {@link BindGroupBindingElement | binding} by name in all {@link inputsBindings | input bindings}
* @param bindingName - the binding name or key
* @returns - the found binding, or null if not found
*/
getBindingByName(bindingName: Binding['name'] = ''): BindGroupBindingElement | undefined {
return this.inputsBindings.find((binding) => binding.name === bindingName)
}
/**
* Look for a {@link BindGroupBufferBindingElement | buffer binding} by name in all {@link inputsBindings | input bindings}
* @param bindingName - the binding name or key
* @returns - the found binding, or null if not found
*/
getBufferBindingByName(bindingName: Binding['name'] = ''): BindGroupBufferBindingElement | undefined {
return this.inputsBindings.find((binding) => binding.name === bindingName && 'buffer' in binding) as
| BindGroupBufferBindingElement
| undefined
}
/**
* Force a given buffer binding update flag to update it at next render
* @param bufferBindingName - the buffer binding name
* @param bindingName - the binding name
*/
shouldUpdateInputsBindings(bufferBindingName?: BufferBinding['name'], bindingName?: BufferBindingInput['name']) {
if (!bufferBindingName) return
const bufferBinding = this.getBindingByName(bufferBindingName)
if (bufferBinding) {
if (!bindingName) {
Object.keys((bufferBinding as BindGroupBufferBindingElement).inputs).forEach((bindingKey) =>
(bufferBinding as BindGroupBufferBindingElement).shouldUpdateBinding(bindingKey)
)
} else {
;(bufferBinding as BindGroupBufferBindingElement).shouldUpdateBinding(bindingName)
}
}
}
/* SAMPLERS & TEXTURES */
/**
* Prepare our textures array and set the {@link TextureBindGroup}
*/
setTextures() {
this.textures = []
this.renderTextures = []
this.texturesBindGroups.push(
new TextureBindGroup(this.renderer, {
label: this.options.label + ': Textures bind group',
})
)
this.options.textures?.forEach((texture) => {
this.addTexture(texture)
})
this.options.renderTextures?.forEach((texture) => {
this.addTexture(texture)
})
}
/**
* Add a texture to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data)
* @param texture - texture to add
*/
addTexture(texture: Texture | RenderTexture) {
if (texture instanceof Texture) {
this.textures.push(texture)
} else if (texture instanceof RenderTexture) {
this.renderTextures.push(texture)
}
// is it used in our shaders?
if (
(this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(texture.options.name) !== -1) ||
(this.options.shaders.fragment &&
(this.options.shaders.fragment as ShaderOptions).code.indexOf(texture.options.name) !== -1) ||
(this.options.shaders.compute && this.options.shaders.compute.code.indexOf(texture.options.name) !== -1)
) {
this.texturesBindGroup.addTexture(texture)
}
}
/**
* Destroy a {@link Texture} or {@link RenderTexture}, only if it is not used by another object or cached.
* @param texture - {@link Texture} or {@link RenderTexture} to eventually destroy
*/
destroyTexture(texture: Texture | RenderTexture) {
// do not destroy a texture that must stay in cache
if ((texture as Texture).options.cache) return
// check if this texture is used by another object before actually destroying it
const objectsUsingTexture = this.renderer.getObjectsByTexture(texture)
const shouldDestroy =
!objectsUsingTexture || !objectsUsingTexture.some((object) => object.material.uuid !== this.uuid)
if (shouldDestroy) {
texture.destroy()
}
}
/**
* Destroy all the Material textures
*/
destroyTextures() {
this.textures?.forEach((texture) => this.destroyTexture(texture))
this.renderTextures?.forEach((texture) => this.destroyTexture(texture))
this.textures = []
this.renderTextures = []
}
/**
* Prepare our samplers array and always add a default sampler if not already passed as parameter
*/
setSamplers() {
this.samplers = []
this.options.samplers?.forEach((sampler) => {
this.addSampler(sampler)
})
// create our default sampler if needed
const hasDefaultSampler = this.samplers.find((sampler) => sampler.name === 'defaultSampler')
if (!hasDefaultSampler) {
const sampler = new Sampler(this.renderer, { name: 'defaultSampler' })
this.addSampler(sampler)
}
}
/**
* Add a sampler to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data)
* @param sampler - sampler to add
*/
addSampler(sampler: Sampler) {
this.samplers.push(sampler)
// is it used in our shaders?
if (
(this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(sampler.name) !== -1) ||
(this.options.shaders.fragment &&
(this.options.shaders.fragment as ShaderOptions).code.indexOf(sampler.name) !== -1) ||
(this.options.shaders.compute && this.options.shaders.compute.code.indexOf(sampler.name) !== -1)
) {
this.texturesBindGroup.addSampler(sampler)
}
}
/* BUFFER RESULTS */
/**
* Map a {@link GPUBuffer} and put a copy of the data into a {@link Float32Array}
* @param buffer - {@link GPUBuffer} to map
* @async
* @returns - {@link Float32Array} holding the {@link GPUBuffer} data
*/
async getBufferResult(buffer: GPUBuffer): Promise<Float32Array> {
await buffer.mapAsync(GPUMapMode.READ)
const result = new Float32Array(buffer.getMappedRange().slice(0))
buffer.unmap()
return result
}
/**
* Map the content of a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array}
* @param bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer}
* @async
* @returns - {@link Float32Array} holding the {@link GPUBuffer} data
*/
async getBufferBindingResultByBindingName(bindingName: Binding['name'] = ''): Promise<Float32Array> {
const binding = this.getBufferBindingByName(bindingName)
if (binding && 'buffer' in binding) {
const dstBuffer = this.renderer.copyBufferToBuffer({
srcBuffer: binding.buffer,
})
return await this.getBufferResult(dstBuffer)
} else {
return new Float32Array(0)
}
}
/**
* Map the content of a specific {@link BufferElement | buffer element} belonging to a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array}
* @param parameters - parameters used to get the result
* @param parameters.bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer}
* @param parameters.bufferElementName - The name of the {@link BufferElement | buffer element} from which to extract the data afterwards
* @returns - {@link Float32Array} holding {@link GPUBuffer} data
*/
async getBufferElementResultByNames({
bindingName,
bufferElementName,
}: {
bindingName: Binding['name']
bufferElementName: BufferElement['name']
}): Promise<Float32Array> {
const result = await this.getBufferBindingResultByBindingName(bindingName)
if (!bufferElementName || result.length) {
return result
} else {
const binding = this.getBufferBindingByName(bindingName)
if (binding) {
return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName })
} else {
return result
}
}
}
/* RENDER */
/**
* Called before rendering the Material.
* First, check if we need to create our bind groups or pipeline
* Then render the {@link textures}
* Finally updates all the {@link bindGroups | bind groups}
*/
onBeforeRender() {
// set our material if needed
this.compileMaterial()
// first what needs to be done for all textures
this.textures.forEach((texture) => {
texture.render()
})
// update bind groups
this.updateBindGroups()
}
/**
* Set the current pipeline
* @param pass - current pass encoder
*/
setPipeline(pass: GPURenderPassEncoder | GPUComputePassEncoder) {
this.renderer.pipelineManager.setCurrentPipeline(pass, this.pipelineEntry)
}
/**
* Render the material if it is ready:
* Set the current pipeline and set the bind groups
* @param pass - current pass encoder
*/
render(pass: GPURenderPassEncoder | GPUComputePassEncoder) {
// renderer or pipeline are not ready yet
// not really needed since meshes/compute passes do already check it beforehand
// mostly here as a safeguard
if (!this.ready) return
// set current pipeline
this.setPipeline(pass)
// set bind groups
this.bindGroups.forEach((bindGroup) => {
pass.setBindGroup(bindGroup.index, bindGroup.bindGroup)
})
}
/**
* Destroy the Material
*/
destroy() {
// destroy all buffers created with createBuffer
this.destroyBindGroups()
this.destroyTextures()
}
}