122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import { BindGroupLayout } from './bind-group-layout.js'
|
|
import { FragmentStateDescriptor, ShaderPair, ShaderPairDescriptor, VertexStateDescriptor } from './shader-module.js'
|
|
import { MaterialError, WebGPUObjectError } from '../utils/errors.js'
|
|
import { ResourceType } from 'wgsl_reflect'
|
|
import { BindGroup } from './bind-group.js'
|
|
import { Texture } from './texture.js'
|
|
import { Buffer } from './buffer.js'
|
|
import { Sampler } from './sampler.js'
|
|
|
|
/** @import { FragmentStateDescriptor, VertexStateDescriptor } from './shader-module.js' */
|
|
|
|
type BindingResource = Buffer | Texture | Sampler
|
|
|
|
interface MaterialPipelineDescriptor {
|
|
label?: string
|
|
pipelineLayout?: GPUPipelineLayout
|
|
vertex: VertexStateDescriptor
|
|
fragment?: FragmentStateDescriptor
|
|
primitive?: GPUPrimitiveState
|
|
}
|
|
|
|
interface MaterialDescriptor extends ShaderPairDescriptor {
|
|
bindGroupLayouts?: BindGroupLayout[]
|
|
}
|
|
|
|
export class Material {
|
|
_device: GPUDevice
|
|
_shaders: ShaderPair
|
|
_bindGroupLayouts: BindGroupLayout[]
|
|
_pipelineLayout: GPUPipelineLayout
|
|
|
|
get shaders() {
|
|
return this._shaders
|
|
}
|
|
|
|
get bindGroupLayouts() {
|
|
return this._bindGroupLayouts
|
|
}
|
|
|
|
constructor(device: GPUDevice, descriptor: MaterialDescriptor) {
|
|
this._device = device
|
|
this._shaders = Material._reflectShaders(descriptor)
|
|
const bgl = descriptor.bindGroupLayouts
|
|
|
|
if (bgl && bgl.length > 0) {
|
|
this._bindGroupLayouts = bgl
|
|
} else {
|
|
this._bindGroupLayouts = this._reflectBindGroupLayouts(device, this._shaders)
|
|
}
|
|
|
|
if (this._bindGroupLayouts && this.bindGroupLayouts.length > 0) {
|
|
try {
|
|
this._pipelineLayout = device.createPipelineLayout({
|
|
bindGroupLayouts: this._bindGroupLayouts.map(bgl => bgl.handle)
|
|
})
|
|
} catch (err) {
|
|
throw WebGPUObjectError.from(err, Material)
|
|
}
|
|
}
|
|
}
|
|
|
|
static _reflectShaders(shaders: ShaderPairDescriptor): ShaderPair {
|
|
if (shaders == null) {
|
|
throw MaterialError.missingShader('vertex')
|
|
}
|
|
|
|
if ('vertex' in shaders) {
|
|
return ShaderPair.fromPair(shaders)
|
|
}
|
|
}
|
|
|
|
_reflectBindGroupLayouts(device: GPUDevice, shaders: ShaderPair): BindGroupLayout[] {
|
|
const layouts = shaders.createBindGroupLayoutEntries()
|
|
return layouts.map(entries => BindGroupLayout.create(device, { entries }))
|
|
}
|
|
|
|
createBindGroup(groupIndex: number, resources: Record<PropertyKey, BindingResource>, label?: string) {
|
|
if (groupIndex < 0 || groupIndex >= this._bindGroupLayouts.length) {
|
|
throw new Error(`Invalid bind group index: ${groupIndex}`)
|
|
}
|
|
|
|
const bgl = this._bindGroupLayouts[groupIndex]
|
|
|
|
let entries = []
|
|
for (const name in resources) {
|
|
const resource = resources[name]
|
|
|
|
const variableInfo = this._shaders.findVariableInfo(name, groupIndex)
|
|
|
|
const entry = {
|
|
binding: variableInfo.binding,
|
|
resource: resource.toBindingResource()
|
|
}
|
|
|
|
if (variableInfo.resourceType === ResourceType.Uniform || variableInfo.resourceType === ResourceType.Storage) {
|
|
// TODO: handle user provided offset/size
|
|
}
|
|
|
|
entries.push(entry)
|
|
}
|
|
|
|
entries.sort((a, b) => a.binding - b.binding)
|
|
|
|
return BindGroup.create(this._device, {
|
|
layout: bgl.handle,
|
|
entries,
|
|
label
|
|
})
|
|
}
|
|
|
|
getRenderPipelineDescriptor(descriptor: MaterialPipelineDescriptor): GPURenderPipelineDescriptor {
|
|
const { fragment, vertex } = this.shaders.getRenderPipelineStates(descriptor)
|
|
|
|
return {
|
|
label: descriptor.label,
|
|
layout: descriptor.pipelineLayout || this._pipelineLayout,
|
|
fragment,
|
|
vertex,
|
|
primitive: descriptor.primitive || { topology: 'triangle-list' }
|
|
}
|
|
}
|
|
}
|