automatic material bind group generation

This commit is contained in:
Rowan 2025-04-21 04:07:30 -05:00
parent cad8a1555e
commit d75f3b4c49
9 changed files with 231 additions and 8 deletions

83
dist/index.js vendored
View file

@ -412,6 +412,18 @@
} }
return result; return result;
} }
/**
* @param {Object} [descriptor={}]
* @param {number} [descriptor.offset=0]
* @param {number} [descriptor.size]
*/
toBindingResource({ offset, size } = {}) {
return {
buffer: this._handle,
offset: offset || 0,
size: size || this.size
};
}
destroy() { destroy() {
this._handle?.destroy(); this._handle?.destroy();
this._handle = null; this._handle = null;
@ -6320,7 +6332,8 @@
return this._reflection; return this._reflection;
} }
}; };
var ReflectedShader = class { var ReflectedShader = class _ReflectedShader {
static _reflectTypes = ["uniforms", "storage", "textures", "samplers"];
_module; _module;
get module() { get module() {
return this._module; return this._module;
@ -6331,6 +6344,23 @@
constructor(shader) { constructor(shader) {
this._module = shader; this._module = shader;
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) {
const reflection = this.module.reflect();
for (const type of _ReflectedShader._reflectTypes) {
const variables = reflection[type];
if (variables) {
const variable = variables.find((v2) => v2.name === name && v2.group === group);
if (variable) {
return variable;
}
}
}
}
/** /**
* @param {string} stageName * @param {string} stageName
* @returns {string | undefined} * @returns {string | undefined}
@ -6592,6 +6622,18 @@
buffers: descriptor.buffers || [] buffers: descriptor.buffers || []
}; };
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) {
let variableInfo = this._vertex.findVariableInfo(name, group);
if (!variableInfo && this._fragment !== this._vertex) {
variableInfo = this._fragment.findVariableInfo(name, group);
}
return variableInfo;
}
/** /**
* @param {ShaderPairStateDescriptor} descriptor * @param {ShaderPairStateDescriptor} descriptor
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>} * @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}
@ -6836,6 +6878,9 @@
getView() { getView() {
return this.createDefaultView(); return this.createDefaultView();
} }
toBindingResource() {
return this._handle;
}
destroy() { destroy() {
this._handle?.destroy(); this._handle?.destroy();
this._handle = void 0; this._handle = void 0;
@ -6878,6 +6923,9 @@
throw WebGPUObjectError.from(err, _Sampler); throw WebGPUObjectError.from(err, _Sampler);
} }
} }
toBindingResource() {
return this._handle;
}
}; };
// src/resources/material.js // src/resources/material.js
@ -6939,6 +6987,33 @@
const layouts = shaders.createBindGroupLayoutEntries(); const layouts = shaders.createBindGroupLayoutEntries();
return layouts.map((entries) => BindGroupLayout.create(device, { entries })); return layouts.map((entries) => BindGroupLayout.create(device, { entries }));
} }
/**
*
*/
createBindGroup(groupIndex, resources, label) {
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 === a.Uniform || variableInfo.resourceType === a.Storage) {
}
entries.push(entry);
}
entries.sort((a2, b2) => a2.binding - b2.binding);
return BindGroup.create(this._device, {
layout: bgl.handle,
entries,
label
});
}
/** /**
* @typedef MaterialPipelineDescriptor * @typedef MaterialPipelineDescriptor
* @property {string} [label] * @property {string} [label]
@ -7486,9 +7561,9 @@
binding: 0, binding: 0,
resource: uniformBuffer resource: uniformBuffer
}]; }];
const uniformBindGroup = graphicsDevice.createBindGroup( const uniformBindGroup = material.createBindGroup(
material.bindGroupLayouts[0], 0,
uniformBindings, { transform: uniformBuffer },
"Uniforms" "Uniforms"
); );
async function frame() { async function frame() {

View file

@ -98,7 +98,6 @@ async function main() {
{ vertex: shaderModule }, { vertex: shaderModule },
) )
const vertexBufferLayout = { const vertexBufferLayout = {
arrayStride: 3 * 4, arrayStride: 3 * 4,
attributes: [ attributes: [
@ -124,11 +123,16 @@ async function main() {
resource: uniformBuffer resource: uniformBuffer
}] }]
const uniformBindGroup = graphicsDevice.createBindGroup( const uniformBindGroup = material.createBindGroup(
material.bindGroupLayouts[0], 0,
uniformBindings, { transform: uniformBuffer },
'Uniforms' 'Uniforms'
) )
//const uniformBindGroup = graphicsDevice.createBindGroup(
// material.bindGroupLayouts[0],
// uniformBindings,
// 'Uniforms'
//)
async function frame() { async function frame() {
if (!graphicsDevice.isInitialized) { if (!graphicsDevice.isInitialized) {

View file

@ -173,6 +173,19 @@ export class Buffer {
return result return result
} }
/**
* @param {Object} [descriptor={}]
* @param {number} [descriptor.offset=0]
* @param {number} [descriptor.size]
*/
toBindingResource({ offset, size } = {}) {
return {
buffer: this._handle,
offset: offset || 0,
size: size || this.size
}
}
destroy() { destroy() {
this._handle?.destroy() this._handle?.destroy()
this._handle = null this._handle = null

43
src/resources/geometry.js Normal file
View file

@ -0,0 +1,43 @@
import { Buffer } from './buffer.js'
/**
* @typedef GeometryDescriptor
* @param {Buffer} vertices
* @param {number} [vertexCount]
* @param {GPUVertexBufferLayout} layout
* @property {Buffer} [indices]
* @property {number} [indexCount]
* @property {GPUIndexFormat} [format]
*/
export class Geometry {
_device
_vertices
_vertexCount
_vertexBufferLayout
_indices
_indexCount
_format
/**
* @param {GPUDevice} device
* @param {GeometryDescriptor} descriptor
*/
constructor(device, descriptor) {
this._device = device
this._vertices = descriptor.vertices
this._vertexCount = descriptor.vertexCount
this._vertexBufferLayout = descriptor.layout
if (descriptor.indices) {
this._indices = descriptor.indices
this._indexCount = descriptor.indexCount
this._format = descriptor.format
if (this._indexCount == null || this._format == null) {
throw new Error('indexCount or format is required when creating an indexed geometry')
}
}
}
}

View file

@ -1,6 +1,8 @@
import { BindGroupLayout } from './bind-group-layout.js' import { BindGroupLayout } from './bind-group-layout.js'
import { ShaderPair, ShaderModule } from './shader-module.js' import { ShaderPair, ShaderModule } from './shader-module.js'
import { MaterialError, WebGPUObjectError } from '../utils/errors.js' import { MaterialError, WebGPUObjectError } from '../utils/errors.js'
import { ResourceType } from 'wgsl_reflect'
import { BindGroup } from './bind-group.js'
/** @import { FragmentStateDescriptor, VertexStateDescriptor } from './shader-module.js' */ /** @import { FragmentStateDescriptor, VertexStateDescriptor } from './shader-module.js' */
@ -83,6 +85,43 @@ export class Material {
return layouts.map(entries => BindGroupLayout.create(device, { entries })) return layouts.map(entries => BindGroupLayout.create(device, { entries }))
} }
/**
*
*/
createBindGroup(groupIndex, resources, label) {
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
})
}
/** /**
* @typedef MaterialPipelineDescriptor * @typedef MaterialPipelineDescriptor
* @property {string} [label] * @property {string} [label]

View file

@ -42,5 +42,9 @@ export class Sampler {
throw WebGPUObjectError.from(err, Sampler) throw WebGPUObjectError.from(err, Sampler)
} }
} }
toBindingResource() {
return this._handle
}
} }

View file

@ -79,6 +79,7 @@ export class ShaderModule {
*/ */
export class ReflectedShader { export class ReflectedShader {
static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers']
_module _module
get module() { get module() {
@ -93,6 +94,25 @@ export class ReflectedShader {
this._module = shader this._module = shader
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) {
const reflection = this.module.reflect()
for (const type of ReflectedShader._reflectTypes) {
const variables = reflection[type]
if (variables) {
const variable = variables.find(v => v.name === name && v.group === group)
if (variable) {
return variable
}
}
}
}
/** /**
* @param {string} stageName * @param {string} stageName
* @returns {string | undefined} * @returns {string | undefined}
@ -402,6 +422,21 @@ export class ShaderPair {
} }
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) {
let variableInfo = this._vertex.findVariableInfo(name, group)
if (!variableInfo && this._fragment !== this._vertex) {
variableInfo = this._fragment.findVariableInfo(name, group)
}
return variableInfo
}
/** /**
* @param {ShaderPairStateDescriptor} descriptor * @param {ShaderPairStateDescriptor} descriptor
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>} * @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}

View file

@ -100,6 +100,10 @@ export class Texture {
return this.createDefaultView() return this.createDefaultView()
} }
toBindingResource() {
return this._handle
}
destroy() { destroy() {
this._handle?.destroy() this._handle?.destroy()
this._handle = undefined this._handle = undefined

View file

@ -1,3 +1,9 @@
/**
* @template T
* @template {keyof T} K
* @typedef {Pick<Partial<T>, K> & Omit<T, K>} Optional
*/
/** /**
* @template T, U * @template T, U
* @typedef { * @typedef {