oktaeder/src/resources/Texture2D.ts

207 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Vector2Object, Vector2Tuple } from "../data";
import { Renderer } from "../oktaeder";
export type Texture2DFormat =
| "linear"
| "srgb"
| "hdr"
| "depth"
;
export interface Texture2DProps {
readonly name?: string;
readonly width: number;
readonly height: number;
readonly format: Texture2DFormat;
readonly usage?: GPUTextureUsageFlags;
}
export interface Texture2DResizeProps {
readonly width?: number;
readonly height?: number;
readonly format?: Texture2DFormat;
readonly usage?: GPUTextureUsageFlags;
}
export interface Texture2DAdvancedWriteProps {
readonly origin: Vector2Object | Vector2Tuple,
readonly data: BufferSource | SharedArrayBuffer,
readonly bytesPerRow: number,
readonly width: number,
readonly height: number,
}
export class Texture2D {
declare readonly type: "Texture2D";
_renderer: Renderer;
_name: string;
_texture: GPUTexture;
_textureView: GPUTextureView;
_format: Texture2DFormat;
constructor(renderer: Renderer, {
name = "",
width,
height,
format,
usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
}: Texture2DProps) {
this._renderer = renderer;
this._name = name;
const gpuFormat = gpuTextureFormat(format);
this._renderer = renderer;
this._texture = renderer._device.createTexture({
usage,
size: { width, height },
format: gpuFormat,
label: name
});
this._textureView = this._texture.createView({
format: gpuFormat,
dimension: "2d",
label: `${name}.textureView`,
});
this._format = format;
}
/**
* Destroys owned GPU resources. The texture should not be used after
* calling this method.
* @returns `this` for chaining
*/
dispose(): Texture2D {
this._texture.destroy();
return this;
}
get width(): number {
return this._texture.width;
}
get height(): number {
return this._texture.height;
}
get bytesPerSample(): number {
return sampleSize(this._format);
}
get samplesPerPixel(): number {
return sampleCount(this._format);
}
writeFull(data: BufferSource | SharedArrayBuffer): Texture2D {
const bytesPerSample = this.bytesPerSample;
const samplesPerPixel = this.samplesPerPixel;
const bytesPerRow = this.width * samplesPerPixel * bytesPerSample;
const byteLength = this.height * bytesPerRow;
if (data.byteLength !== byteLength) {
throw new Error(`Cannot fully write to a texture with different byte length. Source data has byte length of ${data.byteLength}. Texture [${this._name}] is ${this.width}×${this.height} pixels in size, uses ${this._format} format at ${bytesPerSample} ${bytesPerSample === 1 ? "byte" : "bytes"} per sample and ${samplesPerPixel} ${samplesPerPixel === 1 ? "sample" : "samples"} per pixel, which makes its byte length equal to ${byteLength}.`);
}
this._renderer._device.queue.writeTexture(
{ texture: this._texture },
data,
{ bytesPerRow },
{ width: this.width, height: this.height },
);
return this;
}
writePartial({
origin,
data,
bytesPerRow,
width,
height,
}: Texture2DAdvancedWriteProps): Texture2D {
this._renderer._device.queue.writeTexture(
{ texture: this._texture, origin },
data,
{ bytesPerRow },
{ width, height },
);
return this;
}
/**
* Resize the texture and/or change its format, discarding currently stored
* data.
* @param props Desired texture properties. Any unspecified property will
* stay unchanged.
* @returns `this` for chaining
*/
resizeDiscard({
width = this._texture.width,
height = this._texture.height,
format = this._format,
usage = this._texture.usage,
}: Texture2DResizeProps): Texture2D {
this._texture.destroy();
const gpuFormat = gpuTextureFormat(format);
this._texture = this._renderer._device.createTexture({
usage,
size: { width, height },
format: gpuFormat,
label: this._name
});
this._textureView = this._texture.createView({
format: gpuFormat,
dimension: "2d",
label: `${this._name}.textureView`,
});
return this;
}
}
Object.defineProperty(Texture2D.prototype, "type", { value: "Texture2D" });
export function isTexture2D(value: unknown): value is Texture2D {
return Boolean(value) && (value as Texture2D).type === "Texture2D";
}
function gpuTextureFormat(format: Texture2DFormat): GPUTextureFormat {
switch (format) {
case "linear": return "rgba8unorm";
case "srgb": return "rgba8unorm-srgb";
case "hdr": return "rgba16float";
case "depth": return "depth32float";
}
}
function sampleCount(format: Texture2DFormat): number {
switch (format) {
case "linear": return 4;
case "srgb": return 4;
case "hdr": return 4;
case "depth": return 1;
}
}
function sampleSize(format: Texture2DFormat): number {
switch (format) {
case "linear": return 1;
case "srgb": return 1;
case "hdr": return 2;
case "depth": return 4;
}
}