move to ts
This commit is contained in:
parent
d75f3b4c49
commit
95b25c962a
33 changed files with 1020 additions and 1103 deletions
285
dist/index.js
vendored
285
dist/index.js
vendored
|
@ -1,12 +1,8 @@
|
||||||
(() => {
|
(() => {
|
||||||
// src/utils/errors.js
|
// src/utils/errors.ts
|
||||||
var WebGPUError = class _WebGPUError extends Error {
|
var WebGPUError = class _WebGPUError extends Error {
|
||||||
/**
|
constructor(message, options2) {
|
||||||
* @param {string} message
|
super(`WebGPUError: ${message}`, options2);
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`WebGPUError: ${message}`, options);
|
|
||||||
}
|
}
|
||||||
static unsupported() {
|
static unsupported() {
|
||||||
return new _WebGPUError("WebGPU is not supported on this browser");
|
return new _WebGPUError("WebGPU is not supported on this browser");
|
||||||
|
@ -32,58 +28,32 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var WebGPUObjectError = class _WebGPUObjectError extends Error {
|
var WebGPUObjectError = class _WebGPUObjectError extends Error {
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {Error} cause
|
|
||||||
* @param {string | Newable<T>} [type]
|
|
||||||
*/
|
|
||||||
static from(cause, type) {
|
static from(cause, type) {
|
||||||
const name = typeof type === "string" ? type : type.name;
|
const name = typeof type === "string" ? type : type.name;
|
||||||
return new _WebGPUObjectError(`could not create ${name}`, { cause });
|
return new _WebGPUObjectError(`could not create ${name}`, { cause });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BufferError = class _BufferError extends WebGPUObjectError {
|
var BufferError = class _BufferError extends WebGPUObjectError {
|
||||||
/**
|
constructor(message, options2) {
|
||||||
* @param {string} message
|
super(`BufferError: ${message}`, options2);
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`BufferError: ${message}`, options);
|
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {Error} cause
|
|
||||||
*/
|
|
||||||
static from(cause) {
|
static from(cause) {
|
||||||
return new _BufferError("could not create buffer", { cause });
|
return new _BufferError("could not create buffer", { cause });
|
||||||
}
|
}
|
||||||
static invalidRead() {
|
static invalidRead() {
|
||||||
return new _BufferError("cannot read a buffer without MAP_READ usage");
|
return new _BufferError("cannot read a buffer without MAP_READ usage");
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {number} offset
|
|
||||||
* @param {number} size
|
|
||||||
*/
|
|
||||||
static outOfBounds(offset, size) {
|
static outOfBounds(offset, size) {
|
||||||
return new _BufferError(`buffer offset/size (${offset}/${size}) exceeds buffer dimensions`);
|
return new _BufferError(`buffer offset/size (${offset}/${size}) exceeds buffer dimensions`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var MaterialError = class extends WebGPUObjectError {
|
var MaterialError = class extends WebGPUObjectError {
|
||||||
/**
|
constructor(message, options2) {
|
||||||
* @param {string} message
|
super(`MaterialError: ${message}`, options2);
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`MaterialError: ${message}`, options);
|
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {Error} cause
|
|
||||||
*/
|
|
||||||
static from(cause) {
|
static from(cause) {
|
||||||
return new BufferError("could not create material", { cause });
|
return new BufferError("could not create material", { cause });
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {string} shaderType
|
|
||||||
*/
|
|
||||||
static missingShader(shaderType) {
|
static missingShader(shaderType) {
|
||||||
return new BufferError(`missing ${shaderType} shader`);
|
return new BufferError(`missing ${shaderType} shader`);
|
||||||
}
|
}
|
||||||
|
@ -166,82 +136,53 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/utils.js
|
// src/utils/index.ts
|
||||||
var uppercase = (s2) => (
|
var uppercase = (s2) => s2.toUpperCase();
|
||||||
/** @type {Uppercase<T>} */
|
var split = (delim, s2) => s2.split(delim);
|
||||||
s2.toUpperCase()
|
var fromKebab = (s2) => split("-", s2);
|
||||||
|
var toPascal = (xs) => uppercase(xs[0]) + xs.slice(1);
|
||||||
|
var pascal = (xs) => xs.map(toPascal).join("");
|
||||||
|
var Enum = (...values) => Object.freeze(
|
||||||
|
values.reduce((acc, x2) => {
|
||||||
|
const key = pascal(fromKebab(x2)).toString();
|
||||||
|
acc[key] = x2;
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
);
|
);
|
||||||
var fromKebab = (s2) => (
|
var FlagEnum = (...values) => Object.freeze(
|
||||||
/** @type {Split<T, '-'>} */
|
values.reduce((acc, x2, i2) => {
|
||||||
s2.split("-")
|
const key = pascal(fromKebab(x2)).toString();
|
||||||
);
|
acc[key] = 1 << i2;
|
||||||
var toPascal = (xs) => (
|
return acc;
|
||||||
/** @type {PascalCase<T>} */
|
}, {})
|
||||||
uppercase(xs[0]) + xs.slice(1)
|
|
||||||
);
|
|
||||||
var pascal = (xs) => (
|
|
||||||
/** @type {PascalCase<T>} */
|
|
||||||
xs.map(toPascal).join("")
|
|
||||||
);
|
|
||||||
var Enum = (...values) => (
|
|
||||||
/** @type {Readonly<{ [K in T as PascalCase<K>]: K }>} */
|
|
||||||
Object.freeze(
|
|
||||||
values.reduce((acc, x2) => {
|
|
||||||
const key = pascal(fromKebab(x2)).toString();
|
|
||||||
acc[key] = x2;
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
var FlagEnum = (...values) => (
|
|
||||||
/** @type {never} */
|
|
||||||
Object.freeze(
|
|
||||||
values.reduce((acc, x2, i2) => {
|
|
||||||
const key = pascal(fromKebab(x2)).toString();
|
|
||||||
acc[key] = 1 << i2;
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
var EventEmitter = class {
|
var EventEmitter = class {
|
||||||
_listeners = {};
|
constructor() {
|
||||||
/**
|
this._listeners = {};
|
||||||
* @param {PropertyKey} event
|
}
|
||||||
* @param {Listener} callback
|
|
||||||
*/
|
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
this._listeners[event] = this._listeners[event] || [];
|
this._listeners[event] = this._listeners[event] || [];
|
||||||
this._listeners[event].push(callback);
|
this._listeners[event].push(callback);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {PropertyKey} event
|
|
||||||
* @param {...any} args
|
|
||||||
*/
|
|
||||||
emit(event, ...args) {
|
emit(event, ...args) {
|
||||||
const listeners = this._listeners[event];
|
const listeners = this._listeners[event];
|
||||||
if (listeners) {
|
if (listeners) {
|
||||||
listeners.forEach(
|
listeners.forEach(
|
||||||
/** @param {Listener} cb */
|
|
||||||
(cb) => cb(...args)
|
(cb) => cb(...args)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {PropertyKey} event
|
|
||||||
* @param {Listener} callback
|
|
||||||
*/
|
|
||||||
off(event, callback) {
|
off(event, callback) {
|
||||||
const listeners = this._listeners[event];
|
const listeners = this._listeners[event];
|
||||||
if (listeners) {
|
if (listeners) {
|
||||||
this._listeners[event] = listeners.filter(
|
this._listeners[event] = listeners.filter(
|
||||||
/** @param {Listener} cb */
|
|
||||||
(cb) => cb !== callback
|
(cb) => cb !== callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/utils/internal-enums.js
|
// src/utils/internal-enums.ts
|
||||||
var ShaderStage = FlagEnum(
|
var ShaderStage = FlagEnum(
|
||||||
"vertex",
|
"vertex",
|
||||||
"fragment",
|
"fragment",
|
||||||
|
@ -450,23 +391,19 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/utils/events.js
|
// src/utils/events.ts
|
||||||
var GraphicsDeviceInitialized = class {
|
var GraphicsDeviceInitialized = class {
|
||||||
static EventName = "graphics-device:initialized";
|
static {
|
||||||
graphicsDevice;
|
this.EventName = "graphics-device:initialized";
|
||||||
/**
|
}
|
||||||
* @param {GraphicsDevice} graphicsDevice
|
|
||||||
*/
|
|
||||||
constructor(graphicsDevice) {
|
constructor(graphicsDevice) {
|
||||||
this.graphicsDevice = graphicsDevice;
|
this.graphicsDevice = graphicsDevice;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var GraphicsDeviceLost = class {
|
var GraphicsDeviceLost = class {
|
||||||
static EventName = "graphics-device:device-lost";
|
static {
|
||||||
info;
|
this.EventName = "graphics-device:device-lost";
|
||||||
/**
|
}
|
||||||
* @param {GPUDeviceLostInfo} info
|
|
||||||
*/
|
|
||||||
constructor(info) {
|
constructor(info) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
}
|
}
|
||||||
|
@ -5752,7 +5689,7 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/utils/bindings.js
|
// src/utils/bindings.ts
|
||||||
var BindingMap = class extends Map {
|
var BindingMap = class extends Map {
|
||||||
};
|
};
|
||||||
var GroupBindingMap = class extends Map {
|
var GroupBindingMap = class extends Map {
|
||||||
|
@ -6079,7 +6016,7 @@
|
||||||
"instance"
|
"instance"
|
||||||
);
|
);
|
||||||
|
|
||||||
// src/utils/wgsl-to-wgpu.js
|
// src/utils/wgsl-to-wgpu.ts
|
||||||
var parseTextureType = (typeName) => {
|
var parseTextureType = (typeName) => {
|
||||||
const chevronIndex = typeName.indexOf("<");
|
const chevronIndex = typeName.indexOf("<");
|
||||||
const type = typeName.slice(0, chevronIndex);
|
const type = typeName.slice(0, chevronIndex);
|
||||||
|
@ -6227,6 +6164,20 @@
|
||||||
return "2d";
|
return "2d";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var textureToImageDimension = (dimension) => {
|
||||||
|
switch (dimension) {
|
||||||
|
case "1d":
|
||||||
|
return "1d";
|
||||||
|
case "2d":
|
||||||
|
case "2d-array":
|
||||||
|
case "cube-array":
|
||||||
|
return "2d";
|
||||||
|
case "3d":
|
||||||
|
return "3d";
|
||||||
|
default:
|
||||||
|
return "2d";
|
||||||
|
}
|
||||||
|
};
|
||||||
var wgslToWgpuFormat = (format) => {
|
var wgslToWgpuFormat = (format) => {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case "f32":
|
case "f32":
|
||||||
|
@ -6799,8 +6750,31 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/utils/bitflags.ts
|
||||||
|
var BitFlags = class _BitFlags {
|
||||||
|
get flags() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
constructor(value) {
|
||||||
|
this._value = value;
|
||||||
|
}
|
||||||
|
static has(a2, b2) {
|
||||||
|
return (a2 & b2) === b2;
|
||||||
|
}
|
||||||
|
static add(a2, b2) {
|
||||||
|
return a2 | b2;
|
||||||
|
}
|
||||||
|
has(b2) {
|
||||||
|
return _BitFlags.has(this._value, b2);
|
||||||
|
}
|
||||||
|
add(b2) {
|
||||||
|
return _BitFlags.add(this._value, b2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/resources/texture.js
|
// src/resources/texture.js
|
||||||
var Texture = class _Texture {
|
var Texture = class _Texture {
|
||||||
|
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
|
||||||
_device;
|
_device;
|
||||||
_handle;
|
_handle;
|
||||||
/** @type {GPUTextureView | undefined} */
|
/** @type {GPUTextureView | undefined} */
|
||||||
|
@ -6857,6 +6831,95 @@
|
||||||
throw WebGPUObjectError.from(err, _Texture);
|
throw WebGPUObjectError.from(err, _Texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static _generateMipLevels(size) {
|
||||||
|
const max = Math.max.apply(void 0, size);
|
||||||
|
return 1 + Math.log2(max) | 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {string | URL} url
|
||||||
|
* @param {GPUTextureDescriptor} desciptor
|
||||||
|
*/
|
||||||
|
static async fromUrl(device, url, descriptor) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch remote resource: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const usage = options.usage || _Texture._defaultUsage;
|
||||||
|
const dimension = descriptor.dimension ? textureToImageDimension(descriptor.dimension) : "2d";
|
||||||
|
const blob = await response.blob();
|
||||||
|
const bitmap = await createImageBitmap(blob);
|
||||||
|
const size = [bitmap.width, bitmap.height];
|
||||||
|
const desc = {
|
||||||
|
usage,
|
||||||
|
dimension,
|
||||||
|
size,
|
||||||
|
format: descriptor.format || "rgba8unorm",
|
||||||
|
mipLevelCount: descriptor.mipLevelCount || _Texture._generateMipCount(...size),
|
||||||
|
...descriptor
|
||||||
|
};
|
||||||
|
const texture = _Texture.create(device, desc);
|
||||||
|
texture.upload(bitmap);
|
||||||
|
return texture;
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, _Texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {GPUExtent3DStrict} size
|
||||||
|
* @param {GPUTextureFormat} format
|
||||||
|
* @param {GPUTextureDescriptor} descriptor
|
||||||
|
*/
|
||||||
|
static createRenderTarget(device, size, format, descriptor) {
|
||||||
|
const usage = descriptor.usage || _Texture._defaultUsage;
|
||||||
|
return _Texture.create(device, {
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
usage,
|
||||||
|
...descriptor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @typedef UploadTextureInfo
|
||||||
|
* @property {GPUTexelCopyTextureInfo} destination
|
||||||
|
* @property {GPUTexelCopyBufferLayout} dataLayout
|
||||||
|
* @property {GPUExtent3DStrict} size
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {GPUAllowSharedBufferSource} source
|
||||||
|
* @param {UploadTextureInfo} [options={}]
|
||||||
|
*/
|
||||||
|
upload(source, options2 = {}) {
|
||||||
|
const mipLevel = options2.destination.mipLevel || 0;
|
||||||
|
const size = options2.size || [
|
||||||
|
Math.max(1, this.width >> mipLevel),
|
||||||
|
Math.max(1, this.height >> mipLevel),
|
||||||
|
this.depthOrArrayLayers
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
this._device.queue.writeTexture(
|
||||||
|
{ ...options2.destination, texture: this._handle },
|
||||||
|
source,
|
||||||
|
options2.dataLayout,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, _Texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {GPUCommandEncoder} commandEncoder
|
||||||
|
*/
|
||||||
|
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 i2 = 1; i2 < this.mipLevelCount; ++i2) {
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param {GPUTextureViewDescriptor} [descriptor]
|
* @param {GPUTextureViewDescriptor} [descriptor]
|
||||||
* @throws {TextureError}
|
* @throws {TextureError}
|
||||||
|
@ -6879,7 +6942,7 @@
|
||||||
return this.createDefaultView();
|
return this.createDefaultView();
|
||||||
}
|
}
|
||||||
toBindingResource() {
|
toBindingResource() {
|
||||||
return this._handle;
|
return this.getView();
|
||||||
}
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
this._handle?.destroy();
|
this._handle?.destroy();
|
||||||
|
@ -7067,21 +7130,21 @@
|
||||||
/**
|
/**
|
||||||
* @param {GPURequestAdapterOptions} [options]
|
* @param {GPURequestAdapterOptions} [options]
|
||||||
*/
|
*/
|
||||||
withAdapter(options) {
|
withAdapter(options2) {
|
||||||
if (!this.isSupported()) {
|
if (!this.isSupported()) {
|
||||||
throw WebGPUError.unsupported();
|
throw WebGPUError.unsupported();
|
||||||
}
|
}
|
||||||
this._adapter_options = options;
|
this._adapter_options = options2;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {GPUDeviceDescriptor} [options]
|
* @param {GPUDeviceDescriptor} [options]
|
||||||
*/
|
*/
|
||||||
withDevice(options) {
|
withDevice(options2) {
|
||||||
if (!this.isSupported()) {
|
if (!this.isSupported()) {
|
||||||
throw WebGPUError.unsupported();
|
throw WebGPUError.unsupported();
|
||||||
}
|
}
|
||||||
this._device_descriptor = options;
|
this._device_descriptor = options2;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
async build() {
|
async build() {
|
||||||
|
@ -7389,23 +7452,23 @@
|
||||||
* @param {boolean} [options.generateMipmaps=false]
|
* @param {boolean} [options.generateMipmaps=false]
|
||||||
* @param {boolean} [options.flipY=false]
|
* @param {boolean} [options.flipY=false]
|
||||||
*/
|
*/
|
||||||
createTextureFromBitmap(bitmap, options) {
|
createTextureFromBitmap(bitmap, options2) {
|
||||||
if (!this._isInitialized) {
|
if (!this._isInitialized) {
|
||||||
throw GraphicsDeviceError.uninitialized();
|
throw GraphicsDeviceError.uninitialized();
|
||||||
}
|
}
|
||||||
if (!bitmap) {
|
if (!bitmap) {
|
||||||
throw new TypeError("Provided bitmap is null.");
|
throw new TypeError("Provided bitmap is null.");
|
||||||
}
|
}
|
||||||
const usage = (options.usage ?? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT) | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
|
const usage = (options2.usage ?? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT) | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
|
||||||
const mipLevelCount = 1;
|
const mipLevelCount = 1;
|
||||||
const descriptor = {
|
const descriptor = {
|
||||||
label: options.label,
|
label: options2.label,
|
||||||
size: {
|
size: {
|
||||||
width: bitmap.width,
|
width: bitmap.width,
|
||||||
height: bitmap.height,
|
height: bitmap.height,
|
||||||
depthOrArrayLayers: 1
|
depthOrArrayLayers: 1
|
||||||
},
|
},
|
||||||
format: options.format ?? "rgba8unorm",
|
format: options2.format ?? "rgba8unorm",
|
||||||
usage,
|
usage,
|
||||||
dimension: "2d",
|
dimension: "2d",
|
||||||
mipLevelCount,
|
mipLevelCount,
|
||||||
|
@ -7414,7 +7477,7 @@
|
||||||
try {
|
try {
|
||||||
const texture = this.device.createTexture(descriptor);
|
const texture = this.device.createTexture(descriptor);
|
||||||
this.queue.copyExternalImageToTexture(
|
this.queue.copyExternalImageToTexture(
|
||||||
{ source: bitmap, flipY: options.flipY ?? false },
|
{ source: bitmap, flipY: options2.flipY ?? false },
|
||||||
{ texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
|
{ texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
|
||||||
descriptor.size
|
descriptor.size
|
||||||
);
|
);
|
||||||
|
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -13,7 +13,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@webgpu/types": "^0.1.60",
|
"@webgpu/types": "^0.1.60",
|
||||||
"esbuild": "^0.25.2"
|
"esbuild": "^0.25.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
@ -489,6 +490,20 @@
|
||||||
"@esbuild/win32-x64": "0.25.2"
|
"@esbuild/win32-x64": "0.25.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wgsl_reflect": {
|
"node_modules/wgsl_reflect": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.0.tgz",
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@webgpu/types": "^0.1.60",
|
"@webgpu/types": "^0.1.60",
|
||||||
"esbuild": "^0.25.2"
|
"esbuild": "^0.25.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wgsl_reflect": "^1.2.0"
|
"wgsl_reflect": "^1.2.0"
|
||||||
|
|
|
@ -5,32 +5,26 @@ import { SwapChain } from './swap-chain.js'
|
||||||
export class CommandRecorder {
|
export class CommandRecorder {
|
||||||
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 }
|
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 }
|
||||||
|
|
||||||
_device
|
_device: GPUDevice
|
||||||
_swapChain
|
_swapChain: SwapChain
|
||||||
_label
|
|
||||||
|
|
||||||
_encoder
|
_encoder: GPUCommandEncoder
|
||||||
|
|
||||||
/** @type {GPURenderPassEncoder | undefined} */
|
_passEncoder?: GPURenderPassEncoder
|
||||||
_passEncoder
|
|
||||||
|
|
||||||
/**
|
get label() {
|
||||||
* @param {GPUDevice} device
|
return this._encoder.label
|
||||||
* @param {SwapChain} swapChain
|
}
|
||||||
* @param {string} [label]
|
|
||||||
*/
|
|
||||||
constructor(device, swapChain, label) {
|
constructor(device: GPUDevice, swapChain: SwapChain, label: string) {
|
||||||
this._device = device
|
this._device = device
|
||||||
this._swapChain = swapChain
|
this._swapChain = swapChain
|
||||||
this._label = label
|
|
||||||
|
|
||||||
this._encoder = device.createCommandEncoder({ label })
|
this._encoder = device.createCommandEncoder({ label })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_defaultColorAttachment(): [GPURenderPassColorAttachment] {
|
||||||
* @returns {[GPURenderPassColorAttachment]}
|
|
||||||
*/
|
|
||||||
_defaultColorAttachment() {
|
|
||||||
const view = this._swapChain.getCurrentTextureView()
|
const view = this._swapChain.getCurrentTextureView()
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
|
@ -40,12 +34,8 @@ export class CommandRecorder {
|
||||||
storeOp: StoreOp.Store
|
storeOp: StoreOp.Store
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {GPURenderPassColorAttachment[]} [colorAttachments]
|
beginRenderPass(colorAttachments: GPURenderPassColorAttachment[], depthStencilAttachment: GPURenderPassDepthStencilAttachment): GPURenderPassEncoder {
|
||||||
* @param {GPURenderPassDepthStencilAttachment} [depthStencilAttachment]
|
|
||||||
* @returns {GPURenderPassEncoder}
|
|
||||||
*/
|
|
||||||
beginRenderPass(colorAttachments, depthStencilAttachment) {
|
|
||||||
if (this._passEncoder) {
|
if (this._passEncoder) {
|
||||||
throw CommandRecorderError.activeRenderPass()
|
throw CommandRecorderError.activeRenderPass()
|
||||||
}
|
}
|
||||||
|
@ -53,7 +43,7 @@ export class CommandRecorder {
|
||||||
const attachments = colorAttachments || this._defaultColorAttachment()
|
const attachments = colorAttachments || this._defaultColorAttachment()
|
||||||
|
|
||||||
const descriptor = {
|
const descriptor = {
|
||||||
label: this._label || 'RenderPass',
|
label: this.label || 'RenderPass',
|
||||||
colorAttachments: attachments,
|
colorAttachments: attachments,
|
||||||
depthStencilAttachment
|
depthStencilAttachment
|
||||||
}
|
}
|
|
@ -2,57 +2,54 @@ import { SwapChain } from './swap-chain.js'
|
||||||
import { Buffer, UniformBuffer } from '../resources/buffer.js'
|
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.js'
|
import { EventEmitter } from '../utils/index.js'
|
||||||
import { ShaderModule } from '../resources/shader-module.js'
|
import { ShaderModule, ShaderPairStateDescriptor } 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'
|
||||||
import { BindGroup } from '../resources/bind-group.js'
|
import { BindGroup } from '../resources/bind-group.js'
|
||||||
import { Texture } from '../resources/texture.js'
|
import { Texture } from '../resources/texture.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef BaseBindGroupEntry
|
|
||||||
* @property {GPUIndex32} binding
|
|
||||||
* @property {BindingResource} resource
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
offset?: number
|
|
||||||
size?: number
|
|
||||||
* }} BufferBindingResource
|
|
||||||
*
|
|
||||||
* @typedef {{ textureView?: GPUTextureView }} TextureBindingResource
|
|
||||||
*
|
|
||||||
* @typedef {Buffer | Texture | Sampler} BindingResource
|
|
||||||
*
|
|
||||||
* @typedef {
|
|
||||||
(BufferBindingResource | TextureBindingResource) &
|
|
||||||
BaseBindGroupEntry
|
|
||||||
* } BindGroupEntry
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Sampler } from '../resources/sampler.js'
|
import { Sampler } from '../resources/sampler.js'
|
||||||
import { ResourceType } from '../utils/internal-enums.js'
|
import { ResourceType } from '../utils/internal-enums.js'
|
||||||
import { Material } from '../resources/material.js'
|
import { Material } from '../resources/material.js'
|
||||||
|
|
||||||
|
type BindingResource = Buffer | Texture | Sampler
|
||||||
|
|
||||||
|
interface TextureBindingResource {
|
||||||
|
textureView?: GPUTextureView
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BufferBindingResource {
|
||||||
|
offset?: number
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseBindGroupEntry {
|
||||||
|
binding: GPUIndex32
|
||||||
|
resource: BindingResource
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BindGroupEntry = (BufferBindingResource | TextureBindingResource) & BaseBindGroupEntry
|
||||||
|
|
||||||
|
interface BitmapTextureOptions {
|
||||||
|
label?: string
|
||||||
|
format: GPUTextureFormat
|
||||||
|
usage: GPUTextureUsageFlags
|
||||||
|
generateMipmaps: boolean
|
||||||
|
flipY: boolean
|
||||||
|
}
|
||||||
class GraphicsDeviceBuilder {
|
class GraphicsDeviceBuilder {
|
||||||
_canvas
|
_canvas: HTMLCanvasElement
|
||||||
|
|
||||||
|
_adapterOptions: GPURequestAdapterOptions
|
||||||
|
_deviceDescriptor: GPUDeviceDescriptor
|
||||||
|
|
||||||
get canvas() {
|
get canvas() {
|
||||||
return this._canvas
|
return this._canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {GPURequestAdapterOptions} */
|
|
||||||
_adapter_options
|
|
||||||
|
|
||||||
/** @type {GPUDeviceDescriptor} */
|
constructor(canvasElement: HTMLCanvasElement) {
|
||||||
_device_descriptor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLCanvasElement} [canvasElement]
|
|
||||||
*/
|
|
||||||
constructor(canvasElement) {
|
|
||||||
this._canvas = canvasElement
|
this._canvas = canvasElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,35 +57,26 @@ class GraphicsDeviceBuilder {
|
||||||
return navigator.gpu
|
return navigator.gpu
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
withCanvas(canvasElement: HTMLCanvasElement) {
|
||||||
* @param {HTMLCanvasElement} canvasElement
|
|
||||||
*/
|
|
||||||
withCanvas(canvasElement) {
|
|
||||||
this._canvas = canvasElement
|
this._canvas = canvasElement
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
withAdapter(options: GPURequestAdapterOptions) {
|
||||||
* @param {GPURequestAdapterOptions} [options]
|
|
||||||
*/
|
|
||||||
withAdapter(options) {
|
|
||||||
if (!this.isSupported()) {
|
if (!this.isSupported()) {
|
||||||
throw WebGPUError.unsupported()
|
throw WebGPUError.unsupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
this._adapter_options = options
|
this._adapterOptions = options
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
withDevice(options: GPUDeviceDescriptor) {
|
||||||
* @param {GPUDeviceDescriptor} [options]
|
|
||||||
*/
|
|
||||||
withDevice(options) {
|
|
||||||
if (!this.isSupported()) {
|
if (!this.isSupported()) {
|
||||||
throw WebGPUError.unsupported()
|
throw WebGPUError.unsupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
this._device_descriptor = options
|
this._deviceDescriptor = options
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,39 +84,28 @@ class GraphicsDeviceBuilder {
|
||||||
return new GraphicsDevice(
|
return new GraphicsDevice(
|
||||||
this._canvas,
|
this._canvas,
|
||||||
new DeviceHandler(
|
new DeviceHandler(
|
||||||
this._adapter_options,
|
this._adapterOptions,
|
||||||
this._device_descriptor
|
this._deviceDescriptor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceHandler {
|
class DeviceHandler {
|
||||||
/** @type {GPURequestAdapterOptions} */
|
_adapterOptions: GPURequestAdapterOptions
|
||||||
_adapterOptions
|
_adapter: GPUAdapter
|
||||||
|
_deviceDescriptor: GPUDeviceDescriptor
|
||||||
/** @type {GPUAdapter} */
|
_device: GPUDevice
|
||||||
_adapter
|
|
||||||
|
|
||||||
get adapter() {
|
get adapter() {
|
||||||
return this._adapter
|
return this._adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {GPUDeviceDescriptor} */
|
|
||||||
_deviceDescriptor
|
|
||||||
|
|
||||||
/** @type {GPUDevice} */
|
|
||||||
_device
|
|
||||||
|
|
||||||
get device() {
|
get device() {
|
||||||
return this._device
|
return this._device
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(adapterOptions: GPURequestAdapterOptions, deviceDescriptor: GPUDeviceDescriptor) {
|
||||||
* @param {GPURequestAdapterOptions} adapterOptions
|
|
||||||
* @param {GPUDeviceDescriptor} deviceDescriptor
|
|
||||||
*/
|
|
||||||
constructor(adapterOptions, deviceDescriptor) {
|
|
||||||
this._adapterOptions = adapterOptions
|
this._adapterOptions = adapterOptions
|
||||||
this._deviceDescriptor = deviceDescriptor
|
this._deviceDescriptor = deviceDescriptor
|
||||||
}
|
}
|
||||||
|
@ -149,14 +126,10 @@ class DeviceHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphicsDevice extends EventEmitter {
|
export class GraphicsDevice extends EventEmitter {
|
||||||
_canvas
|
_canvas: HTMLCanvasElement
|
||||||
_deviceHandler
|
_deviceHandler: DeviceHandler
|
||||||
|
_swapChain: SwapChain
|
||||||
/** @type {SwapChain} */
|
_queue: GPUQueue
|
||||||
_swapChain
|
|
||||||
|
|
||||||
/** @type {GPUQueue} */
|
|
||||||
_queue
|
|
||||||
|
|
||||||
_isInitialized = false
|
_isInitialized = false
|
||||||
|
|
||||||
|
@ -180,22 +153,14 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
return this._swapChain
|
return this._swapChain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(canvas: HTMLCanvasElement, deviceHandler: DeviceHandler) {
|
||||||
/**
|
|
||||||
* @param {HTMLCanvasElement} canvas
|
|
||||||
* @param {DeviceHandler} deviceHandler
|
|
||||||
*/
|
|
||||||
constructor(canvas, deviceHandler) {
|
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this._canvas = canvas
|
this._canvas = canvas
|
||||||
this._deviceHandler = deviceHandler
|
this._deviceHandler = deviceHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static build(canvas: HTMLCanvasElement) {
|
||||||
* @param {HTMLCanvasElement} [canvas]
|
|
||||||
*/
|
|
||||||
static build(canvas) {
|
|
||||||
return new GraphicsDeviceBuilder(canvas)
|
return new GraphicsDeviceBuilder(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +169,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
|
|
||||||
this._swapChain = new SwapChain(
|
this._swapChain = new SwapChain(
|
||||||
this._canvas,
|
this._canvas,
|
||||||
this._deviceHandler.device
|
this._deviceHandler.device,
|
||||||
)
|
)
|
||||||
|
|
||||||
this._swapChain.configure()
|
this._swapChain.configure()
|
||||||
|
@ -230,19 +195,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createBuffer(descriptor: GPUBufferDescriptor, data: ArrayBufferView | ArrayBuffer): Buffer {
|
||||||
* @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() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -258,12 +211,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createUniformBuffer(size: number, data: ArrayBufferView | ArrayBuffer, label: string) {
|
||||||
* @param {number} size
|
|
||||||
* @param {ArrayBufferView | ArrayBuffer} [data]
|
|
||||||
* @param {string} [label]
|
|
||||||
*/
|
|
||||||
createUniformBuffer(size, data, label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
const buffer = UniformBuffer.create(this.device, {
|
const buffer = UniformBuffer.create(this.device, {
|
||||||
|
@ -278,13 +226,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createShaderModule(code: string, label: string): ShaderModule {
|
||||||
* Creates a shader module from WGSL code.
|
|
||||||
* @param {string} code
|
|
||||||
* @param {string} [label]
|
|
||||||
* @returns {ShaderModule}
|
|
||||||
*/
|
|
||||||
createShaderModule(code, label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -294,12 +236,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createRenderPipeline(descriptor: GPURenderPipelineDescriptor): RenderPipeline {
|
||||||
* Creates a render pipeline.
|
|
||||||
* @param {GPURenderPipelineDescriptor} descriptor - Raw render pipeline descriptor.
|
|
||||||
* @returns {RenderPipeline}
|
|
||||||
*/
|
|
||||||
createRenderPipeline(descriptor) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -310,10 +247,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createMaterial(shaders: ShaderPairStateDescriptor) {
|
||||||
* @param {import('../resources/material.js').ShaderPairDescriptor} shaders
|
|
||||||
*/
|
|
||||||
createMaterial(shaders) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -323,22 +257,13 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createCommandRecorder(label?: string): CommandRecorder {
|
||||||
* Creates a CommandRecorder to begin recording GPU commands.
|
|
||||||
* @param {string} [label]
|
|
||||||
* @returns {CommandRecorder}
|
|
||||||
*/
|
|
||||||
createCommandRecorder(label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
return new CommandRecorder(this.device, this._swapChain, label)
|
return new CommandRecorder(this.device, this._swapChain, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createBindGroupLayout(entries: GPUBindGroupLayoutEntry[], label: string) {
|
||||||
* @param {GPUBindGroupLayoutEntry[]} entries
|
|
||||||
* @param {string} [label]
|
|
||||||
*/
|
|
||||||
createBindGroupLayout(entries, label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
return BindGroupLayout.create(this.device, {
|
return BindGroupLayout.create(this.device, {
|
||||||
|
@ -347,11 +272,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getBindingResource(binding: BindGroupEntry): GPUBindingResource {
|
||||||
* @param {BindGroupEntry} binding
|
|
||||||
* @returns {GPUBindingResource}
|
|
||||||
*/
|
|
||||||
_getBindingResource(binding) {
|
|
||||||
const resource = binding.resource
|
const resource = binding.resource
|
||||||
switch (resource.resourceType) {
|
switch (resource.resourceType) {
|
||||||
case ResourceType.Sampler:
|
case ResourceType.Sampler:
|
||||||
|
@ -382,12 +303,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createBindGroup(layout: BindGroupLayout, bindings: BindGroupEntry[], label?: string) {
|
||||||
* @param {BindGroupLayout} layout
|
|
||||||
* @param {BindGroupEntry[]} bindings
|
|
||||||
* @param {string} [label]
|
|
||||||
*/
|
|
||||||
createBindGroup(layout, bindings, label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,11 +319,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createPipelineLayout(layouts: Array<BindGroupLayout>, label?: string) {
|
||||||
* @param {Array<BindGroupLayout>} layouts
|
|
||||||
* @param {string} [label]
|
|
||||||
*/
|
|
||||||
createPipelineLayout(layouts, label) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
const bindGroupLayouts = layouts.map(layout => layout.handle)
|
const bindGroupLayouts = layouts.map(layout => layout.handle)
|
||||||
|
@ -418,19 +330,13 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createSampler(descriptor?: GPUSamplerDescriptor) {
|
||||||
* @param {GPUSamplerDescriptor} [descriptor]
|
|
||||||
*/
|
|
||||||
createSampler(descriptor) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
return Sampler.create(this.device, descriptor)
|
return Sampler.create(this.device, descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createTexture(descriptor: GPUTextureDescriptor) {
|
||||||
* @param {GPUTextureDescriptor} descriptor
|
|
||||||
*/
|
|
||||||
createTexture(descriptor) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -440,16 +346,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createTextureFromBitmap(bitmap: ImageBitmap, options: BitmapTextureOptions) {
|
||||||
* @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, options) {
|
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
if (!bitmap) { throw new TypeError('Provided bitmap is null.') }
|
if (!bitmap) { throw new TypeError('Provided bitmap is null.') }
|
||||||
|
@ -458,8 +355,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
|
|
||||||
const mipLevelCount = 1
|
const mipLevelCount = 1
|
||||||
|
|
||||||
/** @type {GPUTextureDescriptor} */
|
const descriptor: GPUTextureDescriptor = {
|
||||||
const descriptor = {
|
|
||||||
label: options.label,
|
label: options.label,
|
||||||
size: {
|
size: {
|
||||||
width: bitmap.width,
|
width: bitmap.width,
|
||||||
|
@ -487,11 +383,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
submitCommands(commandBuffers: GPUCommandBuffer[]) {
|
||||||
* Submits an array of command buffers to the GPU queue.
|
|
||||||
* @param {GPUCommandBuffer[]} commandBuffers
|
|
||||||
*/
|
|
||||||
submitCommands(commandBuffers) {
|
|
||||||
if (!this._isInitialized || !commandBuffers || commandBuffers.length === 0) return
|
if (!this._isInitialized || !commandBuffers || commandBuffers.length === 0) return
|
||||||
|
|
||||||
this.queue.submit(commandBuffers)
|
this.queue.submit(commandBuffers)
|
|
@ -1,21 +1,17 @@
|
||||||
import { WebGPUError } from '../utils/errors.js'
|
import { WebGPUError } from '../utils/errors.js'
|
||||||
|
import { PositiveInteger } from '../utils/index.js'
|
||||||
|
|
||||||
|
type SwapChainConfiguration = Omit<GPUCanvasConfiguration, 'device' | 'format'>
|
||||||
|
|
||||||
/** @import { PositiveInteger } from '../utils.js' */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Omit<GPUCanvasConfiguration, 'device' | 'format'>} SwapChainConfiguration
|
|
||||||
*/
|
|
||||||
export class SwapChain {
|
export class SwapChain {
|
||||||
_canvas
|
_canvas: HTMLCanvasElement
|
||||||
_device
|
_device: GPUDevice
|
||||||
_context
|
_context: GPUCanvasContext
|
||||||
_format
|
_format: GPUTextureFormat
|
||||||
_width
|
_width: number
|
||||||
_height
|
_height: number
|
||||||
|
|
||||||
/** @type {SwapChainConfiguration} */
|
_configuration: SwapChainConfiguration
|
||||||
_configuration
|
|
||||||
|
|
||||||
get context() {
|
get context() {
|
||||||
return this._context
|
return this._context
|
||||||
|
@ -33,15 +29,7 @@ export class SwapChain {
|
||||||
return this._height
|
return this._height
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(canvas: HTMLCanvasElement, device: GPUDevice, context?: GPUCanvasContext) {
|
||||||
* @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._canvas = canvas
|
||||||
this._device = device
|
this._device = device
|
||||||
|
|
||||||
|
@ -56,10 +44,7 @@ export class SwapChain {
|
||||||
this._height = canvas.height
|
this._height = canvas.height
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
configure(configuration?: SwapChainConfiguration) {
|
||||||
* @param {SwapChainConfiguration} [configuration]
|
|
||||||
*/
|
|
||||||
configure(configuration) {
|
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
this._configuration = configuration
|
this._configuration = configuration
|
||||||
}
|
}
|
||||||
|
@ -79,12 +64,7 @@ export class SwapChain {
|
||||||
return this._context.getCurrentTexture().createView()
|
return this._context.getCurrentTexture().createView()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
resize<const T extends number>(width: PositiveInteger<T>, height: PositiveInteger<T>) {
|
||||||
* @template {number} const T
|
|
||||||
* @param {PositiveInteger<T>} width
|
|
||||||
* @param {PositiveInteger<T>} height
|
|
||||||
*/
|
|
||||||
resize(width, height) {
|
|
||||||
if (width <= 0 || height <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Enum } from './utils.js'
|
import { Enum } from './utils/index.js'
|
||||||
|
|
||||||
|
|
||||||
export const AddressMode = Enum(
|
export const AddressMode = Enum(
|
|
@ -1,17 +1,17 @@
|
||||||
import { BufferError, WebGPUError } from '../utils/errors.js'
|
import { BufferError, WebGPUError } from '../utils/errors.js'
|
||||||
|
import { TypedArray, TypedArrayConstructor } from '../utils/index.js'
|
||||||
import { ResourceType } from '../utils/internal-enums.js'
|
import { ResourceType } from '../utils/internal-enums.js'
|
||||||
|
|
||||||
/** @import { TypedArray, TypedArrayConstructor } from '../utils.js' */
|
type SmallTypedArray = Exclude<TypedArray, BigInt64Array | BigUint64Array>
|
||||||
/** @import { BindGroupEntry, BindingResource, BufferBindingResource } from '../core/graphics-device.js' */
|
type SmallTypedArrayConstructor = Exclude<TypedArrayConstructor, BigInt64ArrayConstructor | BigUint64ArrayConstructor>
|
||||||
|
|
||||||
export class Buffer {
|
export class Buffer {
|
||||||
_device
|
_device: GPUDevice
|
||||||
_handle
|
_handle: GPUBuffer
|
||||||
|
|
||||||
_mapped = false
|
_mapped = false
|
||||||
|
|
||||||
/** @type {GPUBuffer} */
|
_defaultStagingBuffer: GPUBuffer
|
||||||
_defaultStagingBuffer
|
|
||||||
|
|
||||||
get handle() {
|
get handle() {
|
||||||
return this._handle
|
return this._handle
|
||||||
|
@ -29,20 +29,12 @@ export class Buffer {
|
||||||
return ResourceType.Buffer
|
return ResourceType.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(device: GPUDevice, texture: GPUBuffer) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUBuffer} texture
|
|
||||||
*/
|
|
||||||
constructor(device, texture) {
|
|
||||||
this._device = device
|
this._device = device
|
||||||
this._handle = texture
|
this._handle = texture
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static create(device: GPUDevice, descriptor: GPUBufferDescriptor) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUBufferDescriptor} descriptor
|
|
||||||
*/
|
|
||||||
static create(device, descriptor) {
|
|
||||||
try {
|
try {
|
||||||
return new Buffer(
|
return new Buffer(
|
||||||
device,
|
device,
|
||||||
|
@ -53,29 +45,18 @@ export class Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_createStagingOptions(size: number = this.size) {
|
||||||
* @param {number} [size]
|
|
||||||
*/
|
|
||||||
_createStagingOptions(size = this.size) {
|
|
||||||
return {
|
return {
|
||||||
size,
|
size,
|
||||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getStagingBuffer(size: number) {
|
||||||
* @param {number} [size]
|
|
||||||
*/
|
|
||||||
_getStagingBuffer(size) {
|
|
||||||
return this._device.createBuffer(this._createStagingOptions(size))
|
return this._device.createBuffer(this._createStagingOptions(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
write(data: ArrayBufferView | ArrayBuffer, offset: number = 0, dataOffset: number = 0) {
|
||||||
* @param {ArrayBufferView | ArrayBuffer} data
|
|
||||||
* @param {number} [offset=0]
|
|
||||||
* @param {number} [dataOffset=0]
|
|
||||||
*/
|
|
||||||
write(data, offset = 0, dataOffset = 0) {
|
|
||||||
if (!(this.usage & GPUBufferUsage.COPY_DST)) {
|
if (!(this.usage & GPUBufferUsage.COPY_DST)) {
|
||||||
console.warn('Buffer usage does not include COPY_DST. Buffer.write may fail.')
|
console.warn('Buffer usage does not include COPY_DST. Buffer.write may fail.')
|
||||||
}
|
}
|
||||||
|
@ -92,17 +73,8 @@ export class Buffer {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Exclude<TypedArray, BigInt64Array | BigUint64Array>} SmallTypedArray
|
|
||||||
* @typedef {Exclude<TypedArrayConstructor, BigInt64ArrayConstructor | BigUint64ArrayConstructor>} SmallTypedArrayConstructor
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
async read(out: SmallTypedArray | DataView | undefined, byteOffset: number = 0, byteSize: number = -1) {
|
||||||
* @param {SmallTypedArray | DataView | undefined} [out]
|
|
||||||
* @param {number} [byteOffset=0]
|
|
||||||
* @param {number} [byteSize]
|
|
||||||
*/
|
|
||||||
async read(out, byteOffset = 0, byteSize = -1) {
|
|
||||||
if (!this._device) {
|
if (!this._device) {
|
||||||
throw WebGPUError.deviceUnavailable()
|
throw WebGPUError.deviceUnavailable()
|
||||||
}
|
}
|
||||||
|
@ -124,15 +96,15 @@ export class Buffer {
|
||||||
if (out.byteLength < byteSize) { throw new RangeError(`Provided output buffer too small`) }
|
if (out.byteLength < byteSize) { throw new RangeError(`Provided output buffer too small`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
let result
|
let result: SmallTypedArray | ArrayBuffer | DataView<ArrayBufferLike>
|
||||||
let range
|
let range: ArrayBuffer
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.handle.mapAsync(GPUMapMode.READ, byteOffset, byteSize)
|
await this.handle.mapAsync(GPUMapMode.READ, byteOffset, byteSize)
|
||||||
range = this.handle.getMappedRange(byteOffset, byteSize)
|
range = this.handle.getMappedRange(byteOffset, byteSize)
|
||||||
|
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
const SourceView = /** @type {SmallTypedArrayConstructor} */ (out.constructor)
|
const SourceView = out.constructor as SmallTypedArrayConstructor
|
||||||
const bytesPerElement = SourceView.BYTES_PER_ELEMENT
|
const bytesPerElement = SourceView.BYTES_PER_ELEMENT
|
||||||
|
|
||||||
if (!bytesPerElement) {
|
if (!bytesPerElement) {
|
||||||
|
@ -173,12 +145,7 @@ export class Buffer {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
toBindingResource({ offset, size }: { offset?: number; size?: number } = {}) {
|
||||||
* @param {Object} [descriptor={}]
|
|
||||||
* @param {number} [descriptor.offset=0]
|
|
||||||
* @param {number} [descriptor.size]
|
|
||||||
*/
|
|
||||||
toBindingResource({ offset, size } = {}) {
|
|
||||||
return {
|
return {
|
||||||
buffer: this._handle,
|
buffer: this._handle,
|
||||||
offset: offset || 0,
|
offset: offset || 0,
|
||||||
|
@ -194,19 +161,11 @@ export class Buffer {
|
||||||
|
|
||||||
|
|
||||||
export class UniformBuffer extends Buffer {
|
export class UniformBuffer extends Buffer {
|
||||||
/**
|
constructor(device: GPUDevice, buffer: GPUBuffer) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUBuffer} buffer
|
|
||||||
*/
|
|
||||||
constructor(device, buffer) {
|
|
||||||
super(device, buffer)
|
super(device, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static create(device: GPUDevice, descriptor: Omit<GPUBufferDescriptor, 'usage'>) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {Omit<GPUBufferDescriptor, 'usage'>} descriptor
|
|
||||||
*/
|
|
||||||
static create(device, descriptor) {
|
|
||||||
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
||||||
return super.create(device, {
|
return super.create(device, {
|
||||||
usage,
|
usage,
|
0
src/resources/newFile.ts
Normal file
0
src/resources/newFile.ts
Normal file
|
@ -16,11 +16,10 @@ import { BufferBindingType } from '../enum.js'
|
||||||
/** @import { WGSLAccess, WGSLSamplerType } from '../utils/wgsl-to-wgpu.js' */
|
/** @import { WGSLAccess, WGSLSamplerType } from '../utils/wgsl-to-wgpu.js' */
|
||||||
|
|
||||||
export class ShaderModule {
|
export class ShaderModule {
|
||||||
_handle
|
_handle: GPUShaderModule
|
||||||
_code
|
_code: string
|
||||||
|
|
||||||
/** @type {WgslReflect | undefined} */
|
_reflection: WgslReflect | undefined
|
||||||
_reflection
|
|
||||||
|
|
||||||
get handle() {
|
get handle() {
|
||||||
return this._handle
|
return this._handle
|
||||||
|
@ -30,11 +29,7 @@ export class ShaderModule {
|
||||||
return this._handle.label
|
return this._handle.label
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(device: GPUDevice, descriptor: GPUShaderModuleDescriptor) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUShaderModuleDescriptor} descriptor
|
|
||||||
*/
|
|
||||||
constructor(device, descriptor) {
|
|
||||||
this._code = descriptor.code
|
this._code = descriptor.code
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -44,11 +39,7 @@ export class ShaderModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static create(device: GPUDevice, descriptor: GPUShaderModuleDescriptor) {
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUShaderModuleDescriptor} descriptor
|
|
||||||
*/
|
|
||||||
static create(device, descriptor) {
|
|
||||||
return new ShaderModule(device, descriptor)
|
return new ShaderModule(device, descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,49 +54,41 @@ export class ShaderModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface FragmentStateDescriptor {
|
||||||
* @typedef FragmentStateDescriptor
|
constants?: Record<string, GPUPipelineConstantValue>
|
||||||
* @property {Record<string, GPUPipelineConstantValue>} [constants={}]
|
targets?: GPUColorTargetState[]
|
||||||
* @property {GPUColorTargetState[]} [targets=[]]
|
}
|
||||||
*
|
|
||||||
* @typedef VertexStateDescriptor
|
export interface VertexStateDescriptor {
|
||||||
* @property {Record<string, GPUPipelineConstantValue>} [constants={}]
|
constants?: Record<string, GPUPipelineConstantValue>
|
||||||
* @property {GPUVertexBufferLayout[]} [buffers=[]]
|
buffers?: GPUVertexBufferLayout[]
|
||||||
*
|
}
|
||||||
* @typedef ShaderPairStateDescriptor
|
|
||||||
* @property {FragmentStateDescriptor} [fragment]
|
export interface ShaderPairStateDescriptor {
|
||||||
* @property {VertexStateDescriptor} vertex
|
fragment?: FragmentStateDescriptor
|
||||||
*
|
vertex: VertexStateDescriptor
|
||||||
*/
|
}
|
||||||
|
|
||||||
export class ReflectedShader {
|
export class ReflectedShader {
|
||||||
static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers']
|
static _reflectTypes = ['uniforms', 'storage', 'textures', 'samplers']
|
||||||
_module
|
_module: ShaderModule
|
||||||
|
|
||||||
get module() {
|
get module() {
|
||||||
return this._module
|
return this._module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
constructor(shader: ShaderModule) {
|
||||||
* @param {ShaderModule} shader
|
|
||||||
*/
|
|
||||||
constructor(shader) {
|
|
||||||
this._module = shader
|
this._module = shader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
findVariableInfo(name: string, group: number): VariableInfo | undefined {
|
||||||
* @param {string} name
|
|
||||||
* @param {number} group
|
|
||||||
* @returns {VariableInfo | undefined}
|
|
||||||
*/
|
|
||||||
findVariableInfo(name, group) {
|
|
||||||
const reflection = this.module.reflect()
|
const reflection = this.module.reflect()
|
||||||
for (const type of ReflectedShader._reflectTypes) {
|
for (const type of ReflectedShader._reflectTypes) {
|
||||||
const variables = reflection[type]
|
const variables = reflection[type]
|
||||||
if (variables) {
|
if (variables) {
|
||||||
const variable = variables.find(v => v.name === name && v.group === group)
|
const variable = variables.find((v: VariableInfo) => v.name === name && v.group === group)
|
||||||
if (variable) {
|
if (variable) {
|
||||||
return variable
|
return variable
|
||||||
}
|
}
|
||||||
|
@ -113,11 +96,7 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getEntrypoint(stageName: string): string | undefined {
|
||||||
* @param {string} stageName
|
|
||||||
* @returns {string | undefined}
|
|
||||||
*/
|
|
||||||
getEntrypoint(stageName) {
|
|
||||||
const entry = this.module.reflect().entry
|
const entry = this.module.reflect().entry
|
||||||
|
|
||||||
// TODO: determine how to correctly handle
|
// TODO: determine how to correctly handle
|
||||||
|
@ -126,10 +105,7 @@ export class ReflectedShader {
|
||||||
entry[stageName][0].name : undefined
|
entry[stageName][0].name : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getShaderStages(): GPUShaderStageFlags {
|
||||||
* @returns {GPUShaderStageFlags}
|
|
||||||
*/
|
|
||||||
getShaderStages() {
|
|
||||||
const entry = this._module.reflect().entry
|
const entry = this._module.reflect().entry
|
||||||
let stages = 0
|
let stages = 0
|
||||||
|
|
||||||
|
@ -145,19 +121,12 @@ export class ReflectedShader {
|
||||||
return stages
|
return stages
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
hasStage(stages: GPUShaderStageFlags) {
|
||||||
* @param {GPUShaderStageFlags} stages
|
|
||||||
*/
|
|
||||||
hasStage(stages) {
|
|
||||||
return this.getShaderStages()
|
return this.getShaderStages()
|
||||||
& stages
|
& stages
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getBindingsForStage(stages: GPUShaderStageFlags, out: GroupBindingMap = new GroupBindingMap()) {
|
||||||
* @param {GPUShaderStageFlags} stages
|
|
||||||
* @param {GroupBindingMap} [out=new GroupBindingMap()]
|
|
||||||
*/
|
|
||||||
getBindingsForStage(stages, out = new GroupBindingMap()) {
|
|
||||||
const groups = this._module.reflect().getBindGroups()
|
const groups = this._module.reflect().getBindGroups()
|
||||||
|
|
||||||
groups.forEach((bindings, groupIndex) => {
|
groups.forEach((bindings, groupIndex) => {
|
||||||
|
@ -179,19 +148,11 @@ export class ReflectedShader {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _sortKeyIndices(map: Map<any, any>): number[] {
|
||||||
* @param {Map<any, any>} map
|
|
||||||
* @returns {number[]}
|
|
||||||
*/
|
|
||||||
static _sortKeyIndices(map) {
|
|
||||||
return Array.from(map.keys()).sort((a, b) => a - b)
|
return Array.from(map.keys()).sort((a, b) => a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _parseUniform(_variableInfo: VariableInfo): GPUBufferBindingLayout {
|
||||||
* @param {VariableInfo} _variableInfo
|
|
||||||
* @returns {GPUBufferBindingLayout}
|
|
||||||
*/
|
|
||||||
static _parseUniform(_variableInfo) {
|
|
||||||
return {
|
return {
|
||||||
type: BufferBindingType.Uniform,
|
type: BufferBindingType.Uniform,
|
||||||
// TODO: infer these two properties
|
// TODO: infer these two properties
|
||||||
|
@ -200,15 +161,10 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _parseStorage(variableInfo: VariableInfo): GPUBufferBindingLayout {
|
||||||
* @param {VariableInfo} variableInfo
|
|
||||||
* @returns {GPUBufferBindingLayout}
|
|
||||||
*/
|
|
||||||
static _parseStorage(variableInfo) {
|
|
||||||
return {
|
return {
|
||||||
type: accessToBufferType(
|
type: accessToBufferType(
|
||||||
/** @type {WGSLAccess} */
|
variableInfo.access
|
||||||
(variableInfo.access)
|
|
||||||
),
|
),
|
||||||
// TODO: infer these two properties
|
// TODO: infer these two properties
|
||||||
hasDynamicOffset: false,
|
hasDynamicOffset: false,
|
||||||
|
@ -216,11 +172,7 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _parseTexture(variableInfo: VariableInfo): GPUTextureBindingLayout {
|
||||||
* @param {VariableInfo} variableInfo
|
|
||||||
* @returns {GPUTextureBindingLayout}
|
|
||||||
*/
|
|
||||||
static _parseTexture(variableInfo) {
|
|
||||||
const [type, sampledType] = parseTextureType(
|
const [type, sampledType] = parseTextureType(
|
||||||
variableInfo.type.name
|
variableInfo.type.name
|
||||||
)
|
)
|
||||||
|
@ -232,39 +184,27 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _parseSampler(variableInfo: VariableInfo): GPUSamplerBindingLayout {
|
||||||
* @param {VariableInfo} variableInfo
|
|
||||||
* @returns {GPUSamplerBindingLayout}
|
|
||||||
*/
|
|
||||||
static _parseSampler(variableInfo) {
|
|
||||||
return {
|
return {
|
||||||
type: typeToSamplerBindingType(
|
type: typeToSamplerBindingType(
|
||||||
/** @type {WGSLSamplerType} */(variableInfo.type.name)
|
variableInfo.type.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _parseStorageTexture(variableInfo: VariableInfo): GPUStorageTextureBindingLayout {
|
||||||
* @param {VariableInfo} variableInfo
|
|
||||||
* @returns {GPUStorageTextureBindingLayout}
|
|
||||||
*/
|
|
||||||
static _parseStorageTexture(variableInfo) {
|
|
||||||
const [type] = parseTextureType(variableInfo.type.name)
|
const [type] = parseTextureType(variableInfo.type.name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access: accessToStorageTextureAccess(
|
access: accessToStorageTextureAccess(
|
||||||
/** @type {WGSLAccess} */(variableInfo.access)
|
variableInfo.access
|
||||||
),
|
),
|
||||||
format: wgslToWgpuFormat(variableInfo.type.name),
|
format: wgslToWgpuFormat(variableInfo.type.name),
|
||||||
viewDimension: typeToViewDimension(type)
|
viewDimension: typeToViewDimension(type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static _variableInfoToEntry(variableStageInfo: VariableStageInfo): GPUBindGroupLayoutEntry {
|
||||||
* @param {VariableStageInfo} variableStageInfo
|
|
||||||
* @returns {GPUBindGroupLayoutEntry}
|
|
||||||
*/
|
|
||||||
static _variableInfoToEntry(variableStageInfo) {
|
|
||||||
const { stages: visibility, variableInfo } = variableStageInfo
|
const { stages: visibility, variableInfo } = variableStageInfo
|
||||||
|
|
||||||
switch (variableInfo.resourceType) {
|
switch (variableInfo.resourceType) {
|
||||||
|
@ -304,10 +244,7 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static createBindGroupLayoutEntries(groupBindings: GroupBindingMap) {
|
||||||
* @param {GroupBindingMap} groupBindings
|
|
||||||
*/
|
|
||||||
static createBindGroupLayoutEntries(groupBindings) {
|
|
||||||
const sortedGroupIndices = this._sortKeyIndices(groupBindings)
|
const sortedGroupIndices = this._sortKeyIndices(groupBindings)
|
||||||
|
|
||||||
return sortedGroupIndices.map(groupIndex => {
|
return sortedGroupIndices.map(groupIndex => {
|
||||||
|
@ -321,16 +258,10 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShaderPair {
|
export class ShaderPair {
|
||||||
/** @type {ReflectedShader} */
|
_vertex: ReflectedShader
|
||||||
_vertex
|
_fragment: ReflectedShader
|
||||||
/** @type {ReflectedShader} */
|
|
||||||
_fragment
|
|
||||||
|
|
||||||
/**
|
constructor(vertex: ReflectedShader, fragment?: ReflectedShader) {
|
||||||
* @param {ReflectedShader} vertex
|
|
||||||
* @param {ReflectedShader} [fragment]
|
|
||||||
*/
|
|
||||||
constructor(vertex, fragment) {
|
|
||||||
if (!vertex) {
|
if (!vertex) {
|
||||||
throw new Error('Missing vertex shader')
|
throw new Error('Missing vertex shader')
|
||||||
}
|
}
|
||||||
|
@ -355,20 +286,16 @@ export class ShaderPair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {ShaderModule} shader */
|
static fromUnifiedShader(shader: ShaderModule) {
|
||||||
static fromUnifiedShader(shader) {
|
|
||||||
return new ShaderPair(
|
return new ShaderPair(
|
||||||
new ReflectedShader(shader)
|
new ReflectedShader(shader)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static fromPair(value: {
|
||||||
* @param {{
|
vertex: ShaderModule
|
||||||
vertex: ShaderModule,
|
fragment?: ShaderModule
|
||||||
fragment?: ShaderModule
|
}) {
|
||||||
* }} value
|
|
||||||
*/
|
|
||||||
static fromPair(value) {
|
|
||||||
const vert = new ReflectedShader(value.vertex)
|
const vert = new ReflectedShader(value.vertex)
|
||||||
const frag = value.fragment && new ReflectedShader(value.fragment)
|
const frag = value.fragment && new ReflectedShader(value.fragment)
|
||||||
return new ShaderPair(vert, frag)
|
return new ShaderPair(vert, frag)
|
||||||
|
@ -396,11 +323,7 @@ export class ShaderPair {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getFragmentState(descriptor: FragmentStateDescriptor): GPUFragmentState {
|
||||||
* @param {FragmentStateDescriptor} descriptor
|
|
||||||
* @returns {GPUFragmentState}
|
|
||||||
*/
|
|
||||||
_getFragmentState(descriptor) {
|
|
||||||
return {
|
return {
|
||||||
module: this._fragment.module.handle,
|
module: this._fragment.module.handle,
|
||||||
entryPoint: this._fragment.getEntrypoint('fragment'),
|
entryPoint: this._fragment.getEntrypoint('fragment'),
|
||||||
|
@ -409,11 +332,7 @@ export class ShaderPair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getVertexState(descriptor: VertexStateDescriptor): GPUVertexState {
|
||||||
* @param {VertexStateDescriptor} descriptor
|
|
||||||
* @returns {GPUVertexState}
|
|
||||||
*/
|
|
||||||
_getVertexState(descriptor) {
|
|
||||||
return {
|
return {
|
||||||
module: this._vertex.module.handle,
|
module: this._vertex.module.handle,
|
||||||
entryPoint: this._vertex.getEntrypoint('vertex'),
|
entryPoint: this._vertex.getEntrypoint('vertex'),
|
||||||
|
@ -422,12 +341,7 @@ export class ShaderPair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
findVariableInfo(name: string, group: number): VariableInfo | undefined {
|
||||||
* @param {string} name
|
|
||||||
* @param {number} group
|
|
||||||
* @returns {VariableInfo | undefined}
|
|
||||||
*/
|
|
||||||
findVariableInfo(name, group) {
|
|
||||||
let variableInfo = this._vertex.findVariableInfo(name, group)
|
let variableInfo = this._vertex.findVariableInfo(name, group)
|
||||||
|
|
||||||
if (!variableInfo && this._fragment !== this._vertex) {
|
if (!variableInfo && this._fragment !== this._vertex) {
|
||||||
|
@ -437,11 +351,7 @@ export class ShaderPair {
|
||||||
return variableInfo
|
return variableInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getRenderPipelineStates(descriptor: ShaderPairStateDescriptor): Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'> {
|
||||||
* @param {ShaderPairStateDescriptor} descriptor
|
|
||||||
* @returns {Pick<GPURenderPipelineDescriptor, 'vertex' | 'fragment'>}
|
|
||||||
*/
|
|
||||||
getRenderPipelineStates(descriptor) {
|
|
||||||
return {
|
return {
|
||||||
fragment: this._getFragmentState(descriptor.fragment),
|
fragment: this._getFragmentState(descriptor.fragment),
|
||||||
vertex: this._getVertexState(descriptor.vertex),
|
vertex: this._getVertexState(descriptor.vertex),
|
|
@ -1,113 +0,0 @@
|
||||||
import { WebGPUObjectError } from '../utils/errors.js'
|
|
||||||
import { ResourceType } from '../utils/internal-enums.js'
|
|
||||||
|
|
||||||
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
|
||||||
|
|
||||||
export class Texture {
|
|
||||||
_device
|
|
||||||
_handle
|
|
||||||
|
|
||||||
/** @type {GPUTextureView | undefined} */
|
|
||||||
_defaultView
|
|
||||||
|
|
||||||
get handle() {
|
|
||||||
return this._handle
|
|
||||||
}
|
|
||||||
|
|
||||||
get width() {
|
|
||||||
return this._handle.width
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
return this._handle.height
|
|
||||||
}
|
|
||||||
|
|
||||||
get format() {
|
|
||||||
return this._handle.format
|
|
||||||
}
|
|
||||||
|
|
||||||
get depthOrArrayLayers() {
|
|
||||||
return this._handle.depthOrArrayLayers
|
|
||||||
}
|
|
||||||
|
|
||||||
get usage() {
|
|
||||||
return this._handle.usage
|
|
||||||
}
|
|
||||||
|
|
||||||
get dimension() {
|
|
||||||
return this._handle.dimension
|
|
||||||
}
|
|
||||||
|
|
||||||
get mipLevelCount() {
|
|
||||||
return this._handle.mipLevelCount
|
|
||||||
}
|
|
||||||
|
|
||||||
get label() {
|
|
||||||
return this._handle.label
|
|
||||||
}
|
|
||||||
|
|
||||||
get resourceType() {
|
|
||||||
return ResourceType.TextureView
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUTexture} texture
|
|
||||||
*/
|
|
||||||
constructor(device, texture) {
|
|
||||||
this._device = device
|
|
||||||
this._handle = texture
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {GPUDevice} device
|
|
||||||
* @param {GPUTextureDescriptor} descriptor
|
|
||||||
*/
|
|
||||||
static create(device, descriptor) {
|
|
||||||
try {
|
|
||||||
return new Texture(
|
|
||||||
device,
|
|
||||||
device.createTexture(descriptor)
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
throw WebGPUObjectError.from(err, Texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {GPUTextureViewDescriptor} [descriptor]
|
|
||||||
* @throws {TextureError}
|
|
||||||
*/
|
|
||||||
createDefaultView(descriptor) {
|
|
||||||
if (!descriptor && this._defaultView) {
|
|
||||||
return this._defaultView
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const view = this._handle.createView(descriptor)
|
|
||||||
|
|
||||||
if (!descriptor) {
|
|
||||||
this._defaultView = view
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
} catch (err) {
|
|
||||||
throw WebGPUObjectError.from(err, Texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getView() {
|
|
||||||
return this.createDefaultView()
|
|
||||||
}
|
|
||||||
|
|
||||||
toBindingResource() {
|
|
||||||
return this._handle
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this._handle?.destroy()
|
|
||||||
this._handle = undefined
|
|
||||||
this._defaultView = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
230
src/resources/texture.ts
Normal file
230
src/resources/texture.ts
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import { CommandRecorder } from '../core/command-recorder.js'
|
||||||
|
import { BitFlags } from '../utils/bitflags.js'
|
||||||
|
import { WebGPUObjectError } from '../utils/errors.js'
|
||||||
|
import { ResourceType } from '../utils/internal-enums.js'
|
||||||
|
import { textureToImageDimension } from '../utils/wgsl-to-wgpu.js'
|
||||||
|
|
||||||
|
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
||||||
|
|
||||||
|
export class Texture {
|
||||||
|
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING
|
||||||
|
| GPUTextureUsage.COPY_DST
|
||||||
|
| GPUTextureUsage.RENDER_ATTACHMENT
|
||||||
|
|
||||||
|
_device
|
||||||
|
_handle
|
||||||
|
|
||||||
|
/** @type {GPUTextureView | undefined} */
|
||||||
|
_defaultView
|
||||||
|
|
||||||
|
get handle() {
|
||||||
|
return this._handle
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
return this._handle.width
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
return this._handle.height
|
||||||
|
}
|
||||||
|
|
||||||
|
get format() {
|
||||||
|
return this._handle.format
|
||||||
|
}
|
||||||
|
|
||||||
|
get depthOrArrayLayers() {
|
||||||
|
return this._handle.depthOrArrayLayers
|
||||||
|
}
|
||||||
|
|
||||||
|
get usage() {
|
||||||
|
return this._handle.usage
|
||||||
|
}
|
||||||
|
|
||||||
|
get dimension() {
|
||||||
|
return this._handle.dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
get mipLevelCount() {
|
||||||
|
return this._handle.mipLevelCount
|
||||||
|
}
|
||||||
|
|
||||||
|
get label() {
|
||||||
|
return this._handle.label
|
||||||
|
}
|
||||||
|
|
||||||
|
get resourceType() {
|
||||||
|
return ResourceType.TextureView
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {GPUTexture} texture
|
||||||
|
*/
|
||||||
|
constructor(device, texture) {
|
||||||
|
this._device = device
|
||||||
|
this._handle = texture
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {GPUTextureDescriptor} descriptor
|
||||||
|
*/
|
||||||
|
static create(device, descriptor) {
|
||||||
|
try {
|
||||||
|
return new Texture(
|
||||||
|
device,
|
||||||
|
device.createTexture(descriptor)
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _generateMipLevels(size) {
|
||||||
|
const max = Math.max.apply(undefined, size)
|
||||||
|
return 1 + Math.log2(max) | 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {string | URL} url
|
||||||
|
* @param {GPUTextureDescriptor} desciptor
|
||||||
|
*/
|
||||||
|
static async fromUrl(device, url, descriptor) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch remote resource: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = options.usage || Texture._defaultUsage
|
||||||
|
const dimension = descriptor.dimension ? textureToImageDimension(descriptor.dimension) : '2d'
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
const bitmap = await createImageBitmap(blob)
|
||||||
|
const size = [bitmap.width, bitmap.height]
|
||||||
|
|
||||||
|
const desc = {
|
||||||
|
usage,
|
||||||
|
dimension,
|
||||||
|
size,
|
||||||
|
format: descriptor.format || 'rgba8unorm',
|
||||||
|
mipLevelCount: descriptor.mipLevelCount || Texture._generateMipCount(...size),
|
||||||
|
...descriptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const texture = Texture.create(device, desc)
|
||||||
|
|
||||||
|
texture.upload(bitmap)
|
||||||
|
|
||||||
|
return texture
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUDevice} device
|
||||||
|
* @param {GPUExtent3DStrict} size
|
||||||
|
* @param {GPUTextureFormat} format
|
||||||
|
* @param {GPUTextureDescriptor} descriptor
|
||||||
|
*/
|
||||||
|
static createRenderTarget(device, size, format, descriptor) {
|
||||||
|
const usage = descriptor.usage || Texture._defaultUsage
|
||||||
|
|
||||||
|
return Texture.create(device, {
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
usage,
|
||||||
|
...descriptor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef UploadTextureInfo
|
||||||
|
* @property {GPUTexelCopyTextureInfo} destination
|
||||||
|
* @property {GPUTexelCopyBufferLayout} dataLayout
|
||||||
|
* @property {GPUExtent3DStrict} size
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUAllowSharedBufferSource} source
|
||||||
|
* @param {UploadTextureInfo} [options={}]
|
||||||
|
*/
|
||||||
|
upload(source, options = {}) {
|
||||||
|
const mipLevel = options.destination.mipLevel || 0
|
||||||
|
const size = options.size || [
|
||||||
|
Math.max(1, this.width >> mipLevel),
|
||||||
|
Math.max(1, this.height >> mipLevel),
|
||||||
|
this.depthOrArrayLayers
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._device.queue.writeTexture(
|
||||||
|
{ ...options.destination, texture: this._handle },
|
||||||
|
source,
|
||||||
|
options.dataLayout,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUCommandEncoder} commandEncoder
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GPUTextureViewDescriptor} [descriptor]
|
||||||
|
* @throws {TextureError}
|
||||||
|
*/
|
||||||
|
createDefaultView(descriptor) {
|
||||||
|
if (!descriptor && this._defaultView) {
|
||||||
|
return this._defaultView
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const view = this._handle.createView(descriptor)
|
||||||
|
|
||||||
|
if (!descriptor) {
|
||||||
|
this._defaultView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
} catch (err) {
|
||||||
|
throw WebGPUObjectError.from(err, Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getView() {
|
||||||
|
return this.createDefaultView()
|
||||||
|
}
|
||||||
|
|
||||||
|
toBindingResource() {
|
||||||
|
return this.getView()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this._handle?.destroy()
|
||||||
|
this._handle = undefined
|
||||||
|
this._defaultView = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
317
src/utils.js
317
src/utils.js
|
@ -1,317 +0,0 @@
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @template {keyof T} K
|
|
||||||
* @typedef {Pick<Partial<T>, K> & Omit<T, K>} Optional
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @typedef {
|
|
||||||
{ [P in keyof T]: T[P] } &
|
|
||||||
{ [P in keyof U]?: never}
|
|
||||||
* } Only
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @typedef {Only<T, U> | Only<U, T>} Either
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {new (...args: any[]) => T} Newable<T>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {
|
|
||||||
Int8Array
|
|
||||||
| Uint8Array
|
|
||||||
| Uint8ClampedArray
|
|
||||||
| Int16Array
|
|
||||||
| Uint16Array
|
|
||||||
| Int32Array
|
|
||||||
| Uint32Array
|
|
||||||
| Float32Array
|
|
||||||
| Float64Array
|
|
||||||
| BigInt64Array
|
|
||||||
| BigUint64Array
|
|
||||||
} TypedArray
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {
|
|
||||||
Int8ArrayConstructor
|
|
||||||
| Uint8ArrayConstructor
|
|
||||||
| Uint8ClampedArrayConstructor
|
|
||||||
| Int16ArrayConstructor
|
|
||||||
| Uint16ArrayConstructor
|
|
||||||
| Int32ArrayConstructor
|
|
||||||
| Uint32ArrayConstructor
|
|
||||||
| Float32ArrayConstructor
|
|
||||||
| Float64ArrayConstructor
|
|
||||||
| BigInt64ArrayConstructor
|
|
||||||
| BigUint64ArrayConstructor
|
|
||||||
} TypedArrayConstructor
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {
|
|
||||||
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
|
|
||||||
2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576,
|
|
||||||
2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456,
|
|
||||||
536870912, 1073741824, 2147483648]
|
|
||||||
* } PowersOfTwo
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {number} T
|
|
||||||
* @typedef {`${T}` extends `-${string}` | '0' | `${string}.${string}` ? never : T } PositiveInteger
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {ReadonlyArray<string>} T
|
|
||||||
* @typedef {T extends readonly [] ? [] : T extends readonly [infer Head extends string, ... infer Tail extends ReadonlyArray<string>] ? [Capitalize<Head>, ...CapitalizeAll<Tail>] : string[]} CapitalizeAll
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {ReadonlyArray<string>} T
|
|
||||||
* @template {string} [Sep='']
|
|
||||||
* @typedef {T extends readonly [] ? '' : T extends readonly [infer Head] ? Head : T extends readonly [infer Head extends string, ...infer Tail extends ReadonlyArray<string>] ? `${Head}${Sep}${Join<Tail, Sep>}` : string} Join
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} S
|
|
||||||
* @template {string} D
|
|
||||||
* @typedef {D extends '' ? [S] : S extends `${infer Head}${D}${infer Tail}` ? [Head, ...Split<Tail, D>] : [S]} Split
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @typedef {'-' | '_' | ' '} WordSeparator
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} S
|
|
||||||
* @template {string} D
|
|
||||||
* @typedef {S extends `${infer Left}${D}${infer Right}` ? `${PascalCaseFromDelimiter<Left, D>}${Capitalize<PascalCaseFromDelimiter<Right, D>>}` : S} PascalCaseFromDelimiter
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} S
|
|
||||||
* @typedef {Capitalize<PascalCaseFromDelimiter<PascalCaseFromDelimiter<PascalCaseFromDelimiter<Lowercase<S>, '-'>, '_'>, ' '>>} PascalCaseString
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string | ReadonlyArray<string>} T
|
|
||||||
* @typedef {T extends String ? PascalCaseString<T> : T extends ReadonlyArray<string> ? Join<CapitalizeAll<T>, ''> : T} PascalCase
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {readonly any[]} T
|
|
||||||
* @template V
|
|
||||||
* @template {any[]} [Acc=[]]
|
|
||||||
* @typedef {
|
|
||||||
T extends readonly [infer Head, ...infer Tail]
|
|
||||||
? Head extends V ? Acc['length'] : IndexOf<Tail, V, [...Acc, Head]>
|
|
||||||
: never
|
|
||||||
* } IndexOf
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} T
|
|
||||||
* @param {T} s
|
|
||||||
* @returns {Uppercase<T>}
|
|
||||||
*/
|
|
||||||
export const uppercase = s => /** @type {Uppercase<T>} */(s.toUpperCase())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} T
|
|
||||||
* @param {T} s
|
|
||||||
* @returns {Lowercase<T>}
|
|
||||||
*/
|
|
||||||
export const lowercase = s => /** @type {Lowercase<T>} */(s.toLowerCase())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} const T
|
|
||||||
* @param {T} s
|
|
||||||
* @returns {Split<T, '-'>}
|
|
||||||
*/
|
|
||||||
export const fromKebab = s => /** @type {Split<T, '-'>} */(s.split('-'))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} const T
|
|
||||||
* @param {T} xs
|
|
||||||
* @returns {PascalCase<T>}
|
|
||||||
*/
|
|
||||||
export const toPascal = xs => /** @type {PascalCase<T>} */(uppercase(xs[0]) + xs.slice(1))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string[]} const T
|
|
||||||
* @param {T} xs
|
|
||||||
* @returns {PascalCase<T>}
|
|
||||||
*/
|
|
||||||
const pascal = xs => /** @type {PascalCase<T>} */(xs.map(toPascal).join(''))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} const T
|
|
||||||
* @param {...T} values
|
|
||||||
* @returns {Readonly<{ [K in T as PascalCase<K>]: K }>}
|
|
||||||
*/
|
|
||||||
export const Enum = (...values) => /** @type {Readonly<{ [K in T as PascalCase<K>]: K }>} */(Object.freeze(
|
|
||||||
values.reduce((acc, x) => {
|
|
||||||
const key = pascal(fromKebab(x)).toString()
|
|
||||||
acc[key] = x
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {T extends `${infer N extends number}` ? N : never } ParseInt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {string} const T
|
|
||||||
* @template {T[]} const A
|
|
||||||
* @param {A} values
|
|
||||||
* @returns {Readonly<{ [K in keyof A as A[K] extends T ? PascalCase<A[K]> : never]: PowersOfTwo[ParseInt<K>] }>}
|
|
||||||
*/
|
|
||||||
export const FlagEnum = (...values) => /** @type {never} */(Object.freeze(
|
|
||||||
values.reduce((acc, x, i) => {
|
|
||||||
const key = pascal(fromKebab(x)).toString()
|
|
||||||
acc[key] = 1 << i
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
/** @typedef {(...args: any) => void} Listener */
|
|
||||||
export class EventEmitter {
|
|
||||||
_listeners = {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {PropertyKey} event
|
|
||||||
* @param {Listener} callback
|
|
||||||
*/
|
|
||||||
on(event, callback) {
|
|
||||||
this._listeners[event] = this._listeners[event] || []
|
|
||||||
this._listeners[event].push(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {PropertyKey} event
|
|
||||||
* @param {...any} args
|
|
||||||
*/
|
|
||||||
emit(event, ...args) {
|
|
||||||
const listeners = this._listeners[event]
|
|
||||||
|
|
||||||
if (listeners) {
|
|
||||||
listeners.forEach(
|
|
||||||
/** @param {Listener} cb */
|
|
||||||
cb => cb(...args)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {PropertyKey} event
|
|
||||||
* @param {Listener} callback
|
|
||||||
*/
|
|
||||||
off(event, callback) {
|
|
||||||
const listeners = this._listeners[event]
|
|
||||||
|
|
||||||
if (listeners) {
|
|
||||||
this._listeners[event] = listeners.filter(
|
|
||||||
/** @param {Listener} cb */
|
|
||||||
cb => cb !== callback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {{}} T
|
|
||||||
* @template {keyof T} K
|
|
||||||
* @param {T} obj
|
|
||||||
* @param {...K} keys
|
|
||||||
* @returns {Pick<T, K>}
|
|
||||||
*/
|
|
||||||
export const pick = (obj, ...keys) => /** @type {Pick<T, K>} */(Object.fromEntries(
|
|
||||||
keys
|
|
||||||
.filter(key => key in obj)
|
|
||||||
.map(key => [key, obj[key]])
|
|
||||||
))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {{}} T
|
|
||||||
* @template {PropertyKey & keyof T} K
|
|
||||||
* @param {T} obj
|
|
||||||
* @param {...K} keys
|
|
||||||
* @returns {{ [Key in K]: Key extends keyof T ? T[Key] : never }}
|
|
||||||
*/
|
|
||||||
export const inclusivePick = (obj, ...keys) => /** @type {{[Key in K]: Key extends keyof T ? T[Key] : never}} */(Object.fromEntries(
|
|
||||||
keys.map(key => [key, obj[key]])
|
|
||||||
))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {{}} T
|
|
||||||
* @template {PropertyKey & keyof T} K
|
|
||||||
* @param {T} obj
|
|
||||||
* @param {...K} keys
|
|
||||||
* @returns {Omit<T, K>}
|
|
||||||
*/
|
|
||||||
export const omit = (obj, ...keys) => /** @type {Omit<T, K>} */(Object.fromEntries(
|
|
||||||
Object.entries(obj)
|
|
||||||
.filter(([key]) => !keys.includes(/** @type {K} */(key)))
|
|
||||||
))
|
|
||||||
|
|
||||||
/** @template {number} T */
|
|
||||||
export class Flags {
|
|
||||||
_value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {T} value
|
|
||||||
*/
|
|
||||||
constructor(value) {
|
|
||||||
this._value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if `flag` exists in `bitflags`
|
|
||||||
*
|
|
||||||
* @template {number} T
|
|
||||||
* @param {T} flag
|
|
||||||
* @param {T} bitflags
|
|
||||||
*/
|
|
||||||
static has(flag, bitflags) {
|
|
||||||
return bitflags & flag
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combine two flags
|
|
||||||
*
|
|
||||||
* @template {number} T
|
|
||||||
* @param {T} a
|
|
||||||
* @param {T} b
|
|
||||||
*/
|
|
||||||
static add(a, b) {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if `flag` exists in these bitflags
|
|
||||||
*
|
|
||||||
* @param {T} flag
|
|
||||||
*/
|
|
||||||
has(flag) {
|
|
||||||
return Flags.has(flag, this._value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add `flag` to these bitflags
|
|
||||||
* @param {T} flag
|
|
||||||
*/
|
|
||||||
add(flag) {
|
|
||||||
return Flags.add(flag, this._value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { VariableInfo } from 'wgsl_reflect'
|
|
||||||
|
|
||||||
export class VariableStageInfo {
|
|
||||||
stages
|
|
||||||
variableInfo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {GPUShaderStageFlags} stages
|
|
||||||
* @param {VariableInfo} variableInfo
|
|
||||||
*/
|
|
||||||
constructor(stages, variableInfo) {
|
|
||||||
this.stages = stages
|
|
||||||
this.variableInfo = variableInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends Map<number, VariableStageInfo>
|
|
||||||
*/
|
|
||||||
export class BindingMap extends Map { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends Map<number, BindingMap>
|
|
||||||
*/
|
|
||||||
export class GroupBindingMap extends Map { }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
18
src/utils/bindings.ts
Normal file
18
src/utils/bindings.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { VariableInfo } from 'wgsl_reflect'
|
||||||
|
|
||||||
|
export class VariableStageInfo {
|
||||||
|
stages: GPUShaderStageFlags
|
||||||
|
variableInfo: VariableInfo
|
||||||
|
|
||||||
|
constructor(stages: GPUShaderStageFlags, variableInfo: VariableInfo) {
|
||||||
|
this.stages = stages
|
||||||
|
this.variableInfo = variableInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BindingMap extends Map<number, VariableStageInfo> { }
|
||||||
|
|
||||||
|
export class GroupBindingMap extends Map<number, BindingMap> { }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
export class BitFlags {
|
|
||||||
_value
|
|
||||||
|
|
||||||
get flags() {
|
|
||||||
return this._value
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(value) {
|
|
||||||
this._value = value
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {number} a
|
|
||||||
* @param {number} b
|
|
||||||
*/
|
|
||||||
static has(a, b) {
|
|
||||||
return (a & b) === b
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} a
|
|
||||||
* @param {number} b
|
|
||||||
*/
|
|
||||||
static add(a, b) {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} b
|
|
||||||
*/
|
|
||||||
has(b) {
|
|
||||||
return BitFlags.has(this._value, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} b
|
|
||||||
*/
|
|
||||||
add(b) {
|
|
||||||
return BitFlags.add(this._value, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
28
src/utils/bitflags.ts
Normal file
28
src/utils/bitflags.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export class BitFlags {
|
||||||
|
_value: number
|
||||||
|
|
||||||
|
get flags() {
|
||||||
|
return this._value
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(value: number) {
|
||||||
|
this._value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
static has(a: number, b: number) {
|
||||||
|
return (a & b) === b
|
||||||
|
}
|
||||||
|
|
||||||
|
static add(a: number, b: number) {
|
||||||
|
return a | b
|
||||||
|
}
|
||||||
|
|
||||||
|
has(b: number) {
|
||||||
|
return BitFlags.has(this._value, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
add(b: number) {
|
||||||
|
return BitFlags.add(this._value, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
/** @import { Newable } from '../utils.js' */
|
import { Newable } from './index.js'
|
||||||
|
|
||||||
export class WebGPUError extends Error {
|
export class WebGPUError extends Error {
|
||||||
/**
|
constructor(message: string, options?: ErrorOptions) {
|
||||||
* @param {string} message
|
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`WebGPUError: ${message}`, options)
|
super(`WebGPUError: ${message}`, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,30 +35,18 @@ export class CommandRecorderError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebGPUObjectError extends Error {
|
export class WebGPUObjectError extends Error {
|
||||||
/**
|
static from<T extends Error>(cause: T, type: string | Newable) {
|
||||||
* @template T
|
|
||||||
* @param {Error} cause
|
|
||||||
* @param {string | Newable<T>} [type]
|
|
||||||
*/
|
|
||||||
static from(cause, type) {
|
|
||||||
const name = typeof type === 'string' ? type : type.name
|
const name = typeof type === 'string' ? type : type.name
|
||||||
return new WebGPUObjectError(`could not create ${name}`, { cause })
|
return new WebGPUObjectError(`could not create ${name}`, { cause })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BufferError extends WebGPUObjectError {
|
export class BufferError extends WebGPUObjectError {
|
||||||
/**
|
constructor(message: string, options?: ErrorOptions) {
|
||||||
* @param {string} message
|
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`BufferError: ${message}`, options)
|
super(`BufferError: ${message}`, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static from<T extends Error>(cause: T) {
|
||||||
* @param {Error} cause
|
|
||||||
*/
|
|
||||||
static from(cause) {
|
|
||||||
return new BufferError('could not create buffer', { cause })
|
return new BufferError('could not create buffer', { cause })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,35 +54,21 @@ export class BufferError extends WebGPUObjectError {
|
||||||
return new BufferError('cannot read a buffer without MAP_READ usage')
|
return new BufferError('cannot read a buffer without MAP_READ usage')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static outOfBounds(offset: number, size: number) {
|
||||||
* @param {number} offset
|
|
||||||
* @param {number} size
|
|
||||||
*/
|
|
||||||
static outOfBounds(offset, size) {
|
|
||||||
return new BufferError(`buffer offset/size (${offset}/${size}) exceeds buffer dimensions`)
|
return new BufferError(`buffer offset/size (${offset}/${size}) exceeds buffer dimensions`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MaterialError extends WebGPUObjectError {
|
export class MaterialError extends WebGPUObjectError {
|
||||||
/**
|
constructor(message: string, options?: ErrorOptions) {
|
||||||
* @param {string} message
|
|
||||||
* @param {ErrorOptions} [options]
|
|
||||||
*/
|
|
||||||
constructor(message, options) {
|
|
||||||
super(`MaterialError: ${message}`, options)
|
super(`MaterialError: ${message}`, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static from<T extends Error>(cause: T) {
|
||||||
* @param {Error} cause
|
|
||||||
*/
|
|
||||||
static from(cause) {
|
|
||||||
return new BufferError('could not create material', { cause })
|
return new BufferError('could not create material', { cause })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static missingShader(shaderType: string) {
|
||||||
* @param {string} shaderType
|
|
||||||
*/
|
|
||||||
static missingShader(shaderType) {
|
|
||||||
return new BufferError(`missing ${shaderType} shader`)
|
return new BufferError(`missing ${shaderType} shader`)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,24 +2,18 @@ import { GraphicsDevice } from '../core/graphics-device.js'
|
||||||
|
|
||||||
export class GraphicsDeviceInitialized {
|
export class GraphicsDeviceInitialized {
|
||||||
static EventName = 'graphics-device:initialized'
|
static EventName = 'graphics-device:initialized'
|
||||||
graphicsDevice
|
graphicsDevice: GraphicsDevice
|
||||||
|
|
||||||
/**
|
constructor(graphicsDevice: GraphicsDevice) {
|
||||||
* @param {GraphicsDevice} graphicsDevice
|
|
||||||
*/
|
|
||||||
constructor(graphicsDevice) {
|
|
||||||
this.graphicsDevice = graphicsDevice
|
this.graphicsDevice = graphicsDevice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphicsDeviceLost {
|
export class GraphicsDeviceLost {
|
||||||
static EventName = 'graphics-device:device-lost'
|
static EventName = 'graphics-device:device-lost'
|
||||||
info
|
info: GPUDeviceLostInfo
|
||||||
|
|
||||||
/**
|
constructor(info: GPUDeviceLostInfo) {
|
||||||
* @param {GPUDeviceLostInfo} info
|
|
||||||
*/
|
|
||||||
constructor(info) {
|
|
||||||
this.info = info
|
this.info = info
|
||||||
}
|
}
|
||||||
}
|
}
|
160
src/utils/index.ts
Normal file
160
src/utils/index.ts
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K & Omit<T, K>>
|
||||||
|
|
||||||
|
export type Only<T, U> = { [P in keyof T]: T[P] } & { [P in keyof U]?: never }
|
||||||
|
|
||||||
|
export type Either<T, U> = Only<T, U> | Only<U, T>
|
||||||
|
|
||||||
|
export interface Newable {
|
||||||
|
new(...args: any[]): any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypedArray = Int8Array
|
||||||
|
| Uint8Array
|
||||||
|
| Uint8ClampedArray
|
||||||
|
| Int16Array
|
||||||
|
| Uint16Array
|
||||||
|
| Int32Array
|
||||||
|
| Uint32Array
|
||||||
|
| Float32Array
|
||||||
|
| Float64Array
|
||||||
|
| BigInt64Array
|
||||||
|
| BigUint64Array
|
||||||
|
|
||||||
|
export type TypedArrayConstructor = Int8ArrayConstructor
|
||||||
|
| Uint8ArrayConstructor
|
||||||
|
| Uint8ClampedArrayConstructor
|
||||||
|
| Int16ArrayConstructor
|
||||||
|
| Uint16ArrayConstructor
|
||||||
|
| Int32ArrayConstructor
|
||||||
|
| Uint32ArrayConstructor
|
||||||
|
| Float32ArrayConstructor
|
||||||
|
| Float64ArrayConstructor
|
||||||
|
| BigInt64ArrayConstructor
|
||||||
|
| BigUint64ArrayConstructor
|
||||||
|
|
||||||
|
export type PowersOfTwo = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512,
|
||||||
|
1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144,
|
||||||
|
524288, 1048576, 2097152, 4194304, 8388608, 16777216,
|
||||||
|
33554432, 67108864, 134217728, 268435456, 536870912, 1073741824,
|
||||||
|
2147483648]
|
||||||
|
|
||||||
|
export type PositiveInteger<T extends number> = `${T}` extends `-${string}` | '0' | `${string}.${string}` ? never : T
|
||||||
|
|
||||||
|
export type CapitalizeAll<T extends ReadonlyArray<string>> = T extends readonly [] ? [] : T extends readonly [infer Head extends string, ...infer Tail extends ReadonlyArray<string>] ? [Capitalize<Head>, ...CapitalizeAll<Tail>] : string[]
|
||||||
|
|
||||||
|
export type Join<T extends ReadonlyArray<string>, Sep extends string = ''> = T extends readonly [] ? '' : T extends readonly [infer Head] ? Head : T extends readonly [infer Head extends string, ...infer Tail extends ReadonlyArray<string>] ? `${Head}${Sep}${Join<Tail, Sep>}` : string
|
||||||
|
|
||||||
|
export type Split<S extends string, D extends string> = D extends '' ? [S] : S extends `${infer Head}${D}${infer Tail}` ? [Head, ...Split<Tail, D>] : [S]
|
||||||
|
|
||||||
|
export type PascalCaseFromDelimiter<S extends string, D extends string> = S extends `${infer Left}${D}${infer Right}` ? `${PascalCaseFromDelimiter<Left, D>}${Capitalize<PascalCaseFromDelimiter<Right, D>>}` : S
|
||||||
|
|
||||||
|
export type PascalCaseString<S extends string> = Capitalize<PascalCaseFromDelimiter<PascalCaseFromDelimiter<PascalCaseFromDelimiter<Lowercase<S>, '-'>, '_'>, ' '>>
|
||||||
|
|
||||||
|
export type PascalCase<T extends string | ReadonlyArray<string>> = T extends string ? PascalCaseString<T> : T extends ReadonlyArray<string> ? Join<CapitalizeAll<T>, ''> : T
|
||||||
|
|
||||||
|
export type IndexOf<T extends readonly any[], V, Acc extends any[] = []> = T extends readonly [infer Head, ...infer Tail]
|
||||||
|
? Head extends V ? Acc['length'] : IndexOf<Tail, V, [...Acc, Head]>
|
||||||
|
: never
|
||||||
|
|
||||||
|
export const uppercase = <T extends string>(s: T) => s.toUpperCase() as Uppercase<T>
|
||||||
|
|
||||||
|
export const lowercase = <T extends string>(s: T) => s.toLowerCase() as Lowercase<T>
|
||||||
|
|
||||||
|
export const split = <const T extends string, const D extends string>(delim: D, s: T) => s.split(delim) as Split<T, D>
|
||||||
|
|
||||||
|
export const fromKebab = <const T extends string>(s: T) => split('-', s) as Split<T, '-'>
|
||||||
|
|
||||||
|
export const toPascal = <const T extends string>(xs: T) => uppercase(xs[0]) + xs.slice(1) as PascalCase<T>
|
||||||
|
|
||||||
|
const pascal = <const T extends string[]>(xs: T) => xs.map(toPascal).join('') as PascalCase<T>
|
||||||
|
|
||||||
|
export const Enum = <const T extends string>(...values: T[]) => Object.freeze(
|
||||||
|
values.reduce((acc, x) => {
|
||||||
|
const key = pascal(fromKebab(x)).toString()
|
||||||
|
acc[key] = x
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
) as Readonly<{ [K in T as PascalCase<K>]: K }>
|
||||||
|
|
||||||
|
type ParseInt<T> = T extends `${infer N extends number}` ? N : never
|
||||||
|
|
||||||
|
type FlagEnum<T extends string, A extends T[]> = Readonly<{ [K in keyof A as A[K] extends T ? PascalCase<A[K]> : never]: PowersOfTwo[ParseInt<K>] }>
|
||||||
|
export const FlagEnum = <const T extends string, const A extends T[]>(...values: A) => Object.freeze(
|
||||||
|
values.reduce((acc, x, i) => {
|
||||||
|
const key = pascal(fromKebab(x)).toString()
|
||||||
|
acc[key] = 1 << i
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
) as FlagEnum<T, A>
|
||||||
|
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
(...args: any): void
|
||||||
|
}
|
||||||
|
export class EventEmitter {
|
||||||
|
_listeners = {}
|
||||||
|
|
||||||
|
on(event: PropertyKey, callback: Listener) {
|
||||||
|
this._listeners[event] = this._listeners[event] || []
|
||||||
|
this._listeners[event].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event: PropertyKey, ...args: any[]) {
|
||||||
|
const listeners = this._listeners[event]
|
||||||
|
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(
|
||||||
|
(cb: Listener) => cb(...args)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event: PropertyKey, callback: Listener) {
|
||||||
|
const listeners = this._listeners[event]
|
||||||
|
|
||||||
|
if (listeners) {
|
||||||
|
this._listeners[event] = listeners.filter(
|
||||||
|
(cb: Listener) => cb !== callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pick = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> => (Object.fromEntries(
|
||||||
|
keys
|
||||||
|
.filter(key => key in obj)
|
||||||
|
.map(key => [key, obj[key]])
|
||||||
|
)) as Pick<T, K>
|
||||||
|
|
||||||
|
export const inclusivePick = <T extends object, K extends keyof T>(obj: T, ...keys: K[]) => Object.fromEntries(
|
||||||
|
keys.map(key => [key, obj[key]])
|
||||||
|
) as { [Key in K]: Key extends keyof T ? T[Key] : never }
|
||||||
|
|
||||||
|
export const omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]) => Object.fromEntries(
|
||||||
|
Object.entries(obj)
|
||||||
|
.filter(([key]: K[]) => !keys.includes(key))
|
||||||
|
) as Omit<T, K>
|
||||||
|
|
||||||
|
export class Flags {
|
||||||
|
_value: number
|
||||||
|
|
||||||
|
constructor(value: number) {
|
||||||
|
this._value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
static has(flag: number, bitflags: number) {
|
||||||
|
return bitflags & flag
|
||||||
|
}
|
||||||
|
static add(a: number, b: number) {
|
||||||
|
return a | b
|
||||||
|
}
|
||||||
|
|
||||||
|
has(flag: number) {
|
||||||
|
return Flags.has(flag, this._value)
|
||||||
|
}
|
||||||
|
|
||||||
|
add(flag: number) {
|
||||||
|
return Flags.add(flag, this._value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FlagEnum } from '../utils.js'
|
import { FlagEnum } from '../utils/index.js'
|
||||||
|
|
||||||
export const ShaderStage = FlagEnum(
|
export const ShaderStage = FlagEnum(
|
||||||
'vertex',
|
'vertex',
|
||||||
|
@ -6,11 +6,7 @@ export const ShaderStage = FlagEnum(
|
||||||
'compute'
|
'compute'
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
export const stageFlagToName = (stages: GPUShaderStageFlags): string[] => {
|
||||||
* @param {GPUShaderStageFlags} stages
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
export const stageFlagToName = stages => {
|
|
||||||
const names = []
|
const names = []
|
||||||
|
|
||||||
if (stages & GPUShaderStage.FRAGMENT) {
|
if (stages & GPUShaderStage.FRAGMENT) {
|
||||||
|
@ -28,10 +24,9 @@ export const stageFlagToName = stages => {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type StageFlag = 'fragment' | 'vertex' | 'compute'
|
||||||
* @param {('fragment' | 'vertex' | 'compute')[]} names
|
|
||||||
*/
|
export const nameToStageFlag = (names: StageFlag[]) => {
|
||||||
export const nameToStageFlag = names => {
|
|
||||||
return names.reduce((flags, name) => {
|
return names.reduce((flags, name) => {
|
||||||
switch (name.toLowerCase()) {
|
switch (name.toLowerCase()) {
|
||||||
case 'fragment': return flags | GPUShaderStage.FRAGMENT
|
case 'fragment': return flags | GPUShaderStage.FRAGMENT
|
188
src/utils/mip-generator.ts
Normal file
188
src/utils/mip-generator.ts
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
import code from './mip-shader.wgsl' with { type: 'text' }
|
||||||
|
|
||||||
|
const mip = (n: number) => Math.max(1, n >>> 1)
|
||||||
|
|
||||||
|
export class MipGenerator {
|
||||||
|
_device: GPUDevice
|
||||||
|
_sampler: GPUSampler
|
||||||
|
_pipelines: Record<string, GPURenderPipeline>
|
||||||
|
|
||||||
|
_shader?: GPUShaderModule
|
||||||
|
_bindGroupLayout?: GPUBindGroupLayout
|
||||||
|
_pipelineLayout?: GPUPipelineLayout
|
||||||
|
|
||||||
|
constructor(device: GPUDevice) {
|
||||||
|
this._device = device
|
||||||
|
this._sampler = device.createSampler({ minFilter: 'linear' })
|
||||||
|
this._pipelines = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getShader() {
|
||||||
|
if (!this._shader) {
|
||||||
|
this._shader = this._device.createShaderModule({
|
||||||
|
code
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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: GPUTextureFormat) {
|
||||||
|
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: GPUTexture, descriptor: GPUTextureDescriptor) {
|
||||||
|
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 as GPUExtent3DDict
|
||||||
|
|
||||||
|
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 i = 1; i < descriptor.mipLevelCount; ++i) {
|
||||||
|
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 i = 1; i < descriptor.mipLevelCount; ++i) {
|
||||||
|
encoder.copyTextureToTexture({
|
||||||
|
texture: mipTexture,
|
||||||
|
mipLevel: i - 1
|
||||||
|
}, {
|
||||||
|
texture,
|
||||||
|
mipLevel: i,
|
||||||
|
}, mipLevelSize)
|
||||||
|
|
||||||
|
mipLevelSize.width = mip(mipLevelSize.width)
|
||||||
|
mipLevelSize.height = mip(mipLevelSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._device.queue.submit([encoder.finish()])
|
||||||
|
|
||||||
|
if (!renderToSource) {
|
||||||
|
mipTexture.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
23
src/utils/mip-shader.wgsl
Normal file
23
src/utils/mip-shader.wgsl
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
var<private> pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
||||||
|
vec2<f32>(-1.0, -1.0), vec2<f32>(-1.0, 3.0), vec2<f32>(3.0, -1.0));
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position : vec4<f32>,
|
||||||
|
@location(0) texCoord : vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
|
||||||
|
var output : VertexOutput;
|
||||||
|
output.texCoord = pos[vertexIndex] * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
|
||||||
|
output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0) var imgSampler : sampler;
|
||||||
|
@group(0) @binding(1) var img : texture_2d<f32>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(@location(0) texCoord : vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
|
return textureSample(img, imgSampler, texCoord);
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "es2022",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": ["es2022", "dom"],
|
"lib": ["es2022", "dom"],
|
||||||
"types": ["@webgpu/types"],
|
"types": ["@webgpu/types"],
|
||||||
"checkJs": "true"
|
"checkJs": true
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
Loading…
Add table
Reference in a new issue