diff --git a/build.js b/build.js new file mode 100644 index 0000000..3df1bef --- /dev/null +++ b/build.js @@ -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 })] +}) + diff --git a/dist/index.js b/dist/index.js index 2181b3e..ec941a5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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} width - * @param {PositiveInteger} 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} SmallTypedArray - * @typedef {Exclude} 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} 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} 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} - */ 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} 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} 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 pos : array, 3> = array, 3>(\n vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0));\n\nstruct VertexOutput {\n @builtin(position) position : vec4,\n @location(0) texCoord : vec2,\n};\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n var output : VertexOutput;\n output.texCoord = pos[vertexIndex] * vec2(0.5, -0.5) + vec2(0.5);\n output.position = vec4(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;\n\n@fragment\nfn fragmentMain(@location(0) texCoord : vec2) -> @location(0) vec4 {\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."); diff --git a/index.js b/index.js index e1aa824..1bd9d1b 100644 --- a/index.js +++ b/index.js @@ -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() diff --git a/package-lock.json b/package-lock.json index dc1365f..47ffd76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 411fcf0..85c96e2 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/core/graphics-device.ts b/src/core/graphics-device.ts index 9f0b6fd..ca4604f 100644 --- a/src/core/graphics-device.ts +++ b/src/core/graphics-device.ts @@ -49,7 +49,7 @@ class GraphicsDeviceBuilder { } - constructor(canvasElement: HTMLCanvasElement) { + constructor(canvasElement?: HTMLCanvasElement) { this._canvas = canvasElement } diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..1c480cc --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,5 @@ +declare module '*.wgsl' { + const value: string + export default value +} + diff --git a/src/utils/mip-generator.ts b/src/utils/mip-generator.ts index f9a7053..ef55ae5 100644 --- a/src/utils/mip-generator.ts +++ b/src/utils/mip-generator.ts @@ -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) diff --git a/tsconfig.json b/tsconfig.json index 145c729..6464bba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "esnext", + "module": "es2022", "moduleResolution": "node", "target": "es6", "lib": ["es2022", "dom"],