custom build script; ambient wgsl declaration

This commit is contained in:
Rowan 2025-04-22 02:03:15 -05:00
parent 95b25c962a
commit 81f2d118a0
9 changed files with 227 additions and 358 deletions

11
build.js Normal file
View file

@ -0,0 +1,11 @@
import { build } from 'esbuild'
import { wgsl } from 'esbuild-plugin-wgsl'
// @ts-ignore
await build({
entryPoints: ['index.js'],
bundle: true,
outfile: './dist/index.js',
plugins: [wgsl({ filterWith: true, filterExtension: false })]
})

548
dist/index.js vendored
View file

@ -59,16 +59,8 @@
} }
}; };
// src/core/swap-chain.js // src/core/swap-chain.ts
var SwapChain = class { var SwapChain = class {
_canvas;
_device;
_context;
_format;
_width;
_height;
/** @type {SwapChainConfiguration} */
_configuration;
get context() { get context() {
return this._context; return this._context;
} }
@ -81,14 +73,6 @@
get height() { get height() {
return this._height; return this._height;
} }
/**
* @param {HTMLCanvasElement} canvas
* @param {GPUDevice} device
* @param {GPUCanvasContext} [context]
*
* @throws {WebGPUError}
* Throws an error if unable to request a WebGPU context
*/
constructor(canvas, device, context) { constructor(canvas, device, context) {
this._canvas = canvas; this._canvas = canvas;
this._device = device; this._device = device;
@ -100,9 +84,6 @@
this._width = canvas.width; this._width = canvas.width;
this._height = canvas.height; this._height = canvas.height;
} }
/**
* @param {SwapChainConfiguration} [configuration]
*/
configure(configuration) { configure(configuration) {
if (configuration) { if (configuration) {
this._configuration = configuration; this._configuration = configuration;
@ -119,11 +100,6 @@
getCurrentTextureView() { getCurrentTextureView() {
return this._context.getCurrentTexture().createView(); return this._context.getCurrentTexture().createView();
} }
/**
* @template {number} const T
* @param {PositiveInteger<T>} width
* @param {PositiveInteger<T>} height
*/
resize(width, height) { resize(width, height) {
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return; return;
@ -195,13 +171,13 @@
ExternalTexture: 3 ExternalTexture: 3
}); });
// src/resources/buffer.js // src/resources/buffer.ts
var Buffer = class _Buffer { var Buffer = class _Buffer {
_device; constructor(device, texture) {
_handle; this._mapped = false;
_mapped = false; this._device = device;
/** @type {GPUBuffer} */ this._handle = texture;
_defaultStagingBuffer; }
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -214,18 +190,6 @@
get resourceType() { get resourceType() {
return ResourceType.Buffer; return ResourceType.Buffer;
} }
/**
* @param {GPUDevice} device
* @param {GPUBuffer} texture
*/
constructor(device, texture) {
this._device = device;
this._handle = texture;
}
/**
* @param {GPUDevice} device
* @param {GPUBufferDescriptor} descriptor
*/
static create(device, descriptor) { static create(device, descriptor) {
try { try {
return new _Buffer( return new _Buffer(
@ -236,26 +200,15 @@
throw BufferError.from(err); throw BufferError.from(err);
} }
} }
/**
* @param {number} [size]
*/
_createStagingOptions(size = this.size) { _createStagingOptions(size = this.size) {
return { return {
size, size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
}; };
} }
/**
* @param {number} [size]
*/
_getStagingBuffer(size) { _getStagingBuffer(size) {
return this._device.createBuffer(this._createStagingOptions(size)); return this._device.createBuffer(this._createStagingOptions(size));
} }
/**
* @param {ArrayBufferView | ArrayBuffer} data
* @param {number} [offset=0]
* @param {number} [dataOffset=0]
*/
write(data, offset = 0, dataOffset = 0) { write(data, offset = 0, dataOffset = 0) {
if (!(this.usage & GPUBufferUsage.COPY_DST)) { if (!(this.usage & GPUBufferUsage.COPY_DST)) {
console.warn("Buffer usage does not include COPY_DST. Buffer.write may fail."); console.warn("Buffer usage does not include COPY_DST. Buffer.write may fail.");
@ -270,15 +223,6 @@
dataOffset dataOffset
); );
} }
/**
* @typedef {Exclude<TypedArray, BigInt64Array | BigUint64Array>} SmallTypedArray
* @typedef {Exclude<TypedArrayConstructor, BigInt64ArrayConstructor | BigUint64ArrayConstructor>} SmallTypedArrayConstructor
*/
/**
* @param {SmallTypedArray | DataView | undefined} [out]
* @param {number} [byteOffset=0]
* @param {number} [byteSize]
*/
async read(out, byteOffset = 0, byteSize = -1) { async read(out, byteOffset = 0, byteSize = -1) {
if (!this._device) { if (!this._device) {
throw WebGPUError.deviceUnavailable(); throw WebGPUError.deviceUnavailable();
@ -315,10 +259,7 @@
await this.handle.mapAsync(GPUMapMode.READ, byteOffset, byteSize); await this.handle.mapAsync(GPUMapMode.READ, byteOffset, byteSize);
range = this.handle.getMappedRange(byteOffset, byteSize); range = this.handle.getMappedRange(byteOffset, byteSize);
if (out != null) { if (out != null) {
const SourceView = ( const SourceView = out.constructor;
/** @type {SmallTypedArrayConstructor} */
out.constructor
);
const bytesPerElement = SourceView.BYTES_PER_ELEMENT; const bytesPerElement = SourceView.BYTES_PER_ELEMENT;
if (!bytesPerElement) { if (!bytesPerElement) {
if (out instanceof DataView) { if (out instanceof DataView) {
@ -353,11 +294,6 @@
} }
return result; return result;
} }
/**
* @param {Object} [descriptor={}]
* @param {number} [descriptor.offset=0]
* @param {number} [descriptor.size]
*/
toBindingResource({ offset, size } = {}) { toBindingResource({ offset, size } = {}) {
return { return {
buffer: this._handle, buffer: this._handle,
@ -371,17 +307,9 @@
} }
}; };
var UniformBuffer = class extends Buffer { var UniformBuffer = class extends Buffer {
/**
* @param {GPUDevice} device
* @param {GPUBuffer} buffer
*/
constructor(device, buffer) { constructor(device, buffer) {
super(device, buffer); super(device, buffer);
} }
/**
* @param {GPUDevice} device
* @param {Omit<GPUBufferDescriptor, 'usage'>} descriptor
*/
static create(device, descriptor) { static create(device, descriptor) {
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
return super.create(device, { return super.create(device, {
@ -5695,7 +5623,7 @@
var GroupBindingMap = class extends Map { var GroupBindingMap = class extends Map {
}; };
// src/enum.js // src/enum.ts
var AddressMode = Enum( var AddressMode = Enum(
"clamp-to-edge", "clamp-to-edge",
"repeat", "repeat",
@ -6244,22 +6172,14 @@
} }
}; };
// src/resources/shader-module.js // src/resources/shader-module.ts
var ShaderModule = class _ShaderModule { var ShaderModule = class _ShaderModule {
_handle;
_code;
/** @type {WgslReflect | undefined} */
_reflection;
get handle() { get handle() {
return this._handle; return this._handle;
} }
get label() { get label() {
return this._handle.label; return this._handle.label;
} }
/**
* @param {GPUDevice} device
* @param {GPUShaderModuleDescriptor} descriptor
*/
constructor(device, descriptor) { constructor(device, descriptor) {
this._code = descriptor.code; this._code = descriptor.code;
try { try {
@ -6268,10 +6188,6 @@
throw WebGPUObjectError.from(err, _ShaderModule); throw WebGPUObjectError.from(err, _ShaderModule);
} }
} }
/**
* @param {GPUDevice} device
* @param {GPUShaderModuleDescriptor} descriptor
*/
static create(device, descriptor) { static create(device, descriptor) {
return new _ShaderModule(device, descriptor); return new _ShaderModule(device, descriptor);
} }
@ -6284,22 +6200,15 @@
} }
}; };
var ReflectedShader = class _ReflectedShader { var ReflectedShader = class _ReflectedShader {
static _reflectTypes = ["uniforms", "storage", "textures", "samplers"]; static {
_module; this._reflectTypes = ["uniforms", "storage", "textures", "samplers"];
}
get module() { get module() {
return this._module; return this._module;
} }
/**
* @param {ShaderModule} shader
*/
constructor(shader) { constructor(shader) {
this._module = shader; this._module = shader;
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) { findVariableInfo(name, group) {
const reflection = this.module.reflect(); const reflection = this.module.reflect();
for (const type of _ReflectedShader._reflectTypes) { for (const type of _ReflectedShader._reflectTypes) {
@ -6312,17 +6221,10 @@
} }
} }
} }
/**
* @param {string} stageName
* @returns {string | undefined}
*/
getEntrypoint(stageName) { getEntrypoint(stageName) {
const entry = this.module.reflect().entry; const entry = this.module.reflect().entry;
return entry[stageName].length === 1 ? entry[stageName][0].name : void 0; return entry[stageName].length === 1 ? entry[stageName][0].name : void 0;
} }
/**
* @returns {GPUShaderStageFlags}
*/
getShaderStages() { getShaderStages() {
const entry = this._module.reflect().entry; const entry = this._module.reflect().entry;
let stages = 0; let stages = 0;
@ -6331,16 +6233,9 @@
stages |= entry.compute.length > 0 ? GPUShaderStage.COMPUTE : 0; stages |= entry.compute.length > 0 ? GPUShaderStage.COMPUTE : 0;
return stages; return stages;
} }
/**
* @param {GPUShaderStageFlags} stages
*/
hasStage(stages) { hasStage(stages) {
return this.getShaderStages() & stages; return this.getShaderStages() & stages;
} }
/**
* @param {GPUShaderStageFlags} stages
* @param {GroupBindingMap} [out=new GroupBindingMap()]
*/
getBindingsForStage(stages, out = new GroupBindingMap()) { getBindingsForStage(stages, out = new GroupBindingMap()) {
const groups = this._module.reflect().getBindGroups(); const groups = this._module.reflect().getBindGroups();
groups.forEach((bindings, groupIndex) => { groups.forEach((bindings, groupIndex) => {
@ -6358,17 +6253,9 @@
}); });
return out; return out;
} }
/**
* @param {Map<any, any>} map
* @returns {number[]}
*/
static _sortKeyIndices(map) { static _sortKeyIndices(map) {
return Array.from(map.keys()).sort((a2, b2) => a2 - b2); return Array.from(map.keys()).sort((a2, b2) => a2 - b2);
} }
/**
* @param {VariableInfo} _variableInfo
* @returns {GPUBufferBindingLayout}
*/
static _parseUniform(_variableInfo) { static _parseUniform(_variableInfo) {
return { return {
type: BufferBindingType.Uniform, type: BufferBindingType.Uniform,
@ -6377,14 +6264,9 @@
minBindingSize: 0 minBindingSize: 0
}; };
} }
/**
* @param {VariableInfo} variableInfo
* @returns {GPUBufferBindingLayout}
*/
static _parseStorage(variableInfo) { static _parseStorage(variableInfo) {
return { return {
type: accessToBufferType( type: accessToBufferType(
/** @type {WGSLAccess} */
variableInfo.access variableInfo.access
), ),
// TODO: infer these two properties // TODO: infer these two properties
@ -6392,10 +6274,6 @@
minBindingSize: 0 minBindingSize: 0
}; };
} }
/**
* @param {VariableInfo} variableInfo
* @returns {GPUTextureBindingLayout}
*/
static _parseTexture(variableInfo) { static _parseTexture(variableInfo) {
const [type, sampledType] = parseTextureType( const [type, sampledType] = parseTextureType(
variableInfo.type.name variableInfo.type.name
@ -6406,37 +6284,23 @@
multisampled: type.includes("multisampled") multisampled: type.includes("multisampled")
}; };
} }
/**
* @param {VariableInfo} variableInfo
* @returns {GPUSamplerBindingLayout}
*/
static _parseSampler(variableInfo) { static _parseSampler(variableInfo) {
return { return {
type: typeToSamplerBindingType( type: typeToSamplerBindingType(
/** @type {WGSLSamplerType} */
variableInfo.type.name variableInfo.type.name
) )
}; };
} }
/**
* @param {VariableInfo} variableInfo
* @returns {GPUStorageTextureBindingLayout}
*/
static _parseStorageTexture(variableInfo) { static _parseStorageTexture(variableInfo) {
const [type] = parseTextureType(variableInfo.type.name); const [type] = parseTextureType(variableInfo.type.name);
return { return {
access: accessToStorageTextureAccess( access: accessToStorageTextureAccess(
/** @type {WGSLAccess} */
variableInfo.access variableInfo.access
), ),
format: wgslToWgpuFormat(variableInfo.type.name), format: wgslToWgpuFormat(variableInfo.type.name),
viewDimension: typeToViewDimension(type) viewDimension: typeToViewDimension(type)
}; };
} }
/**
* @param {VariableStageInfo} variableStageInfo
* @returns {GPUBindGroupLayoutEntry}
*/
static _variableInfoToEntry(variableStageInfo) { static _variableInfoToEntry(variableStageInfo) {
const { stages: visibility, variableInfo } = variableStageInfo; const { stages: visibility, variableInfo } = variableStageInfo;
switch (variableInfo.resourceType) { switch (variableInfo.resourceType) {
@ -6475,9 +6339,6 @@
return; return;
} }
} }
/**
* @param {GroupBindingMap} groupBindings
*/
static createBindGroupLayoutEntries(groupBindings) { static createBindGroupLayoutEntries(groupBindings) {
const sortedGroupIndices = this._sortKeyIndices(groupBindings); const sortedGroupIndices = this._sortKeyIndices(groupBindings);
return sortedGroupIndices.map((groupIndex) => { return sortedGroupIndices.map((groupIndex) => {
@ -6487,14 +6348,6 @@
} }
}; };
var ShaderPair = class _ShaderPair { var ShaderPair = class _ShaderPair {
/** @type {ReflectedShader} */
_vertex;
/** @type {ReflectedShader} */
_fragment;
/**
* @param {ReflectedShader} vertex
* @param {ReflectedShader} [fragment]
*/
constructor(vertex, fragment) { constructor(vertex, fragment) {
if (!vertex) { if (!vertex) {
throw new Error("Missing vertex shader"); throw new Error("Missing vertex shader");
@ -6515,18 +6368,11 @@
throw new Error("Missing fragment shader."); throw new Error("Missing fragment shader.");
} }
} }
/** @param {ShaderModule} shader */
static fromUnifiedShader(shader) { static fromUnifiedShader(shader) {
return new _ShaderPair( return new _ShaderPair(
new ReflectedShader(shader) new ReflectedShader(shader)
); );
} }
/**
* @param {{
vertex: ShaderModule,
fragment?: ShaderModule
* }} value
*/
static fromPair(value) { static fromPair(value) {
const vert = new ReflectedShader(value.vertex); const vert = new ReflectedShader(value.vertex);
const frag = value.fragment && new ReflectedShader(value.fragment); const frag = value.fragment && new ReflectedShader(value.fragment);
@ -6549,10 +6395,6 @@
this._createGroupBindings() this._createGroupBindings()
); );
} }
/**
* @param {FragmentStateDescriptor} descriptor
* @returns {GPUFragmentState}
*/
_getFragmentState(descriptor) { _getFragmentState(descriptor) {
return { return {
module: this._fragment.module.handle, module: this._fragment.module.handle,
@ -6561,10 +6403,6 @@
targets: descriptor.targets || [] targets: descriptor.targets || []
}; };
} }
/**
* @param {VertexStateDescriptor} descriptor
* @returns {GPUVertexState}
*/
_getVertexState(descriptor) { _getVertexState(descriptor) {
return { return {
module: this._vertex.module.handle, module: this._vertex.module.handle,
@ -6573,11 +6411,6 @@
buffers: descriptor.buffers || [] buffers: descriptor.buffers || []
}; };
} }
/**
* @param {string} name
* @param {number} group
* @returns {VariableInfo | undefined}
*/
findVariableInfo(name, group) { findVariableInfo(name, group) {
let variableInfo = this._vertex.findVariableInfo(name, group); let variableInfo = this._vertex.findVariableInfo(name, group);
if (!variableInfo && this._fragment !== this._vertex) { if (!variableInfo && this._fragment !== this._vertex) {
@ -6585,10 +6418,6 @@
} }
return variableInfo; return variableInfo;
} }
/**
* @param {ShaderPairStateDescriptor} descriptor
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}
*/
getRenderPipelineStates(descriptor) { getRenderPipelineStates(descriptor) {
return { return {
fragment: this._getFragmentState(descriptor.fragment), fragment: this._getFragmentState(descriptor.fragment),
@ -6597,10 +6426,8 @@
} }
}; };
// src/rendering/render-pipeline.js // src/rendering/render-pipeline.ts
var RenderPipeline = class { var RenderPipeline = class {
_handle;
_label;
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -6617,29 +6444,19 @@
} }
}; };
// src/core/command-recorder.js // src/core/command-recorder.ts
var CommandRecorder = class _CommandRecorder { var CommandRecorder = class _CommandRecorder {
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 }; static {
_device; this._defaultClearValue = { r: 0, g: 0, b: 0, a: 1 };
_swapChain; }
_label; get label() {
_encoder; return this._encoder.label;
/** @type {GPURenderPassEncoder | undefined} */ }
_passEncoder;
/**
* @param {GPUDevice} device
* @param {SwapChain} swapChain
* @param {string} [label]
*/
constructor(device, swapChain, label) { constructor(device, swapChain, label) {
this._device = device; this._device = device;
this._swapChain = swapChain; this._swapChain = swapChain;
this._label = label;
this._encoder = device.createCommandEncoder({ label }); this._encoder = device.createCommandEncoder({ label });
} }
/**
* @returns {[GPURenderPassColorAttachment]}
*/
_defaultColorAttachment() { _defaultColorAttachment() {
const view = this._swapChain.getCurrentTextureView(); const view = this._swapChain.getCurrentTextureView();
return [{ return [{
@ -6649,18 +6466,13 @@
storeOp: StoreOp.Store storeOp: StoreOp.Store
}]; }];
} }
/**
* @param {GPURenderPassColorAttachment[]} [colorAttachments]
* @param {GPURenderPassDepthStencilAttachment} [depthStencilAttachment]
* @returns {GPURenderPassEncoder}
*/
beginRenderPass(colorAttachments, depthStencilAttachment) { beginRenderPass(colorAttachments, depthStencilAttachment) {
if (this._passEncoder) { if (this._passEncoder) {
throw CommandRecorderError.activeRenderPass(); throw CommandRecorderError.activeRenderPass();
} }
const attachments = colorAttachments || this._defaultColorAttachment(); const attachments = colorAttachments || this._defaultColorAttachment();
const descriptor = { const descriptor = {
label: this._label || "RenderPass", label: this.label || "RenderPass",
colorAttachments: attachments, colorAttachments: attachments,
depthStencilAttachment depthStencilAttachment
}; };
@ -6682,10 +6494,8 @@
} }
}; };
// src/resources/bind-group-layout.js // src/resources/bind-group-layout.ts
var BindGroupLayout = class _BindGroupLayout { var BindGroupLayout = class _BindGroupLayout {
_device;
_handle;
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -6716,10 +6526,8 @@
} }
}; };
// src/resources/bind-group.js // src/resources/bind-group.ts
var BindGroup = class _BindGroup { var BindGroup = class _BindGroup {
_device;
_handle;
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -6772,13 +6580,11 @@
} }
}; };
// src/resources/texture.js // src/resources/texture.ts
var Texture = class _Texture { var Texture = class _Texture {
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT; static {
_device; this._defaultUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
_handle; }
/** @type {GPUTextureView | undefined} */
_defaultView;
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -6951,10 +6757,8 @@
} }
}; };
// src/resources/sampler.js // src/resources/sampler.ts
var Sampler = class _Sampler { var Sampler = class _Sampler {
_device;
_handle;
get handle() { get handle() {
return this._handle; return this._handle;
} }
@ -6991,12 +6795,8 @@
} }
}; };
// src/resources/material.js // src/resources/material.ts
var Material = class _Material { var Material = class _Material {
_device;
_shaders;
_bindGroupLayouts;
_pipelineLayout;
get shaders() { get shaders() {
return this._shaders; return this._shaders;
} }
@ -7101,81 +6901,52 @@
} }
}; };
// src/core/graphics-device.js // src/core/graphics-device.ts
var GraphicsDeviceBuilder = class { var GraphicsDeviceBuilder = class {
_canvas;
get canvas() { get canvas() {
return this._canvas; return this._canvas;
} }
/** @type {GPURequestAdapterOptions} */
_adapter_options;
/** @type {GPUDeviceDescriptor} */
_device_descriptor;
/**
* @param {HTMLCanvasElement} [canvasElement]
*/
constructor(canvasElement) { constructor(canvasElement) {
this._canvas = canvasElement; this._canvas = canvasElement;
} }
isSupported() { isSupported() {
return navigator.gpu; return navigator.gpu;
} }
/**
* @param {HTMLCanvasElement} canvasElement
*/
withCanvas(canvasElement) { withCanvas(canvasElement) {
this._canvas = canvasElement; this._canvas = canvasElement;
return this; return this;
} }
/**
* @param {GPURequestAdapterOptions} [options]
*/
withAdapter(options2) { withAdapter(options2) {
if (!this.isSupported()) { if (!this.isSupported()) {
throw WebGPUError.unsupported(); throw WebGPUError.unsupported();
} }
this._adapter_options = options2; this._adapterOptions = options2;
return this; return this;
} }
/**
* @param {GPUDeviceDescriptor} [options]
*/
withDevice(options2) { withDevice(options2) {
if (!this.isSupported()) { if (!this.isSupported()) {
throw WebGPUError.unsupported(); throw WebGPUError.unsupported();
} }
this._device_descriptor = options2; this._deviceDescriptor = options2;
return this; return this;
} }
async build() { async build() {
return new GraphicsDevice( return new GraphicsDevice(
this._canvas, this._canvas,
new DeviceHandler( new DeviceHandler(
this._adapter_options, this._adapterOptions,
this._device_descriptor this._deviceDescriptor
) )
); );
} }
}; };
var DeviceHandler = class { var DeviceHandler = class {
/** @type {GPURequestAdapterOptions} */
_adapterOptions;
/** @type {GPUAdapter} */
_adapter;
get adapter() { get adapter() {
return this._adapter; return this._adapter;
} }
/** @type {GPUDeviceDescriptor} */
_deviceDescriptor;
/** @type {GPUDevice} */
_device;
get device() { get device() {
return this._device; return this._device;
} }
/**
* @param {GPURequestAdapterOptions} adapterOptions
* @param {GPUDeviceDescriptor} deviceDescriptor
*/
constructor(adapterOptions, deviceDescriptor) { constructor(adapterOptions, deviceDescriptor) {
this._adapterOptions = adapterOptions; this._adapterOptions = adapterOptions;
this._deviceDescriptor = deviceDescriptor; this._deviceDescriptor = deviceDescriptor;
@ -7192,13 +6963,12 @@
} }
}; };
var GraphicsDevice = class extends EventEmitter { var GraphicsDevice = class extends EventEmitter {
_canvas; constructor(canvas, deviceHandler) {
_deviceHandler; super();
/** @type {SwapChain} */ this._isInitialized = false;
_swapChain; this._canvas = canvas;
/** @type {GPUQueue} */ this._deviceHandler = deviceHandler;
_queue; }
_isInitialized = false;
get isInitialized() { get isInitialized() {
return this._isInitialized; return this._isInitialized;
} }
@ -7214,18 +6984,6 @@
get swapChain() { get swapChain() {
return this._swapChain; return this._swapChain;
} }
/**
* @param {HTMLCanvasElement} canvas
* @param {DeviceHandler} deviceHandler
*/
constructor(canvas, deviceHandler) {
super();
this._canvas = canvas;
this._deviceHandler = deviceHandler;
}
/**
* @param {HTMLCanvasElement} [canvas]
*/
static build(canvas) { static build(canvas) {
return new GraphicsDeviceBuilder(canvas); return new GraphicsDeviceBuilder(canvas);
} }
@ -7251,17 +7009,6 @@
); );
return true; return true;
} }
/**
* @typedef {Omit<GPUBufferDescriptor, 'mappedAtCreation'>} BufferDescriptor
*/
/**
* Create a GPU buffer
* @param {BufferDescriptor} descriptor
* @param {ArrayBufferView | ArrayBuffer} [data]
* @returns {Buffer}
*
* @throws {GPUBufferError}
*/
createBuffer(descriptor, data) { createBuffer(descriptor, data) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7276,11 +7023,6 @@
throw BufferError.from(err); throw BufferError.from(err);
} }
} }
/**
* @param {number} size
* @param {ArrayBufferView | ArrayBuffer} [data]
* @param {string} [label]
*/
createUniformBuffer(size, data, label) { createUniformBuffer(size, data, label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7294,12 +7036,6 @@
} }
return buffer; return buffer;
} }
/**
* Creates a shader module from WGSL code.
* @param {string} code
* @param {string} [label]
* @returns {ShaderModule}
*/
createShaderModule(code, label) { createShaderModule(code, label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7310,11 +7046,6 @@
throw WebGPUObjectError.from(err, ShaderModule); throw WebGPUObjectError.from(err, ShaderModule);
} }
} }
/**
* Creates a render pipeline.
* @param {GPURenderPipelineDescriptor} descriptor - Raw render pipeline descriptor.
* @returns {RenderPipeline}
*/
createRenderPipeline(descriptor) { createRenderPipeline(descriptor) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7326,9 +7057,6 @@
throw WebGPUObjectError.from(err, RenderPipeline); throw WebGPUObjectError.from(err, RenderPipeline);
} }
} }
/**
* @param {import('../resources/material.js').ShaderPairDescriptor} shaders
*/
createMaterial(shaders) { createMaterial(shaders) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7339,21 +7067,12 @@
throw WebGPUObjectError.from(err, Material); throw WebGPUObjectError.from(err, Material);
} }
} }
/**
* Creates a CommandRecorder to begin recording GPU commands.
* @param {string} [label]
* @returns {CommandRecorder}
*/
createCommandRecorder(label) { createCommandRecorder(label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
} }
return new CommandRecorder(this.device, this._swapChain, label); return new CommandRecorder(this.device, this._swapChain, label);
} }
/**
* @param {GPUBindGroupLayoutEntry[]} entries
* @param {string} [label]
*/
createBindGroupLayout(entries, label) { createBindGroupLayout(entries, label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7363,10 +7082,6 @@
entries entries
}); });
} }
/**
* @param {BindGroupEntry} binding
* @returns {GPUBindingResource}
*/
_getBindingResource(binding) { _getBindingResource(binding) {
const resource = binding.resource; const resource = binding.resource;
switch (resource.resourceType) { switch (resource.resourceType) {
@ -7388,11 +7103,6 @@
}; };
} }
} }
/**
* @param {BindGroupLayout} layout
* @param {BindGroupEntry[]} bindings
* @param {string} [label]
*/
createBindGroup(layout, bindings, label) { createBindGroup(layout, bindings, label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7407,10 +7117,6 @@
label label
}); });
} }
/**
* @param {Array<BindGroupLayout>} layouts
* @param {string} [label]
*/
createPipelineLayout(layouts, label) { createPipelineLayout(layouts, label) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7421,18 +7127,12 @@
bindGroupLayouts bindGroupLayouts
}); });
} }
/**
* @param {GPUSamplerDescriptor} [descriptor]
*/
createSampler(descriptor) { createSampler(descriptor) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
} }
return Sampler.create(this.device, descriptor); return Sampler.create(this.device, descriptor);
} }
/**
* @param {GPUTextureDescriptor} descriptor
*/
createTexture(descriptor) { createTexture(descriptor) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7443,15 +7143,6 @@
throw WebGPUObjectError.from(err, Texture); throw WebGPUObjectError.from(err, Texture);
} }
} }
/**
* @param {ImageBitmap} bitmap
* @param {object} [options]
* @param {string} [options.label]
* @param {GPUTextureFormat} [options.format='rgba8unorm']
* @param {GPUTextureUsageFlags} [options.usage=TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT]
* @param {boolean} [options.generateMipmaps=false]
* @param {boolean} [options.flipY=false]
*/
createTextureFromBitmap(bitmap, options2) { createTextureFromBitmap(bitmap, options2) {
if (!this._isInitialized) { if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized(); throw GraphicsDeviceError.uninitialized();
@ -7487,10 +7178,6 @@
throw WebGPUObjectError.from(err, Texture); throw WebGPUObjectError.from(err, Texture);
} }
} }
/**
* Submits an array of command buffers to the GPU queue.
* @param {GPUCommandBuffer[]} commandBuffers
*/
submitCommands(commandBuffers) { submitCommands(commandBuffers) {
if (!this._isInitialized || !commandBuffers || commandBuffers.length === 0) return; if (!this._isInitialized || !commandBuffers || commandBuffers.length === 0) return;
this.queue.submit(commandBuffers); this.queue.submit(commandBuffers);
@ -7509,6 +7196,158 @@
} }
}; };
// src/utils/mip-shader.wgsl
var mip_shader_default = "var<private> pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0), vec2<f32>(-1.0, 3.0), vec2<f32>(3.0, -1.0));\n\nstruct VertexOutput {\n @builtin(position) position : vec4<f32>,\n @location(0) texCoord : vec2<f32>,\n};\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n var output : VertexOutput;\n output.texCoord = pos[vertexIndex] * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);\n output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);\n return output;\n}\n\n@group(0) @binding(0) var imgSampler : sampler;\n@group(0) @binding(1) var img : texture_2d<f32>;\n\n@fragment\nfn fragmentMain(@location(0) texCoord : vec2<f32>) -> @location(0) vec4<f32> {\n return textureSample(img, imgSampler, texCoord);\n}\n";
// src/utils/mip-generator.ts
var mip = (n2) => Math.max(1, n2 >>> 1);
var MipGenerator = class {
constructor(device) {
this._device = device;
this._sampler = device.createSampler({ minFilter: "linear" });
this._pipelines = {};
}
_getShader() {
if (!this._shader) {
this._shader = this._device.createShaderModule({
code: mip_shader_default
});
}
return this._shader;
}
_getBindGroupLayout() {
if (!this._bindGroupLayout) {
this._bindGroupLayout = this._device.createBindGroupLayout({
entries: [{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}, {
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {}
}]
});
}
return this._bindGroupLayout;
}
_getPipelineLayout() {
if (!this._pipelineLayout) {
this._pipelineLayout = this._device.createPipelineLayout({
label: "Mipmap Generator",
bindGroupLayouts: [this._getBindGroupLayout()]
});
}
return this._pipelineLayout;
}
getPipeline(format) {
let pipeline = this._pipelines[format];
if (!pipeline) {
const shader = this._getShader();
pipeline = this._device.createRenderPipeline({
layout: this._getPipelineLayout(),
vertex: {
module: shader,
entryPoint: "vs_main"
},
fragment: {
module: shader,
entryPoint: "fs_main",
targets: [{ format }]
}
});
this._pipelines[format] = pipeline;
}
return pipeline;
}
generateMipmap(texture, descriptor) {
const pipeline = this.getPipeline(descriptor.format);
if (descriptor.dimension !== "2d") {
throw new Error("Generating mipmaps for anything except 2d is unsupported.");
}
let mipTexture = texture;
const { width, height, depthOrArrayLayers } = descriptor.size;
const renderToSource = descriptor.usage & GPUTextureUsage.RENDER_ATTACHMENT;
if (!renderToSource) {
mipTexture = this._device.createTexture({
size: {
width: mip(width),
height: mip(height),
depthOrArrayLayers
},
format: descriptor.format,
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
mipLevelCount: descriptor.mipLevelCount - 1
});
}
const encoder = this._device.createCommandEncoder({});
for (let layer = 0; layer < depthOrArrayLayers; ++layer) {
let srcView = texture.createView({
baseMipLevel: 0,
mipLevelCount: 1,
dimension: "2d",
baseArrayLayer: layer,
arrayLayerCount: 1
});
let dstMipLevel = renderToSource ? 1 : 0;
for (let i2 = 1; i2 < descriptor.mipLevelCount; ++i2) {
const dstView = mipTexture.createView({
baseMipLevel: dstMipLevel++,
mipLevelCount: 1,
dimension: "2d",
baseArrayLayer: layer,
arrayLayerCount: 1
});
const passEncoder = encoder.beginRenderPass({
colorAttachments: [{
view: dstView,
loadOp: "clear",
storeOp: "store"
}]
});
const bindGroup = this._device.createBindGroup({
layout: this._bindGroupLayout,
entries: [{
binding: 0,
resource: this._sampler
}, {
binding: 1,
resource: srcView
}]
});
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(3, 1, 0, 0);
passEncoder.end();
srcView = dstView;
}
}
if (!renderToSource) {
const mipLevelSize = {
width: mip(width),
height: mip(height),
depthOrArrayLayers
};
for (let i2 = 1; i2 < descriptor.mipLevelCount; ++i2) {
encoder.copyTextureToTexture({
texture: mipTexture,
mipLevel: i2 - 1
}, {
texture,
mipLevel: i2
}, mipLevelSize);
mipLevelSize.width = mip(mipLevelSize.width);
mipLevelSize.height = mip(mipLevelSize.height);
}
}
this._device.queue.submit([encoder.finish()]);
if (!renderToSource) {
mipTexture.destroy();
}
return texture;
}
};
// index.js // index.js
async function main() { async function main() {
const canvas = ( const canvas = (
@ -7522,6 +7361,7 @@
canvas.width = 800; canvas.width = 800;
canvas.height = 600; canvas.height = 600;
const graphicsDevice = await GraphicsDevice.build().withCanvas(canvas).withAdapter({ powerPreference: PowerPreference.HighPerformance }).build(); const graphicsDevice = await GraphicsDevice.build().withCanvas(canvas).withAdapter({ powerPreference: PowerPreference.HighPerformance }).build();
const mipGenerator = new MipGenerator(graphicsDevice.device);
const success = await graphicsDevice.initialize(); const success = await graphicsDevice.initialize();
if (!success) { if (!success) {
console.error("Failed to initialize WebGPU."); console.error("Failed to initialize WebGPU.");

View file

@ -1,5 +1,6 @@
import { GraphicsDevice } from './src/core/graphics-device.js' import { GraphicsDevice } from './src/core/graphics-device.js'
import { PowerPreference, VertexFormat } from './src/enum.js' import { PowerPreference, VertexFormat } from './src/enum.js'
import { MipGenerator } from './src/utils/mip-generator.js'
async function main() { async function main() {
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('webgpu-canvas')) const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('webgpu-canvas'))
@ -17,6 +18,7 @@ async function main() {
.withCanvas(canvas) .withCanvas(canvas)
.withAdapter({ powerPreference: PowerPreference.HighPerformance }) .withAdapter({ powerPreference: PowerPreference.HighPerformance })
.build() .build()
const mipGenerator = new MipGenerator(graphicsDevice.device)
const success = await graphicsDevice.initialize() const success = await graphicsDevice.initialize()

10
package-lock.json generated
View file

@ -14,6 +14,7 @@
"devDependencies": { "devDependencies": {
"@webgpu/types": "^0.1.60", "@webgpu/types": "^0.1.60",
"esbuild": "^0.25.2", "esbuild": "^0.25.2",
"esbuild-plugin-wgsl": "git+https://git.kitsu.cafe/rowan/esbuild-plugin-wgsl.git",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
}, },
@ -490,6 +491,15 @@
"@esbuild/win32-x64": "0.25.2" "@esbuild/win32-x64": "0.25.2"
} }
}, },
"node_modules/esbuild-plugin-wgsl": {
"version": "1.0.0",
"resolved": "git+https://git.kitsu.cafe/rowan/esbuild-plugin-wgsl.git#e1ab30a11972e13b2ee29ddb1149e143ea686dae",
"dev": true,
"license": "ISC",
"peerDependencies": {
"esbuild": "0.x.x"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",

View file

@ -4,7 +4,7 @@
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "esbuild index.js --bundle --outfile=./dist/index.js", "build": "node build.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [], "keywords": [],
@ -14,6 +14,7 @@
"devDependencies": { "devDependencies": {
"@webgpu/types": "^0.1.60", "@webgpu/types": "^0.1.60",
"esbuild": "^0.25.2", "esbuild": "^0.25.2",
"esbuild-plugin-wgsl": "git+https://git.kitsu.cafe/rowan/esbuild-plugin-wgsl.git",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {

View file

@ -49,7 +49,7 @@ class GraphicsDeviceBuilder {
} }
constructor(canvasElement: HTMLCanvasElement) { constructor(canvasElement?: HTMLCanvasElement) {
this._canvas = canvasElement this._canvas = canvasElement
} }

5
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
declare module '*.wgsl' {
const value: string
export default value
}

View file

@ -1,4 +1,4 @@
import code from './mip-shader.wgsl' with { type: 'text' } import code from './mip-shader.wgsl'
const mip = (n: number) => Math.max(1, n >>> 1) const mip = (n: number) => Math.max(1, n >>> 1)

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "esnext", "module": "es2022",
"moduleResolution": "node", "moduleResolution": "node",
"target": "es6", "target": "es6",
"lib": ["es2022", "dom"], "lib": ["es2022", "dom"],