diff --git a/package-lock.json b/package-lock.json index 2eac66f..6407f80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "canvas", + "name": "untitled-game-engine", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/plugins/renderer/webgl-context.js b/src/plugins/renderer/engine/webgl/context.js similarity index 100% rename from src/plugins/renderer/webgl-context.js rename to src/plugins/renderer/engine/webgl/context.js diff --git a/src/plugins/renderer/engine/webgpu/context.js b/src/plugins/renderer/engine/webgpu/context.js new file mode 100644 index 0000000..4aa1976 --- /dev/null +++ b/src/plugins/renderer/engine/webgpu/context.js @@ -0,0 +1,427 @@ +export class WebGPUError extends Error { + /** + * @param {string} message + */ + constructor(message) { + super(message) + } + + static unsupported() { + return new WebGPUError("WebGPU is unsupported in this browser") + } + + static adapterUnavailable() { + return new WebGPUError("Could not request a WebGPU adapter.") + } +} + +export class WebGPUBuilderError extends Error { + /** + * @param {string} message + */ + constructor(message) { + super(message) + } + + /** + * @param {string} property + */ + static missing(property) { + return new WebGPUBuilderError(`Missing required property: ${property}`) + } +} + +class GPUCanvasConfigurationBuilder { + /** @type {WebGPUContextBuilder} */ + #contextBuilder + /** @type {GPUTextureUsageFlags} */ + #usage = 0x10 + /** @type {GPUTextureFormat[]} */ + #viewFormats = [] + /** @type {PredefinedColorSpace} */ + #colorSpace = 'srgb' + /** @type {GPUCanvasToneMapping} */ + #toneMapping = {} + /** @type {GPUCanvasAlphaMode} */ + #alphaMode = 'opaque' + + /** + * @param {WebGPUContextBuilder} builder + */ + constructor(builder) { + this.#contextBuilder = builder + } + + /** + * @param {GPUTextureUsageFlags} usage + */ + usage(usage) { + this.#usage = usage + return this + } + + /** + * @param {GPUTextureUsageFlags} usage + */ + addUsage(usage) { + this.#usage = this.#usage | usage + return this + } + + /** + * @param {GPUTextureFormat[]} formats + */ + viewFormats(formats) { + this.#viewFormats = formats + return this + } + + /** + * @param {GPUTextureFormat} format + */ + addViewFormat(format) { + this.#viewFormats.push(format) + return this + } + + /** + * @param {PredefinedColorSpace} space + */ + colorSpace(space) { + this.#colorSpace = space + return this + } + + /** + * @param {GPUCanvasToneMappingMode} mode + */ + toneMappingMode(mode) { + this.#toneMapping = { mode } + return this + } + + /** + * @param {GPUCanvasAlphaMode} mode + */ + alphaMode(mode) { + this.#alphaMode = mode + return this + } + + /** + * @returns {GPUCanvasBuilderConfiguration} + */ + build() { + return { + usage: this.#usage, + viewFormats: this.#viewFormats, + colorSpace: this.#colorSpace, + toneMapping: this.#toneMapping, + alphaMode: this.#alphaMode + } + } + + /** + * @returns {WebGPUContextBuilder} + */ + apply() { + return this.#contextBuilder.configureCanvas(this.build()) + } +} + +/** @typedef {'core' | 'compatibility'} GPUFeatureLevel */ + +class GPUAdapterOptionsBuilder { + #contextBuilder + /** @type {GPUFeatureLevel} */ + #featureLevel = 'core' + /** @type {GPUPowerPreference} */ + #powerPreference + /** @type {boolean} */ + #forceFallbackAdapter = false + /** @type {boolean} */ + #xrCompatible = false + + /** + * @param {WebGPUContextBuilder} builder + */ + constructor(builder) { + this.#contextBuilder = builder + } + + /** + * @param {GPUFeatureLevel} featureLevel + */ + featureLevel(featureLevel) { + this.#featureLevel = featureLevel + return this + } + + /** + * @param {GPUPowerPreference?} preference + */ + powerPreference(preference) { + this.#powerPreference = preference + return this + } + + /** + * @param {boolean} force + */ + forceFallbackAdapter(force) { + this.#forceFallbackAdapter = force + return this + } + + /** + * @param {boolean} compatible + */ + xrCompatible(compatible) { + this.#xrCompatible = compatible + return this + } + + /** + * @returns {GPURequestAdapterOptions} + */ + build() { + return { + featureLevel: this.#featureLevel, + powerPreference: this.#powerPreference, + forceFallbackAdapter: this.#forceFallbackAdapter, + xrCompatible: this.#xrCompatible + } + } + + apply() { + return this.#contextBuilder.adapter(this.build()) + } +} + +class GPUDeviceDescriptorBuilder { + /** @type {WebGPUContextBuilder} */ + #contextBuilder + + /** @type {GPUFeatureName[]} */ + #features = [] + + /** @type {Record} */ + #limits = {} + + /** + * @param {WebGPUContextBuilder} builder + */ + constructor(builder) { + this.#contextBuilder = builder + } + + /** + * @param {GPUFeatureName[]} features + */ + requiredFeatures(features) { + this.#features = features + return this + } + + /** + * @param {GPUFeatureName} feature + */ + addRequiredFeature(feature) { + this.#features.push(feature) + return this + } + + /** + * @param {Record} limits + */ + requiredLimits(limits) { + this.#limits = limits + return this + } + + /** + * @param {string} key + * @param {GPUSize64?} value + */ + setRequiredLimit(key, value) { + this.#limits[key] = value + return this + } + + /** + * @returns {GPUDeviceDescriptor} + */ + build() { + return { + requiredFeatures: this.#features, + requiredLimits: this.#limits + } + } + + apply() { + return this.#contextBuilder.device(this.build()) + } +} + +/** + * @typedef {Object} GPUCanvasBuilderConfiguration + * @property {GPUTextureUsageFlags} [usage=0x10] + * @property {GPUTextureFormat[]} [viewFormats=[]] + * @property {PredefinedColorSpace} [colorSpace='srgb'] + * @property {GPUCanvasToneMapping} [toneMapping = {}] + * @property {GPUCanvasAlphaMode} [alphaMode='opaque'] + */ + +export class WebGPUContextBuilder { + /** @type {Promise} */ + #adapter + + /** @type {GPUCanvasBuilderConfiguration} */ + #canvasConfig + + /** @type {GPUCanvasContext} */ + #context + + /** @type {Promise} */ + #device + + /** @type {number} */ + #dpr = window.devicePixelRatio || 1 + + /** + * @param {string} type + */ + #warnDefault(type) { + console.warn(`WARN: Requesting WebGPU ${type} with default options.`) + } + + /** + * @param {GPURequestAdapterOptions} [descriptor] + * @returns {WebGPUContextBuilder} + * @throws {WebGPUError} Throws if WebGPU is not supported + */ + adapter(descriptor) { + if (!navigator.gpu) { + throw WebGPUError.unsupported() + } + + this.#adapter = navigator.gpu.requestAdapter(descriptor) + return this + } + + buildAdapterConfiguration() { + return new GPUAdapterOptionsBuilder(this) + } + + /** + * @param {HTMLCanvasElement} canvas + * @returns {WebGPUContextBuilder} + */ + context(canvas) { + this.#context = canvas.getContext('webgpu') + return this + } + + buildCanvasConfiguration() { + return new GPUCanvasConfigurationBuilder(this) + } + + /** + * @param {GPUDeviceDescriptor} [descriptor] + * @returns {WebGPUContextBuilder} + * @throws {WebGPUError} Throws if WebGPU is not supported + */ + device(descriptor) { + if (!this.#adapter) { + this.#warnDefault('adapter') + this.adapter() + } + + this.#device = this.#adapter.then(adapter => adapter.requestDevice(descriptor)) + + return this + } + + buildDeviceDescriptor() { + return new GPUDeviceDescriptorBuilder(this) + } + + /** + * @param {GPUCanvasBuilderConfiguration} [configuration] + * @returns {WebGPUContextBuilder} + */ + configureCanvas(configuration) { + this.#canvasConfig = configuration + return this + } + + /** + * @returns {Promise} + * @throws {WebGPUBuilderError} Throws if context is undefined or if WebGPU is unsupported + */ + async build() { + if (!this.#context) { + throw WebGPUBuilderError.missing('context') + } + + if (!this.#device) { + this.#warnDefault('device') + this.device() + } + + const [adapter, device] = await Promise.all([this.#adapter, this.#device]) + + const format = navigator.gpu.getPreferredCanvasFormat() + + if (this.#canvasConfig) { + this.#context.configure({ + device, + format, + ...this.#canvasConfig + }) + } + + return new WebGPUContext( + this.#context, + adapter, + device, + format, + this.#dpr + ) + } +} + +export class WebGPUContext { + #adapter + #context + #device + #format + #dpr + + /** + * @param {GPUCanvasContext} context + * @param {GPUAdapter} adapter + * @param {GPUDevice} device + * @param {GPUTextureFormat} format + * @param {number} [dpr] + */ + constructor(context, adapter, device, format, dpr) { + this.#context = context + this.#adapter = adapter + this.#device = device + this.#format = format + this.#dpr = dpr || window.devicePixelRatio || 1 + } + + get adapter() { return this.#adapter } + get context() { return this.#context } + get device() { return this.#device } + get queue() { return this.#device.queue } + get format() { return this.#format } + get devicePixelRatio() { return this.#dpr } + + static create() { + return new WebGPUContextBuilder() + } +} + diff --git a/src/plugins/renderer/mesh/index.js b/src/plugins/renderer/engine/webgpu/mesh/index.js similarity index 100% rename from src/plugins/renderer/mesh/index.js rename to src/plugins/renderer/engine/webgpu/mesh/index.js diff --git a/src/plugins/renderer/mesh/mesh.js b/src/plugins/renderer/engine/webgpu/mesh/mesh.js similarity index 100% rename from src/plugins/renderer/mesh/mesh.js rename to src/plugins/renderer/engine/webgpu/mesh/mesh.js diff --git a/src/plugins/renderer/mesh/vertex-format.js b/src/plugins/renderer/engine/webgpu/mesh/vertex-format.js similarity index 97% rename from src/plugins/renderer/mesh/vertex-format.js rename to src/plugins/renderer/engine/webgpu/mesh/vertex-format.js index e236d31..f77021a 100644 --- a/src/plugins/renderer/mesh/vertex-format.js +++ b/src/plugins/renderer/engine/webgpu/mesh/vertex-format.js @@ -1,8 +1,8 @@ // https://www.w3.org/TR/webgpu/#enumdef-gpuvertexformat -import { Union } from '/public/vendor/kojima/union.js' -import { capitalizen } from '/src/utils/index' +import { capitalizen } from "/src/utils/index" +// TODO: move this somewhere that makes sense /** @typedef {Uint8ArrayConstructor | Int8ArrayConstructor | Uint16ArrayConstructor | Int16ArrayConstructor | Uint32ArrayConstructor | Int32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */ /** @type {GPUVertexFormat[]} */ diff --git a/src/plugins/renderer/mesh/vertex.js b/src/plugins/renderer/engine/webgpu/mesh/vertex.js similarity index 100% rename from src/plugins/renderer/mesh/vertex.js rename to src/plugins/renderer/engine/webgpu/mesh/vertex.js diff --git a/src/plugins/renderer/webgpu-context.js b/src/plugins/renderer/webgpu-context.js deleted file mode 100644 index 255fb4a..0000000 --- a/src/plugins/renderer/webgpu-context.js +++ /dev/null @@ -1,427 +0,0 @@ -export class WebGPUError extends Error { - /** - * @param {string} message - */ - constructor(message) { - super(message) - } - - static unsupported() { - return new WebGPUError("WebGPU is unsupported in this browser") - } - - static adapterUnavailable() { - return new WebGPUError("Could not request a WebGPU adapter.") - } -} - -export class WebGPUBuilderError extends Error { - /** - * @param {string} message - */ - constructor(message) { - super(message) - } - - /** - * @param {string} property - */ - static missing(property) { - return new WebGPUBuilderError(`Missing required property: ${property}`) - } -} - -class GPUCanvasConfigurationBuilder { - /** @type {WebGPUContextBuilder} */ - #contextBuilder - /** @type {GPUTextureUsageFlags} */ - #usage = 0x10 - /** @type {GPUTextureFormat[]} */ - #viewFormats = [] - /** @type {PredefinedColorSpace} */ - #colorSpace = 'srgb' - /** @type {GPUCanvasToneMapping} */ - #toneMapping = {} - /** @type {GPUCanvasAlphaMode} */ - #alphaMode = 'opaque' - - /** - * @param {WebGPUContextBuilder} builder - */ - constructor(builder) { - this.#contextBuilder = builder - } - - /** - * @param {GPUTextureUsageFlags} usage - */ - usage(usage) { - this.#usage = usage - return this - } - - /** - * @param {GPUTextureUsageFlags} usage - */ - addUsage(usage) { - this.#usage = this.#usage | usage - return this - } - - /** - * @param {GPUTextureFormat[]} formats - */ - viewFormats(formats) { - this.#viewFormats = formats - return this - } - - /** - * @param {GPUTextureFormat} format - */ - addViewFormat(format) { - this.#viewFormats.push(format) - return this - } - - /** - * @param {PredefinedColorSpace} space - */ - colorSpace(space) { - this.#colorSpace = space - return this - } - - /** - * @param {GPUCanvasToneMappingMode} mode - */ - toneMappingMode(mode) { - this.#toneMapping = { mode } - return this - } - - /** - * @param {GPUCanvasAlphaMode} mode - */ - alphaMode(mode) { - this.#alphaMode = mode - return this - } - - /** - * @returns {GPUCanvasBuilderConfiguration} - */ - build() { - return { - usage: this.#usage, - viewFormats: this.#viewFormats, - colorSpace: this.#colorSpace, - toneMapping: this.#toneMapping, - alphaMode: this.#alphaMode - } - } - - /** - * @returns {WebGPUContextBuilder} - */ - apply() { - return this.#contextBuilder.configureCanvas(this.build()) - } -} - -/** @typedef {'core' | 'compatibility'} GPUFeatureLevel */ - -class GPUAdapterOptionsBuilder { - #contextBuilder - /** @type {GPUFeatureLevel} */ - #featureLevel = 'core' - /** @type {GPUPowerPreference} */ - #powerPreference - /** @type {boolean} */ - #forceFallbackAdapter = false - /** @type {boolean} */ - #xrCompatible = false - - /** - * @param {WebGPUContextBuilder} builder - */ - constructor(builder) { - this.#contextBuilder = builder - } - - /** - * @param {GPUFeatureLevel} featureLevel - */ - featureLevel(featureLevel) { - this.#featureLevel = featureLevel - return this - } - - /** - * @param {GPUPowerPreference?} preference - */ - powerPreference(preference) { - this.#powerPreference = preference - return this - } - - /** - * @param {boolean} force - */ - forceFallbackAdapter(force) { - this.#forceFallbackAdapter = force - return this - } - - /** - * @param {boolean} compatible - */ - xrCompatible(compatible) { - this.#xrCompatible = compatible - return this - } - - /** - * @returns {GPURequestAdapterOptions} - */ - build() { - return { - featureLevel: this.#featureLevel, - powerPreference: this.#powerPreference, - forceFallbackAdapter: this.#forceFallbackAdapter, - xrCompatible: this.#xrCompatible - } - } - - apply() { - return this.#contextBuilder.adapter(this.build()) - } -} - -class GPUDeviceDescriptorBuilder { - /** @type {WebGPUContextBuilder} */ - #contextBuilder - - /** @type {GPUFeatureName[]} */ - #features = [] - - /** @type {Record} */ - #limits = {} - - /** - * @param {WebGPUContextBuilder} builder - */ - constructor(builder) { - this.#contextBuilder = builder - } - - /** - * @param {GPUFeatureName[]} features - */ - requiredFeatures(features) { - this.#features = features - return this - } - - /** - * @param {GPUFeatureName} feature - */ - addRequiredFeature(feature) { - this.#features.push(feature) - return this - } - - /** - * @param {Record} limits - */ - requiredLimits(limits) { - this.#limits = limits - return this - } - - /** - * @param {string} key - * @param {GPUSize64?} value - */ - setRequiredLimit(key, value) { - this.#limits[key] = value - return this - } - - /** - * @returns {GPUDeviceDescriptor} - */ - build() { - return { - requiredFeatures: this.#features, - requiredLimits: this.#limits - } - } - - apply() { - return this.#contextBuilder.device(this.build()) - } -} - -/** - * @typedef {Object} GPUCanvasBuilderConfiguration - * @property {GPUTextureUsageFlags} [usage=0x10] - * @property {GPUTextureFormat[]} [viewFormats=[]] - * @property {PredefinedColorSpace} [colorSpace='srgb'] - * @property {GPUCanvasToneMapping} [toneMapping = {}] - * @property {GPUCanvasAlphaMode} [alphaMode='opaque'] - */ - -export class WebGPUContextBuilder { - /** @type {Promise} */ - #adapter - - /** @type {GPUCanvasBuilderConfiguration} */ - #canvasConfig - - /** @type {GPUCanvasContext} */ - #context - - /** @type {Promise} */ - #device - - /** @type {number} */ - #dpr = window.devicePixelRatio || 1 - - /** - * @param {string} type - */ - #warnDefault(type) { - console.warn(`WARN: Requesting WebGPU ${type} with default options.`) - } - - /** - * @param {GPURequestAdapterOptions} [descriptor] - * @returns {WebGPUContextBuilder} - * @throws {WebGPUError} Throws if WebGPU is not supported - */ - adapter(descriptor) { - if (!navigator.gpu) { - throw WebGPUError.unsupported() - } - - this.#adapter = navigator.gpu.requestAdapter(descriptor) - return this - } - - buildAdapterConfiguration() { - return new GPUAdapterOptionsBuilder(this) - } - - /** - * @param {HTMLCanvasElement} canvas - * @returns {WebGPUContextBuilder} - */ - context(canvas) { - this.#context = canvas.getContext('webgpu') - return this - } - - buildCanvasConfiguration() { - return new GPUCanvasConfigurationBuilder(this) - } - - /** - * @param {GPUDeviceDescriptor} [descriptor] - * @returns {WebGPUContextBuilder} - * @throws {WebGPUError} Throws if WebGPU is not supported - */ - device(descriptor) { - if (!this.#adapter) { - this.#warnDefault('adapter') - this.adapter() - } - - this.#device = this.#adapter.then(adapter => adapter.requestDevice(descriptor)) - - return this - } - - buildDeviceDescriptor() { - return new GPUDeviceDescriptorBuilder(this) - } - - /** - * @param {GPUCanvasBuilderConfiguration} [configuration] - * @returns {WebGPUContextBuilder} - */ - configureCanvas(configuration) { - this.#canvasConfig = configuration - return this - } - - /** - * @returns {Promise} - * @throws {WebGPUBuilderError} Throws if context is undefined or if WebGPU is unsupported - */ - async build() { - if (!this.#context) { - throw WebGPUBuilderError.missing('context') - } - - if (!this.#device) { - this.#warnDefault('device') - this.device() - } - - const [adapter, device] = await Promise.all([this.#adapter, this.#device]) - - const format = navigator.gpu.getPreferredCanvasFormat() - - if (this.#canvasConfig) { - this.#context.configure({ - device, - format, - ...this.#canvasConfig - }) - } - - return new WebGPUContext( - this.#context, - adapter, - device, - format, - this.#dpr - ) - } -} - -export class WebGPUContext { - #adapter - #context - #device - #format - #dpr - - /** - * @param {GPUCanvasContext} context - * @param {GPUAdapter} adapter - * @param {GPUDevice} device - * @param {GPUTextureFormat} format - * @param {number} [dpr] - */ - constructor(context, adapter, device, format, dpr) { - this.#context = context - this.#adapter = adapter - this.#device = device - this.#format = format - this.#dpr = dpr || window.devicePixelRatio || 1 - } - - get adapter() { return this.#adapter } - get context() { return this.#context } - get device() { return this.#device } - get queue() { return this.#device.queue } - get format() { return this.#format } - get devicePixelRatio() { return this.#dpr } - - static create() { - return new WebGPUContextBuilder() - } -} - diff --git a/src/utils/ordered-map.js b/src/utils/ordered-map.js deleted file mode 100644 index 68c24d4..0000000 --- a/src/utils/ordered-map.js +++ /dev/null @@ -1,105 +0,0 @@ -/** @typedef {-1 | 0 | 1} Ternary */ -/** - * @template T - * @typedef {(a: T, b: T) => Ternary | NaN} Comparer - */ - -/** - * @template X, Y - * @param {X | Y} a - * @param {Y} b - * @returns [X | undefined, Y | undefined] - */ -const optionalSecond = (a, b) => { - let x = a - let y = b - - if (!b) { - x = undefined - y = b - } - - return [x, y] -} - -/** - * @template K, V - */ -export class OrderedMap extends Map { - _sort - - /** - * @overload - * @param {Iterable.<[K, V]>} iterable - * @param {Comparer<[K, V]>} comparer - */ - /** - * @overload - * @param {Comparer<[K, V]>} comparer - */ - /** - * @param {Iterable.<[K, V]> | Comparer<[K, V]>} iterable - * @param {Comparer<[K, V]>} [comparer] - */ - constructor(iterable, comparer) { - const [iter, sort] = optionalSecond(iterable, comparer) - - /** @ts-ignore */ - super(iter) - this._sort = sort - } - - /** - * @param {Comparer<[K, V]>} comparer - */ - sortWith(comparer) { - this._sort = comparer - } - - *[Symbol.iterator]() { - /** @ts-ignore */ - yield* [...this.entries()].sort(this._sort) - } -} - -/** - * @template T - * @param {(value: T) => number} get - * @returns {Comparer} - */ -const prioritySort = get => (a, b) => get(a) - get(b) - -/** - * @template K, V - */ -export class PriorityMap extends OrderedMap { - /** - * @typedef {(value: [K, V]) => number} PriorityGetter - */ - - /** @type PriorityGetter */ - _getter - - /** - * @overload - * @param {Iterable.<[K, V]>} iterable - * @param {PriorityGetter} getter - */ - /** - * @overload - * @param {PriorityGetter} getter - */ - /** - * @param {Iterable.<[K, V]> | PriorityGetter} iterable - * @param {PriorityGetter} [getter] - */ - constructor(iterable, getter) { - const [iter, get] = optionalSecond(iterable, getter) - - /** @ts-ignore */ - super(iter, prioritySort(get)) - - /** @ts-ignore */ - this._getter = get - } -} diff --git a/src/utils/webgpu.js b/src/utils/webgpu.js deleted file mode 100644 index f7941e1..0000000 --- a/src/utils/webgpu.js +++ /dev/null @@ -1,9 +0,0 @@ -export class Attribute { - #name - #format - #location - #buffer - #count - #components -} -