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;
}
/**
* @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() {
this._handle?.destroy();
this._handle = null;
@ -6320,7 +6332,8 @@
return this._reflection;
}
};
var ReflectedShader = class {
var ReflectedShader = class _ReflectedShader {
static _reflectTypes = ["uniforms", "storage", "textures", "samplers"];
_module;
get module() {
return this._module;
@ -6331,6 +6344,23 @@
constructor(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
* @returns {string | undefined}
@ -6592,6 +6622,18 @@
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
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}
@ -6836,6 +6878,9 @@
getView() {
return this.createDefaultView();
}
toBindingResource() {
return this._handle;
}
destroy() {
this._handle?.destroy();
this._handle = void 0;
@ -6878,6 +6923,9 @@
throw WebGPUObjectError.from(err, _Sampler);
}
}
toBindingResource() {
return this._handle;
}
};
// src/resources/material.js
@ -6939,6 +6987,33 @@
const layouts = shaders.createBindGroupLayoutEntries();
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
* @property {string} [label]
@ -7486,9 +7561,9 @@
binding: 0,
resource: uniformBuffer
}];
const uniformBindGroup = graphicsDevice.createBindGroup(
material.bindGroupLayouts[0],
uniformBindings,
const uniformBindGroup = material.createBindGroup(
0,
{ transform: uniformBuffer },
"Uniforms"
);
async function frame() {

View file

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

View file

@ -173,6 +173,19 @@ export class Buffer {
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() {
this._handle?.destroy()
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 { ShaderPair, ShaderModule } from './shader-module.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' */
@ -83,6 +85,43 @@ export class Material {
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
* @property {string} [label]

View file

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

View file

@ -79,6 +79,7 @@ export class ShaderModule {
*/
export class ReflectedShader {
static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers']
_module
get module() {
@ -93,6 +94,25 @@ export class ReflectedShader {
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
* @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
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}

View file

@ -100,6 +100,10 @@ export class Texture {
return this.createDefaultView()
}
toBindingResource() {
return this._handle
}
destroy() {
this._handle?.destroy()
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
* @typedef {