This commit is contained in:
Rowan 2025-04-22 21:39:07 -05:00
parent 81f2d118a0
commit 0763a35a65
15 changed files with 136 additions and 448 deletions

View file

@ -6,6 +6,6 @@ await build({
entryPoints: ['index.js'], entryPoints: ['index.js'],
bundle: true, bundle: true,
outfile: './dist/index.js', outfile: './dist/index.js',
plugins: [wgsl({ filterWith: true, filterExtension: false })] plugins: [wgsl()]
}) })

153
dist/index.js vendored
View file

@ -7196,158 +7196,6 @@
} }
}; };
// 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 = (
@ -7361,7 +7209,6 @@
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,6 +1,5 @@
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'))
@ -14,11 +13,9 @@ async function main() {
canvas.height = 600 canvas.height = 600
const graphicsDevice = await GraphicsDevice.build() const graphicsDevice = await GraphicsDevice.build(canvas)
.withCanvas(canvas)
.withAdapter({ powerPreference: PowerPreference.HighPerformance }) .withAdapter({ powerPreference: PowerPreference.HighPerformance })
.build() .finish()
const mipGenerator = new MipGenerator(graphicsDevice.device)
const success = await graphicsDevice.initialize() const success = await graphicsDevice.initialize()
@ -119,22 +116,11 @@ async function main() {
const pipeline = graphicsDevice.createRenderPipeline(pipelineDescriptor) const pipeline = graphicsDevice.createRenderPipeline(pipelineDescriptor)
/** @type {Array<import('./src/core/graphics-device.js').BindGroupEntry>} */
const uniformBindings = [{
binding: 0,
resource: uniformBuffer
}]
const uniformBindGroup = material.createBindGroup( const uniformBindGroup = material.createBindGroup(
0, 0,
{ transform: uniformBuffer }, { transform: uniformBuffer },
'Uniforms' 'Uniforms'
) )
//const uniformBindGroup = graphicsDevice.createBindGroup(
// material.bindGroupLayouts[0],
// uniformBindings,
// 'Uniforms'
//)
async function frame() { async function frame() {
if (!graphicsDevice.isInitialized) { if (!graphicsDevice.isInitialized) {

View file

@ -35,7 +35,7 @@ export class CommandRecorder {
}] }]
} }
beginRenderPass(colorAttachments: GPURenderPassColorAttachment[], depthStencilAttachment: GPURenderPassDepthStencilAttachment): GPURenderPassEncoder { beginRenderPass(colorAttachments?: GPURenderPassColorAttachment[], depthStencilAttachment?: GPURenderPassDepthStencilAttachment): GPURenderPassEncoder {
if (this._passEncoder) { if (this._passEncoder) {
throw CommandRecorderError.activeRenderPass() throw CommandRecorderError.activeRenderPass()
} }

View file

@ -3,7 +3,7 @@ import { Buffer, UniformBuffer } from '../resources/buffer.js'
import { GraphicsDeviceError, WebGPUError, BufferError, WebGPUObjectError } from '../utils/errors.js' import { GraphicsDeviceError, WebGPUError, BufferError, WebGPUObjectError } from '../utils/errors.js'
import { GraphicsDeviceInitialized, GraphicsDeviceLost } from '../utils/events.js' import { GraphicsDeviceInitialized, GraphicsDeviceLost } from '../utils/events.js'
import { EventEmitter } from '../utils/index.js' import { EventEmitter } from '../utils/index.js'
import { ShaderModule, ShaderPairStateDescriptor } from '../resources/shader-module.js' import { ShaderModule, ShaderPairDescriptor } from '../resources/shader-module.js'
import { RenderPipeline } from '../rendering/render-pipeline.js' import { RenderPipeline } from '../rendering/render-pipeline.js'
import { CommandRecorder } from './command-recorder.js' import { CommandRecorder } from './command-recorder.js'
import { BindGroupLayout } from '../resources/bind-group-layout.js' import { BindGroupLayout } from '../resources/bind-group-layout.js'
@ -80,7 +80,7 @@ class GraphicsDeviceBuilder {
return this return this
} }
async build() { async finish() {
return new GraphicsDevice( return new GraphicsDevice(
this._canvas, this._canvas,
new DeviceHandler( new DeviceHandler(
@ -160,7 +160,7 @@ export class GraphicsDevice extends EventEmitter {
this._deviceHandler = deviceHandler this._deviceHandler = deviceHandler
} }
static build(canvas: HTMLCanvasElement) { static build(canvas?: HTMLCanvasElement) {
return new GraphicsDeviceBuilder(canvas) return new GraphicsDeviceBuilder(canvas)
} }
@ -195,7 +195,7 @@ export class GraphicsDevice extends EventEmitter {
return true return true
} }
createBuffer(descriptor: GPUBufferDescriptor, data: ArrayBufferView | ArrayBuffer): Buffer { createBuffer(descriptor: GPUBufferDescriptor, data?: ArrayBufferView | ArrayBuffer): Buffer {
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() } if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
try { try {
@ -247,7 +247,7 @@ export class GraphicsDevice extends EventEmitter {
} }
} }
createMaterial(shaders: ShaderPairStateDescriptor) { createMaterial(shaders: ShaderPairDescriptor) {
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() } if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
try { try {

View file

@ -1,6 +1,6 @@
export class RenderPipeline { export class RenderPipeline {
_handle _handle: GPURenderPipeline
_label _label: string
get handle() { get handle() {
return this._handle return this._handle
@ -10,11 +10,7 @@ export class RenderPipeline {
return this._label return this._label
} }
/** constructor(pipeline: GPURenderPipeline, label: string) {
* @param {GPURenderPipeline} pipeline
* @param {string} [label]
*/
constructor(pipeline, label) {
this._handle = pipeline this._handle = pipeline
this._label = label this._label = label
} }

View file

@ -1,8 +1,8 @@
import { WebGPUObjectError } from '../utils/errors.js' import { WebGPUObjectError } from '../utils/errors.js'
export class BindGroupLayout { export class BindGroupLayout {
_device _device: GPUDevice
_handle _handle: GPUBindGroupLayout
get handle() { get handle() {
return this._handle return this._handle
@ -12,20 +12,12 @@ export class BindGroupLayout {
return this._handle.label return this._handle.label
} }
/** constructor(device: GPUDevice, layout: GPUBindGroupLayout) {
* @param {GPUDevice} device
* @param {GPUBindGroupLayout} layout
*/
constructor(device, layout) {
this._device = device this._device = device
this._handle = layout this._handle = layout
} }
/** static create(device: GPUDevice, descriptor: GPUBindGroupLayoutDescriptor) {
* @param {GPUDevice} device
* @param {GPUBindGroupLayoutDescriptor} descriptor
*/
static create(device, descriptor) {
try { try {
return new BindGroupLayout( return new BindGroupLayout(
device, device,

View file

@ -1,8 +1,8 @@
import { WebGPUObjectError } from '../utils/errors.js' import { WebGPUObjectError } from '../utils/errors.js'
export class BindGroup { export class BindGroup {
_device _device: GPUDevice
_handle _handle: GPUBindGroup
get handle() { get handle() {
return this._handle return this._handle
@ -12,16 +12,12 @@ export class BindGroup {
* @param {GPUDevice} device * @param {GPUDevice} device
* @param {GPUBindGroup} bindGroup * @param {GPUBindGroup} bindGroup
*/ */
constructor(device, bindGroup) { constructor(device: GPUDevice, bindGroup: GPUBindGroup) {
this._device = device this._device = device
this._handle = bindGroup this._handle = bindGroup
} }
/** static create(device: GPUDevice, descriptor: GPUBindGroupDescriptor) {
* @param {GPUDevice} device
* @param {GPUBindGroupDescriptor} descriptor
*/
static create(device, descriptor) {
try { try {
return new BindGroup( return new BindGroup(
device, device,

View file

@ -1,29 +1,24 @@
import { Buffer } from './buffer.js' import { Buffer } from './buffer.js'
/** interface GeometryDescriptor {
* @typedef GeometryDescriptor vertices: Buffer
* @param {Buffer} vertices vertexCount: number
* @param {number} [vertexCount] layout: GPUVertexBufferLayout
* @param {GPUVertexBufferLayout} layout indices?: Buffer
* @property {Buffer} [indices] indexCount?: number
* @property {number} [indexCount] format?: GPUIndexFormat
* @property {GPUIndexFormat} [format] }
*/
export class Geometry { export class Geometry {
_device _device: GPUDevice
_vertices _vertices: Buffer
_vertexCount _vertexCount: number
_vertexBufferLayout _vertexBufferLayout: GPUVertexBufferLayout
_indices _indices: Buffer
_indexCount _indexCount: number
_format _format: string
/** constructor(device: GPUDevice, descriptor: GeometryDescriptor) {
* @param {GPUDevice} device
* @param {GeometryDescriptor} descriptor
*/
constructor(device, descriptor) {
this._device = device this._device = device
this._vertices = descriptor.vertices this._vertices = descriptor.vertices
this._vertexCount = descriptor.vertexCount this._vertexCount = descriptor.vertexCount

View file

@ -1,29 +1,33 @@
import { BindGroupLayout } from './bind-group-layout.js' import { BindGroupLayout } from './bind-group-layout.js'
import { ShaderPair, ShaderModule } from './shader-module.js' import { FragmentStateDescriptor, ShaderPair, ShaderPairDescriptor, VertexStateDescriptor } from './shader-module.js'
import { MaterialError, WebGPUObjectError } from '../utils/errors.js' import { MaterialError, WebGPUObjectError } from '../utils/errors.js'
import { ResourceType } from 'wgsl_reflect' import { ResourceType } from 'wgsl_reflect'
import { BindGroup } from './bind-group.js' import { BindGroup } from './bind-group.js'
import { Texture } from './texture.js'
import { Buffer } from './buffer.js'
import { Sampler } from './sampler.js'
/** @import { FragmentStateDescriptor, VertexStateDescriptor } from './shader-module.js' */ /** @import { FragmentStateDescriptor, VertexStateDescriptor } from './shader-module.js' */
/** type BindingResource = Buffer | Texture | Sampler
* @typedef ShaderPairDescriptor
* @property {ShaderModule} vertex
* @property {ShaderModule} [fragment]
*/
/** interface MaterialPipelineDescriptor {
* @typedef { label?: string
ShaderPairDescriptor & pipelineLayout?: GPUPipelineLayout
{ bindGroupLayouts?: BindGroupLayout[] } vertex: VertexStateDescriptor
* } MaterialDescriptor fragment?: FragmentStateDescriptor
*/ primitive?: GPUPrimitiveState
}
interface MaterialDescriptor extends ShaderPairDescriptor {
bindGroupLayouts?: BindGroupLayout[]
}
export class Material { export class Material {
_device _device: GPUDevice
_shaders _shaders: ShaderPair
_bindGroupLayouts _bindGroupLayouts: BindGroupLayout[]
_pipelineLayout _pipelineLayout: GPUPipelineLayout
get shaders() { get shaders() {
return this._shaders return this._shaders
@ -33,11 +37,7 @@ export class Material {
return this._bindGroupLayouts return this._bindGroupLayouts
} }
/** constructor(device: GPUDevice, descriptor: MaterialDescriptor) {
* @param {GPUDevice} device
* @param {MaterialDescriptor} descriptor
*/
constructor(device, descriptor) {
this._device = device this._device = device
this._shaders = Material._reflectShaders(descriptor) this._shaders = Material._reflectShaders(descriptor)
const bgl = descriptor.bindGroupLayouts const bgl = descriptor.bindGroupLayouts
@ -58,14 +58,8 @@ export class Material {
} }
} }
} }
/**
* Attempts to handle shader modules which represent multiple static _reflectShaders(shaders: ShaderPairDescriptor): ShaderPair {
* shader types.
*
* @param {ShaderPairDescriptor} shaders
* @returns {ShaderPair}
*/
static _reflectShaders(shaders) {
if (shaders == null) { if (shaders == null) {
throw MaterialError.missingShader('vertex') throw MaterialError.missingShader('vertex')
} }
@ -75,20 +69,12 @@ export class Material {
} }
} }
/** _reflectBindGroupLayouts(device: GPUDevice, shaders: ShaderPair): BindGroupLayout[] {
* @param {GPUDevice} device
* @param {ShaderPair} shaders
* @returns {BindGroupLayout[]}
*/
_reflectBindGroupLayouts(device, shaders) {
const layouts = shaders.createBindGroupLayoutEntries() const layouts = shaders.createBindGroupLayoutEntries()
return layouts.map(entries => BindGroupLayout.create(device, { entries })) return layouts.map(entries => BindGroupLayout.create(device, { entries }))
} }
/** createBindGroup(groupIndex: number, resources: Record<PropertyKey, BindingResource>, label?: string) {
*
*/
createBindGroup(groupIndex, resources, label) {
if (groupIndex < 0 || groupIndex >= this._bindGroupLayouts.length) { if (groupIndex < 0 || groupIndex >= this._bindGroupLayouts.length) {
throw new Error(`Invalid bind group index: ${groupIndex}`) throw new Error(`Invalid bind group index: ${groupIndex}`)
} }
@ -122,20 +108,7 @@ export class Material {
}) })
} }
/** getRenderPipelineDescriptor(descriptor: MaterialPipelineDescriptor): GPURenderPipelineDescriptor {
* @typedef MaterialPipelineDescriptor
* @property {string} [label]
* @property {GPUPipelineLayout} [pipelineLayout]
* @property {VertexStateDescriptor} vertex
* @property {FragmentStateDescriptor} [fragment]
* @property {GPUPrimitiveState} [primitive]
*/
/**
* @param {MaterialPipelineDescriptor} descriptor
* @returns {GPURenderPipelineDescriptor}
*/
getRenderPipelineDescriptor(descriptor) {
const { fragment, vertex } = this.shaders.getRenderPipelineStates(descriptor) const { fragment, vertex } = this.shaders.getRenderPipelineStates(descriptor)
return { return {

View file

@ -4,8 +4,8 @@ import { ResourceType } from '../utils/internal-enums.js'
/** @import { BindGroupEntry } from '../core/graphics-device.js' */ /** @import { BindGroupEntry } from '../core/graphics-device.js' */
export class Sampler { export class Sampler {
_device _device: GPUDevice
_handle _handle: GPUSampler
get handle() { get handle() {
return this._handle return this._handle
@ -19,20 +19,12 @@ export class Sampler {
return ResourceType.Sampler return ResourceType.Sampler
} }
/** constructor(device: GPUDevice, sampler: GPUSampler) {
* @param {GPUDevice} device
* @param {GPUSampler} sampler
*/
constructor(device, sampler) {
this._device = device this._device = device
this._handle = sampler this._handle = sampler
} }
/** static create(device: GPUDevice, descriptor: GPUSamplerDescriptor) {
* @param {GPUDevice} device
* @param {GPUSamplerDescriptor} descriptor
*/
static create(device, descriptor) {
try { try {
return new Sampler( return new Sampler(
device, device,

View file

@ -69,6 +69,11 @@ export interface ShaderPairStateDescriptor {
vertex: VertexStateDescriptor vertex: VertexStateDescriptor
} }
export interface ShaderPairDescriptor {
fragment?: ShaderModule
vertex: ShaderModule
}
export class ReflectedShader { export class ReflectedShader {
static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers'] static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers']
_module: ShaderModule _module: ShaderModule
@ -292,10 +297,7 @@ export class ShaderPair {
) )
} }
static fromPair(value: { static fromPair(value: ShaderPairDescriptor) {
vertex: ShaderModule
fragment?: ShaderModule
}) {
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)
return new ShaderPair(vert, frag) return new ShaderPair(vert, frag)

View file

@ -1,21 +1,24 @@
import { CommandRecorder } from '../core/command-recorder.js' import { TextureDimension, TextureFormat } from '../enum.js'
import { BitFlags } from '../utils/bitflags.js'
import { WebGPUObjectError } from '../utils/errors.js' import { WebGPUObjectError } from '../utils/errors.js'
import { ResourceType } from '../utils/internal-enums.js' import { ResourceType } from '../utils/internal-enums.js'
import { textureToImageDimension } from '../utils/wgsl-to-wgpu.js' import { textureToImageDimension } from '../utils/wgsl-to-wgpu.js'
/** @import { BindGroupEntry } from '../core/graphics-device.js' */ /** @import { BindGroupEntry } from '../core/graphics-device.js' */
interface UploadTextureInfo {
destination: GPUTexelCopyTextureInfo
dataLayout: GPUTexelCopyBufferLayout
size: GPUExtent3DStrict
}
export class Texture { export class Texture {
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING
| GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_DST
| GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.RENDER_ATTACHMENT
_device _device: GPUDevice
_handle _handle: GPUTexture
/** @type {GPUTextureView | undefined} */ _defaultView: GPUTextureView | undefined
_defaultView
get handle() { get handle() {
return this._handle return this._handle
@ -57,20 +60,12 @@ export class Texture {
return ResourceType.TextureView return ResourceType.TextureView
} }
/** constructor(device: GPUDevice, texture: GPUTexture) {
* @param {GPUDevice} device
* @param {GPUTexture} texture
*/
constructor(device, texture) {
this._device = device this._device = device
this._handle = texture this._handle = texture
} }
/** static create(device: GPUDevice, descriptor: GPUTextureDescriptor) {
* @param {GPUDevice} device
* @param {GPUTextureDescriptor} descriptor
*/
static create(device, descriptor) {
try { try {
return new Texture( return new Texture(
device, device,
@ -81,18 +76,13 @@ export class Texture {
} }
} }
static _generateMipLevels(size) { static _generateMipLevels(size: number[]) {
const max = Math.max.apply(undefined, size) const max = Math.max.apply(undefined, size)
return 1 + Math.log2(max) | 0 return 1 + Math.log2(max) | 0
} }
/** static async fromUrl(device: GPUDevice, url: string | URL, descriptor: GPUTextureDescriptor) {
* @param {GPUDevice} device
* @param {string | URL} url
* @param {GPUTextureDescriptor} desciptor
*/
static async fromUrl(device, url, descriptor) {
try { try {
const response = await fetch(url) const response = await fetch(url)
@ -100,8 +90,8 @@ export class Texture {
throw new Error(`Failed to fetch remote resource: ${response.statusText}`) throw new Error(`Failed to fetch remote resource: ${response.statusText}`)
} }
const usage = options.usage || Texture._defaultUsage const usage = descriptor.usage || Texture._defaultUsage
const dimension = descriptor.dimension ? textureToImageDimension(descriptor.dimension) : '2d' const dimension = descriptor.dimension ? textureToImageDimension(descriptor.dimension) : TextureDimension['2d']
const blob = await response.blob() const blob = await response.blob()
const bitmap = await createImageBitmap(blob) const bitmap = await createImageBitmap(blob)
@ -111,8 +101,8 @@ export class Texture {
usage, usage,
dimension, dimension,
size, size,
format: descriptor.format || 'rgba8unorm', format: descriptor.format || TextureFormat.Rgba8unorm,
mipLevelCount: descriptor.mipLevelCount || Texture._generateMipCount(...size), mipLevelCount: descriptor.mipLevelCount || Texture._generateMipLevels(size),
...descriptor, ...descriptor,
} }
@ -126,13 +116,7 @@ export class Texture {
} }
} }
/** static createRenderTarget(device: GPUDevice, size: GPUExtent3DStrict, format: GPUTextureFormat, descriptor: GPUTextureDescriptor) {
* @param {GPUDevice} device
* @param {GPUExtent3DStrict} size
* @param {GPUTextureFormat} format
* @param {GPUTextureDescriptor} descriptor
*/
static createRenderTarget(device, size, format, descriptor) {
const usage = descriptor.usage || Texture._defaultUsage const usage = descriptor.usage || Texture._defaultUsage
return Texture.create(device, { return Texture.create(device, {
@ -143,18 +127,8 @@ export class Texture {
}) })
} }
/**
* @typedef UploadTextureInfo
* @property {GPUTexelCopyTextureInfo} destination
* @property {GPUTexelCopyBufferLayout} dataLayout
* @property {GPUExtent3DStrict} size
*/
/** upload(source: GPUAllowSharedBufferSource, options?: UploadTextureInfo) {
* @param {GPUAllowSharedBufferSource} source
* @param {UploadTextureInfo} [options={}]
*/
upload(source, options = {}) {
const mipLevel = options.destination.mipLevel || 0 const mipLevel = options.destination.mipLevel || 0
const size = options.size || [ const size = options.size || [
Math.max(1, this.width >> mipLevel), Math.max(1, this.width >> mipLevel),
@ -174,28 +148,11 @@ export class Texture {
} }
} }
/** generateMipmaps(_commandEncoder: GPUCommandEncoder) {
* @param {GPUCommandEncoder} commandEncoder // TODO: use MipGenerator
*/
generateMipmaps(commandEncoder) {
const requiredUsage = GPUTextureUsage.COPY_SRC
| GPUTextureUsage.COPY_DST
| GPUTextureUsage.RENDER_ATTACHMENT
if (!BitFlags.has(this.usage & requiredUsage)) {
throw new Error('Texture does not have the required usage flags for mipmap generation')
}
for (let i = 1; i < this.mipLevelCount; ++i) {
}
} }
/** createDefaultView(descriptor?: GPUTextureViewDescriptor) {
* @param {GPUTextureViewDescriptor} [descriptor]
* @throws {TextureError}
*/
createDefaultView(descriptor) {
if (!descriptor && this._defaultView) { if (!descriptor && this._defaultView) {
return this._defaultView return this._defaultView
} }

View file

@ -1,73 +1,49 @@
/**
* @typedef {'read' | 'write' | 'read_write'} WGSLAccess
* @typedef {'f32' | 'i32' | 'u32'} WGSLSampledType
*/
import { BufferBindingType, StorageTextureAccess, TextureFormat } from '../enum.js' import { BufferBindingType, StorageTextureAccess, TextureFormat } from '../enum.js'
/** export type WGSLAccess = 'read' | 'write' | 'read_write'
* @typedef { export type WGSLSampledType = 'f32' | 'i32' | 'u32'
'texture_1d' export type WGSLSampledTextureType = 'texture_1d'
| 'texture_2d' | 'texture_2d'
| 'texture_2d_array' | 'texture_2d_array'
| 'texture_3d' | 'texture_3d'
| 'texture_cube' | 'texture_cube'
| 'texture_cube_array' | 'texture_cube_array'
* } WGSLSampledTextureType
*
* @typedef {
'texture_multisampled_2d'
| 'texture_depth_multisampled_2d'
* } WGSLMultisampledTextureType
*
* @typedef {
'texture_storage_1d'
| 'texture_storage_2d'
| 'texture_storage_2d_array'
| 'texture_storage_3d'
* } WGSLStorageTextureType
*
* @typedef {
'texture_depth_2d'
| 'texture_depth_2d_array'
| 'texture_depth_cube'
| 'texture_depth_cube_array'
* } WGSLDepthTextureType
*
* @typedef {
WGSLSampledTextureType
| WGSLMultisampledTextureType
| WGSLStorageTextureType
| WGSLDepthTextureType
* } WGSLTextureType
*/
/** export type WGSLMultisampledTextureType = 'texture_multisampled_2d'
* @typedef { | 'texture_depth_multisampled_2d'
'sampler'
| 'sampler_comparison'
* } WGSLSamplerType
*/
/** export type WGSLStorageTextureType = 'texture_storage_1d'
* @param {string} typeName | 'texture_storage_2d'
* @returns {[WGSLTextureType, WGSLSampledType]} | 'texture_storage_2d_array'
*/ | 'texture_storage_3d'
export const parseTextureType = (typeName) => {
export type WGSLDepthTextureType = 'texture_depth_2d'
| 'texture_depth_2d_array'
| 'texture_depth_cube'
| 'texture_depth_cube_array'
export type WGSLTextureType = WGSLSampledTextureType
| WGSLMultisampledTextureType
| WGSLStorageTextureType
| WGSLDepthTextureType
export type WGSLSamplerType = 'sampler'
| 'sampler_comparison'
export const parseTextureType = (typeName: string): [WGSLTextureType, WGSLSampledType] => {
const chevronIndex = typeName.indexOf('<') const chevronIndex = typeName.indexOf('<')
const type = typeName.slice(0, chevronIndex) const type = typeName.slice(0, chevronIndex)
const sampledType = typeName.slice(chevronIndex + 1, -1) const sampledType = typeName.slice(chevronIndex + 1, -1)
// FIXME: there's no validation
return [ return [
/** @type {WGSLTextureType} */ (type), type as WGSLTextureType,
/** @type {WGSLSampledType} */ (sampledType) sampledType as WGSLSampledType
] ]
} }
/** export const accessToBufferType = (access: WGSLAccess): GPUBufferBindingType => {
* @param {WGSLAccess} access
* @returns {GPUBufferBindingType}
*/
export const accessToBufferType = access => {
switch (access) { switch (access) {
case 'read': return BufferBindingType.ReadOnlyStorage case 'read': return BufferBindingType.ReadOnlyStorage
case 'write': case 'write':
@ -77,11 +53,7 @@ export const accessToBufferType = access => {
} }
} }
/** export const accessToStorageTextureAccess = (access: WGSLAccess): GPUStorageTextureAccess => {
* @param {WGSLAccess} access
* @returns {GPUStorageTextureAccess}
*/
export const accessToStorageTextureAccess = access => {
switch (access) { switch (access) {
case 'read': return StorageTextureAccess.ReadOnly case 'read': return StorageTextureAccess.ReadOnly
case 'write': return StorageTextureAccess.WriteOnly case 'write': return StorageTextureAccess.WriteOnly
@ -135,8 +107,7 @@ export const FormatToFilterType = Object.freeze({
stencil8: 'uint' stencil8: 'uint'
}) })
/** @param {string} format */ export const formatToFilterType = (format: string) => (
export const formatToFilterType = format => (
FormatToFilterType[format] || 'float' FormatToFilterType[format] || 'float'
) )
@ -186,14 +157,12 @@ export const FormatToStride = Object.freeze({
stencil8: 1 stencil8: 1
}) })
/** @param {string} typeName */ export const typeToStride = (typeName: string) => (
export const typeToStride = typeName => (
FormatToStride[typeName] || 1 FormatToStride[typeName] || 1
) )
/** @param {WGSLTextureType} typeName */ export const typeToViewDimension = (typeName: WGSLTextureType) => {
export const typeToViewDimension = typeName => {
switch (typeName) { switch (typeName) {
case 'texture_1d': case 'texture_1d':
case 'texture_storage_1d': case 'texture_storage_1d':
@ -228,11 +197,7 @@ export const typeToViewDimension = typeName => {
} }
} }
/** export const textureToImageDimension = (dimension: GPUTextureViewDimension): GPUTextureDimension => {
* @param {GPUTextureViewDimension} dimension
* @returns {GPUTextureDimension}
*/
export const textureToImageDimension = dimension => {
switch (dimension) { switch (dimension) {
case '1d': case '1d':
return '1d' return '1d'
@ -250,11 +215,7 @@ export const textureToImageDimension = dimension => {
} }
} }
/** export const wgslToWgpuFormat = (format: string): GPUTextureFormat => {
* @param {string} format
* @returns {GPUTextureFormat}
*/
export const wgslToWgpuFormat = (format) => {
switch (format) { switch (format) {
case 'f32': case 'f32':
return TextureFormat.Bgra8unorm return TextureFormat.Bgra8unorm
@ -299,12 +260,7 @@ export const wgslToWgpuFormat = (format) => {
} }
} }
/** export const typeToTextureSampleType = (type: WGSLTextureType, sampledType: WGSLSampledType): GPUTextureSampleType => {
* @param {WGSLTextureType} type
* @param {WGSLSampledType} sampledType
* @returns {GPUTextureSampleType}
*/
export const typeToTextureSampleType = (type, sampledType) => {
if (type.includes('depth')) { if (type.includes('depth')) {
return 'depth' return 'depth'
} }
@ -318,11 +274,7 @@ export const typeToTextureSampleType = (type, sampledType) => {
} }
} }
/** export const typeToSamplerBindingType = (type: WGSLSamplerType): GPUSamplerBindingType => {
* @param {WGSLSamplerType} type
* @returns {GPUSamplerBindingType}
*/
export const typeToSamplerBindingType = type => {
switch (type) { switch (type) {
case 'sampler_comparison': case 'sampler_comparison':
return 'comparison' return 'comparison'