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 {
_canvas;
_device;
_context;
_format;
_width;
_height;
/** @type {SwapChainConfiguration} */
_configuration;
get context() {
return this._context;
}
@ -81,14 +73,6 @@
get 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) {
this._canvas = canvas;
this._device = device;
@ -100,9 +84,6 @@
this._width = canvas.width;
this._height = canvas.height;
}
/**
* @param {SwapChainConfiguration} [configuration]
*/
configure(configuration) {
if (configuration) {
this._configuration = configuration;
@ -119,11 +100,6 @@
getCurrentTextureView() {
return this._context.getCurrentTexture().createView();
}
/**
* @template {number} const T
* @param {PositiveInteger<T>} width
* @param {PositiveInteger<T>} height
*/
resize(width, height) {
if (width <= 0 || height <= 0) {
return;
@ -195,13 +171,13 @@
ExternalTexture: 3
});
// src/resources/buffer.js
// src/resources/buffer.ts
var Buffer = class _Buffer {
_device;
_handle;
_mapped = false;
/** @type {GPUBuffer} */
_defaultStagingBuffer;
constructor(device, texture) {
this._mapped = false;
this._device = device;
this._handle = texture;
}
get handle() {
return this._handle;
}
@ -214,18 +190,6 @@
get resourceType() {
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) {
try {
return new _Buffer(
@ -236,26 +200,15 @@
throw BufferError.from(err);
}
}
/**
* @param {number} [size]
*/
_createStagingOptions(size = this.size) {
return {
size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
};
}
/**
* @param {number} [size]
*/
_getStagingBuffer(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) {
if (!(this.usage & GPUBufferUsage.COPY_DST)) {
console.warn("Buffer usage does not include COPY_DST. Buffer.write may fail.");
@ -270,15 +223,6 @@
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) {
if (!this._device) {
throw WebGPUError.deviceUnavailable();
@ -315,10 +259,7 @@
await this.handle.mapAsync(GPUMapMode.READ, byteOffset, byteSize);
range = this.handle.getMappedRange(byteOffset, byteSize);
if (out != null) {
const SourceView = (
/** @type {SmallTypedArrayConstructor} */
out.constructor
);
const SourceView = out.constructor;
const bytesPerElement = SourceView.BYTES_PER_ELEMENT;
if (!bytesPerElement) {
if (out instanceof DataView) {
@ -353,11 +294,6 @@
}
return result;
}
/**
* @param {Object} [descriptor={}]
* @param {number} [descriptor.offset=0]
* @param {number} [descriptor.size]
*/
toBindingResource({ offset, size } = {}) {
return {
buffer: this._handle,
@ -371,17 +307,9 @@
}
};
var UniformBuffer = class extends Buffer {
/**
* @param {GPUDevice} device
* @param {GPUBuffer} buffer
*/
constructor(device, buffer) {
super(device, buffer);
}
/**
* @param {GPUDevice} device
* @param {Omit<GPUBufferDescriptor, 'usage'>} descriptor
*/
static create(device, descriptor) {
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
return super.create(device, {
@ -5695,7 +5623,7 @@
var GroupBindingMap = class extends Map {
};
// src/enum.js
// src/enum.ts
var AddressMode = Enum(
"clamp-to-edge",
"repeat",
@ -6244,22 +6172,14 @@
}
};
// src/resources/shader-module.js
// src/resources/shader-module.ts
var ShaderModule = class _ShaderModule {
_handle;
_code;
/** @type {WgslReflect | undefined} */
_reflection;
get handle() {
return this._handle;
}
get label() {
return this._handle.label;
}
/**
* @param {GPUDevice} device
* @param {GPUShaderModuleDescriptor} descriptor
*/
constructor(device, descriptor) {
this._code = descriptor.code;
try {
@ -6268,10 +6188,6 @@
throw WebGPUObjectError.from(err, _ShaderModule);
}
}
/**
* @param {GPUDevice} device
* @param {GPUShaderModuleDescriptor} descriptor
*/
static create(device, descriptor) {
return new _ShaderModule(device, descriptor);
}
@ -6284,22 +6200,15 @@
}
};
var ReflectedShader = class _ReflectedShader {
static _reflectTypes = ["uniforms", "storage", "textures", "samplers"];
_module;
static {
this._reflectTypes = ["uniforms", "storage", "textures", "samplers"];
}
get module() {
return this._module;
}
/**
* @param {ShaderModule} shader
*/
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) {
@ -6312,17 +6221,10 @@
}
}
}
/**
* @param {string} stageName
* @returns {string | undefined}
*/
getEntrypoint(stageName) {
const entry = this.module.reflect().entry;
return entry[stageName].length === 1 ? entry[stageName][0].name : void 0;
}
/**
* @returns {GPUShaderStageFlags}
*/
getShaderStages() {
const entry = this._module.reflect().entry;
let stages = 0;
@ -6331,16 +6233,9 @@
stages |= entry.compute.length > 0 ? GPUShaderStage.COMPUTE : 0;
return stages;
}
/**
* @param {GPUShaderStageFlags} stages
*/
hasStage(stages) {
return this.getShaderStages() & stages;
}
/**
* @param {GPUShaderStageFlags} stages
* @param {GroupBindingMap} [out=new GroupBindingMap()]
*/
getBindingsForStage(stages, out = new GroupBindingMap()) {
const groups = this._module.reflect().getBindGroups();
groups.forEach((bindings, groupIndex) => {
@ -6358,17 +6253,9 @@
});
return out;
}
/**
* @param {Map<any, any>} map
* @returns {number[]}
*/
static _sortKeyIndices(map) {
return Array.from(map.keys()).sort((a2, b2) => a2 - b2);
}
/**
* @param {VariableInfo} _variableInfo
* @returns {GPUBufferBindingLayout}
*/
static _parseUniform(_variableInfo) {
return {
type: BufferBindingType.Uniform,
@ -6377,14 +6264,9 @@
minBindingSize: 0
};
}
/**
* @param {VariableInfo} variableInfo
* @returns {GPUBufferBindingLayout}
*/
static _parseStorage(variableInfo) {
return {
type: accessToBufferType(
/** @type {WGSLAccess} */
variableInfo.access
),
// TODO: infer these two properties
@ -6392,10 +6274,6 @@
minBindingSize: 0
};
}
/**
* @param {VariableInfo} variableInfo
* @returns {GPUTextureBindingLayout}
*/
static _parseTexture(variableInfo) {
const [type, sampledType] = parseTextureType(
variableInfo.type.name
@ -6406,37 +6284,23 @@
multisampled: type.includes("multisampled")
};
}
/**
* @param {VariableInfo} variableInfo
* @returns {GPUSamplerBindingLayout}
*/
static _parseSampler(variableInfo) {
return {
type: typeToSamplerBindingType(
/** @type {WGSLSamplerType} */
variableInfo.type.name
)
};
}
/**
* @param {VariableInfo} variableInfo
* @returns {GPUStorageTextureBindingLayout}
*/
static _parseStorageTexture(variableInfo) {
const [type] = parseTextureType(variableInfo.type.name);
return {
access: accessToStorageTextureAccess(
/** @type {WGSLAccess} */
variableInfo.access
),
format: wgslToWgpuFormat(variableInfo.type.name),
viewDimension: typeToViewDimension(type)
};
}
/**
* @param {VariableStageInfo} variableStageInfo
* @returns {GPUBindGroupLayoutEntry}
*/
static _variableInfoToEntry(variableStageInfo) {
const { stages: visibility, variableInfo } = variableStageInfo;
switch (variableInfo.resourceType) {
@ -6475,9 +6339,6 @@
return;
}
}
/**
* @param {GroupBindingMap} groupBindings
*/
static createBindGroupLayoutEntries(groupBindings) {
const sortedGroupIndices = this._sortKeyIndices(groupBindings);
return sortedGroupIndices.map((groupIndex) => {
@ -6487,14 +6348,6 @@
}
};
var ShaderPair = class _ShaderPair {
/** @type {ReflectedShader} */
_vertex;
/** @type {ReflectedShader} */
_fragment;
/**
* @param {ReflectedShader} vertex
* @param {ReflectedShader} [fragment]
*/
constructor(vertex, fragment) {
if (!vertex) {
throw new Error("Missing vertex shader");
@ -6515,18 +6368,11 @@
throw new Error("Missing fragment shader.");
}
}
/** @param {ShaderModule} shader */
static fromUnifiedShader(shader) {
return new _ShaderPair(
new ReflectedShader(shader)
);
}
/**
* @param {{
vertex: ShaderModule,
fragment?: ShaderModule
* }} value
*/
static fromPair(value) {
const vert = new ReflectedShader(value.vertex);
const frag = value.fragment && new ReflectedShader(value.fragment);
@ -6549,10 +6395,6 @@
this._createGroupBindings()
);
}
/**
* @param {FragmentStateDescriptor} descriptor
* @returns {GPUFragmentState}
*/
_getFragmentState(descriptor) {
return {
module: this._fragment.module.handle,
@ -6561,10 +6403,6 @@
targets: descriptor.targets || []
};
}
/**
* @param {VertexStateDescriptor} descriptor
* @returns {GPUVertexState}
*/
_getVertexState(descriptor) {
return {
module: this._vertex.module.handle,
@ -6573,11 +6411,6 @@
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) {
@ -6585,10 +6418,6 @@
}
return variableInfo;
}
/**
* @param {ShaderPairStateDescriptor} descriptor
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}
*/
getRenderPipelineStates(descriptor) {
return {
fragment: this._getFragmentState(descriptor.fragment),
@ -6597,10 +6426,8 @@
}
};
// src/rendering/render-pipeline.js
// src/rendering/render-pipeline.ts
var RenderPipeline = class {
_handle;
_label;
get handle() {
return this._handle;
}
@ -6617,29 +6444,19 @@
}
};
// src/core/command-recorder.js
// src/core/command-recorder.ts
var CommandRecorder = class _CommandRecorder {
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 };
_device;
_swapChain;
_label;
_encoder;
/** @type {GPURenderPassEncoder | undefined} */
_passEncoder;
/**
* @param {GPUDevice} device
* @param {SwapChain} swapChain
* @param {string} [label]
*/
static {
this._defaultClearValue = { r: 0, g: 0, b: 0, a: 1 };
}
get label() {
return this._encoder.label;
}
constructor(device, swapChain, label) {
this._device = device;
this._swapChain = swapChain;
this._label = label;
this._encoder = device.createCommandEncoder({ label });
}
/**
* @returns {[GPURenderPassColorAttachment]}
*/
_defaultColorAttachment() {
const view = this._swapChain.getCurrentTextureView();
return [{
@ -6649,18 +6466,13 @@
storeOp: StoreOp.Store
}];
}
/**
* @param {GPURenderPassColorAttachment[]} [colorAttachments]
* @param {GPURenderPassDepthStencilAttachment} [depthStencilAttachment]
* @returns {GPURenderPassEncoder}
*/
beginRenderPass(colorAttachments, depthStencilAttachment) {
if (this._passEncoder) {
throw CommandRecorderError.activeRenderPass();
}
const attachments = colorAttachments || this._defaultColorAttachment();
const descriptor = {
label: this._label || "RenderPass",
label: this.label || "RenderPass",
colorAttachments: attachments,
depthStencilAttachment
};
@ -6682,10 +6494,8 @@
}
};
// src/resources/bind-group-layout.js
// src/resources/bind-group-layout.ts
var BindGroupLayout = class _BindGroupLayout {
_device;
_handle;
get handle() {
return this._handle;
}
@ -6716,10 +6526,8 @@
}
};
// src/resources/bind-group.js
// src/resources/bind-group.ts
var BindGroup = class _BindGroup {
_device;
_handle;
get handle() {
return this._handle;
}
@ -6772,13 +6580,11 @@
}
};
// src/resources/texture.js
// src/resources/texture.ts
var Texture = class _Texture {
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
_device;
_handle;
/** @type {GPUTextureView | undefined} */
_defaultView;
static {
this._defaultUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
}
get handle() {
return this._handle;
}
@ -6951,10 +6757,8 @@
}
};
// src/resources/sampler.js
// src/resources/sampler.ts
var Sampler = class _Sampler {
_device;
_handle;
get handle() {
return this._handle;
}
@ -6991,12 +6795,8 @@
}
};
// src/resources/material.js
// src/resources/material.ts
var Material = class _Material {
_device;
_shaders;
_bindGroupLayouts;
_pipelineLayout;
get shaders() {
return this._shaders;
}
@ -7101,81 +6901,52 @@
}
};
// src/core/graphics-device.js
// src/core/graphics-device.ts
var GraphicsDeviceBuilder = class {
_canvas;
get canvas() {
return this._canvas;
}
/** @type {GPURequestAdapterOptions} */
_adapter_options;
/** @type {GPUDeviceDescriptor} */
_device_descriptor;
/**
* @param {HTMLCanvasElement} [canvasElement]
*/
constructor(canvasElement) {
this._canvas = canvasElement;
}
isSupported() {
return navigator.gpu;
}
/**
* @param {HTMLCanvasElement} canvasElement
*/
withCanvas(canvasElement) {
this._canvas = canvasElement;
return this;
}
/**
* @param {GPURequestAdapterOptions} [options]
*/
withAdapter(options2) {
if (!this.isSupported()) {
throw WebGPUError.unsupported();
}
this._adapter_options = options2;
this._adapterOptions = options2;
return this;
}
/**
* @param {GPUDeviceDescriptor} [options]
*/
withDevice(options2) {
if (!this.isSupported()) {
throw WebGPUError.unsupported();
}
this._device_descriptor = options2;
this._deviceDescriptor = options2;
return this;
}
async build() {
return new GraphicsDevice(
this._canvas,
new DeviceHandler(
this._adapter_options,
this._device_descriptor
this._adapterOptions,
this._deviceDescriptor
)
);
}
};
var DeviceHandler = class {
/** @type {GPURequestAdapterOptions} */
_adapterOptions;
/** @type {GPUAdapter} */
_adapter;
get adapter() {
return this._adapter;
}
/** @type {GPUDeviceDescriptor} */
_deviceDescriptor;
/** @type {GPUDevice} */
_device;
get device() {
return this._device;
}
/**
* @param {GPURequestAdapterOptions} adapterOptions
* @param {GPUDeviceDescriptor} deviceDescriptor
*/
constructor(adapterOptions, deviceDescriptor) {
this._adapterOptions = adapterOptions;
this._deviceDescriptor = deviceDescriptor;
@ -7192,13 +6963,12 @@
}
};
var GraphicsDevice = class extends EventEmitter {
_canvas;
_deviceHandler;
/** @type {SwapChain} */
_swapChain;
/** @type {GPUQueue} */
_queue;
_isInitialized = false;
constructor(canvas, deviceHandler) {
super();
this._isInitialized = false;
this._canvas = canvas;
this._deviceHandler = deviceHandler;
}
get isInitialized() {
return this._isInitialized;
}
@ -7214,18 +6984,6 @@
get 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) {
return new GraphicsDeviceBuilder(canvas);
}
@ -7251,17 +7009,6 @@
);
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) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7276,11 +7023,6 @@
throw BufferError.from(err);
}
}
/**
* @param {number} size
* @param {ArrayBufferView | ArrayBuffer} [data]
* @param {string} [label]
*/
createUniformBuffer(size, data, label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7294,12 +7036,6 @@
}
return buffer;
}
/**
* Creates a shader module from WGSL code.
* @param {string} code
* @param {string} [label]
* @returns {ShaderModule}
*/
createShaderModule(code, label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7310,11 +7046,6 @@
throw WebGPUObjectError.from(err, ShaderModule);
}
}
/**
* Creates a render pipeline.
* @param {GPURenderPipelineDescriptor} descriptor - Raw render pipeline descriptor.
* @returns {RenderPipeline}
*/
createRenderPipeline(descriptor) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7326,9 +7057,6 @@
throw WebGPUObjectError.from(err, RenderPipeline);
}
}
/**
* @param {import('../resources/material.js').ShaderPairDescriptor} shaders
*/
createMaterial(shaders) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7339,21 +7067,12 @@
throw WebGPUObjectError.from(err, Material);
}
}
/**
* Creates a CommandRecorder to begin recording GPU commands.
* @param {string} [label]
* @returns {CommandRecorder}
*/
createCommandRecorder(label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
}
return new CommandRecorder(this.device, this._swapChain, label);
}
/**
* @param {GPUBindGroupLayoutEntry[]} entries
* @param {string} [label]
*/
createBindGroupLayout(entries, label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7363,10 +7082,6 @@
entries
});
}
/**
* @param {BindGroupEntry} binding
* @returns {GPUBindingResource}
*/
_getBindingResource(binding) {
const resource = binding.resource;
switch (resource.resourceType) {
@ -7388,11 +7103,6 @@
};
}
}
/**
* @param {BindGroupLayout} layout
* @param {BindGroupEntry[]} bindings
* @param {string} [label]
*/
createBindGroup(layout, bindings, label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7407,10 +7117,6 @@
label
});
}
/**
* @param {Array<BindGroupLayout>} layouts
* @param {string} [label]
*/
createPipelineLayout(layouts, label) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7421,18 +7127,12 @@
bindGroupLayouts
});
}
/**
* @param {GPUSamplerDescriptor} [descriptor]
*/
createSampler(descriptor) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
}
return Sampler.create(this.device, descriptor);
}
/**
* @param {GPUTextureDescriptor} descriptor
*/
createTexture(descriptor) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7443,15 +7143,6 @@
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) {
if (!this._isInitialized) {
throw GraphicsDeviceError.uninitialized();
@ -7487,10 +7178,6 @@
throw WebGPUObjectError.from(err, Texture);
}
}
/**
* Submits an array of command buffers to the GPU queue.
* @param {GPUCommandBuffer[]} commandBuffers
*/
submitCommands(commandBuffers) {
if (!this._isInitialized || !commandBuffers || commandBuffers.length === 0) return;
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
async function main() {
const canvas = (
@ -7522,6 +7361,7 @@
canvas.width = 800;
canvas.height = 600;
const graphicsDevice = await GraphicsDevice.build().withCanvas(canvas).withAdapter({ powerPreference: PowerPreference.HighPerformance }).build();
const mipGenerator = new MipGenerator(graphicsDevice.device);
const success = await graphicsDevice.initialize();
if (!success) {
console.error("Failed to initialize WebGPU.");

View file

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

10
package-lock.json generated
View file

@ -14,6 +14,7 @@
"devDependencies": {
"@webgpu/types": "^0.1.60",
"esbuild": "^0.25.2",
"esbuild-plugin-wgsl": "git+https://git.kitsu.cafe/rowan/esbuild-plugin-wgsl.git",
"typescript": "^5.8.3"
}
},
@ -490,6 +491,15 @@
"@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": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",

View file

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

View file

@ -49,7 +49,7 @@ class GraphicsDeviceBuilder {
}
constructor(canvasElement: HTMLCanvasElement) {
constructor(canvasElement?: HTMLCanvasElement) {
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)

View file

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