387 lines
12 KiB
TypeScript
387 lines
12 KiB
TypeScript
/*!
|
|
* 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, Vector3Object, Vector4Object } from "../data";
|
|
import { Renderer } from "../oktaeder";
|
|
|
|
export const POSITION_SIZE = 12;
|
|
export const TEX_COORD_SIZE = 8;
|
|
export const LIGHT_TEX_COORD_SIZE = 8;
|
|
export const NORMAL_SIZE = 12;
|
|
export const TANGENT_SIZE = 16;
|
|
|
|
export interface VertexBufferProps {
|
|
readonly name?: string;
|
|
|
|
readonly vertexCount: number;
|
|
|
|
readonly texCoord?: boolean;
|
|
readonly lightTexCoord?: boolean;
|
|
readonly normal?: boolean;
|
|
readonly tangent?: boolean;
|
|
}
|
|
|
|
export interface VertexBufferResizeProps {
|
|
readonly vertexCount?: number;
|
|
|
|
readonly texCoord?: boolean;
|
|
readonly lightTexCoord?: boolean;
|
|
readonly normal?: boolean;
|
|
readonly tangent?: boolean;
|
|
}
|
|
|
|
export interface VertexBufferWriteArrayProps {
|
|
readonly position?: readonly Vector3Object[];
|
|
readonly texCoord?: readonly Vector2Object[];
|
|
readonly lightTexCoord?: readonly Vector2Object[];
|
|
readonly normal?: readonly Vector3Object[];
|
|
readonly tangent?: readonly Vector4Object[];
|
|
}
|
|
|
|
export interface VertexBufferWriteTypedArrayProps {
|
|
readonly position?: Float32Array;
|
|
readonly texCoord?: Float32Array;
|
|
readonly lightTexCoord?: Float32Array;
|
|
readonly normal?: Float32Array;
|
|
readonly tangent?: Float32Array;
|
|
}
|
|
|
|
export class VertexBuffer {
|
|
|
|
declare readonly type: "VertexBuffer";
|
|
_renderer: Renderer;
|
|
|
|
_name: string;
|
|
|
|
_positionBuffer: GPUBuffer;
|
|
_texCoordBuffer: GPUBuffer | null;
|
|
_lightTexCoordBuffer: GPUBuffer | null;
|
|
_normalBuffer: GPUBuffer | null;
|
|
_tangentBuffer: GPUBuffer | null;
|
|
|
|
constructor(renderer: Renderer, {
|
|
name = "",
|
|
vertexCount,
|
|
texCoord = false,
|
|
lightTexCoord = false,
|
|
normal = false,
|
|
tangent = false,
|
|
}: VertexBufferProps) {
|
|
this._renderer = renderer;
|
|
|
|
this._name = name;
|
|
|
|
this._positionBuffer = renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * POSITION_SIZE,
|
|
label: `${this._name}.position`,
|
|
});
|
|
|
|
this._texCoordBuffer = texCoord ? renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TEX_COORD_SIZE,
|
|
label: `${this._name}.texCoord`,
|
|
}) : null;
|
|
|
|
this._lightTexCoordBuffer = lightTexCoord ? renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * LIGHT_TEX_COORD_SIZE,
|
|
label: `${this._name}.lightTexCoord`,
|
|
}) : null;
|
|
|
|
this._normalBuffer = normal ? renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * NORMAL_SIZE,
|
|
label: `${this._name}.normal`,
|
|
}) : null;
|
|
|
|
this._tangentBuffer = tangent ? renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TANGENT_SIZE,
|
|
label: `${this._name}.tangent`,
|
|
}) : null;
|
|
}
|
|
|
|
/**
|
|
* Destroys owned GPU resources. The vertex buffer should not be used after
|
|
* calling this method.
|
|
* @returns `this` for chaining
|
|
*/
|
|
dispose(): VertexBuffer {
|
|
this._positionBuffer.destroy();
|
|
this._texCoordBuffer?.destroy();
|
|
this._lightTexCoordBuffer?.destroy();
|
|
this._normalBuffer?.destroy();
|
|
this._tangentBuffer?.destroy();
|
|
return this;
|
|
}
|
|
|
|
get vertexCount(): number {
|
|
return this._positionBuffer.size / POSITION_SIZE | 0;
|
|
}
|
|
|
|
get hasTexCoord(): boolean {
|
|
return this._texCoordBuffer !== null;
|
|
}
|
|
|
|
get hasLightTexCoord(): boolean {
|
|
return this._lightTexCoordBuffer !== null;
|
|
}
|
|
|
|
get hasNormal(): boolean {
|
|
return this._normalBuffer !== null;
|
|
}
|
|
|
|
get hasTangent(): boolean {
|
|
return this._tangentBuffer !== null;
|
|
}
|
|
|
|
writeArray(offset: number, {
|
|
position,
|
|
texCoord,
|
|
lightTexCoord,
|
|
normal,
|
|
tangent,
|
|
}: VertexBufferWriteArrayProps): VertexBuffer {
|
|
|
|
if (position !== undefined) {
|
|
const array = new Float32Array(position.length * 3);
|
|
for (let vi = 0, ptr = 0; vi < position.length; ++vi) {
|
|
const vertex = position[vi]!;
|
|
array[ptr++] = vertex.x;
|
|
array[ptr++] = vertex.y;
|
|
array[ptr++] = vertex.z;
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._positionBuffer, offset * POSITION_SIZE | 0, array);
|
|
}
|
|
|
|
if (texCoord !== undefined) {
|
|
if (this._texCoordBuffer === null) {
|
|
throw new Error(`Cannot write array to a missing vertex attribute. Tried writing texture coordinates and vertex buffer [${this._name}] does not have texture coordinates.`);
|
|
}
|
|
const array = new Float32Array(texCoord.length * 2);
|
|
for (let vi = 0, ptr = 0; vi < texCoord.length; ++vi) {
|
|
const vertex = texCoord[vi]!;
|
|
array[ptr++] = vertex.x;
|
|
array[ptr++] = vertex.y;
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._texCoordBuffer, offset * TEX_COORD_SIZE | 0, array);
|
|
}
|
|
|
|
if (lightTexCoord !== undefined) {
|
|
if (this._lightTexCoordBuffer === null) {
|
|
throw new Error(`Cannot write array to a missing vertex attribute. Tried writing light texture coordinates and vertex buffer [${this._name}] does not have light texture coordinates.`);
|
|
}
|
|
const array = new Float32Array(lightTexCoord.length * 2);
|
|
for (let vi = 0, ptr = 0; vi < lightTexCoord.length; ++vi) {
|
|
const vertex = lightTexCoord[vi]!;
|
|
array[ptr++] = vertex.x;
|
|
array[ptr++] = vertex.y;
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._lightTexCoordBuffer, offset * LIGHT_TEX_COORD_SIZE | 0, array);
|
|
}
|
|
|
|
if (normal !== undefined) {
|
|
if (this._normalBuffer === null) {
|
|
throw new Error(`Cannot write array to a missing vertex attribute. Tried writing normals and vertex buffer [${this._name}] does not have normals.`);
|
|
}
|
|
const array = new Float32Array(normal.length * 3);
|
|
for (let vi = 0, ptr = 0; vi < normal.length; ++vi) {
|
|
const vertex = normal[vi]!;
|
|
array[ptr++] = vertex.x;
|
|
array[ptr++] = vertex.y;
|
|
array[ptr++] = vertex.z;
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._normalBuffer, offset * NORMAL_SIZE | 0, array);
|
|
}
|
|
|
|
if (tangent !== undefined) {
|
|
if (this._tangentBuffer === null) {
|
|
throw new Error(`Cannot write array to a missing vertex attribute. Tried writing tangents and vertex buffer [${this._name}] does not have tangents.`);
|
|
}
|
|
const array = new Float32Array(tangent.length * 4);
|
|
for (let vi = 0, ptr = 0; vi < tangent.length; ++vi) {
|
|
const vertex = tangent[vi]!;
|
|
array[ptr++] = vertex.x;
|
|
array[ptr++] = vertex.y;
|
|
array[ptr++] = vertex.z;
|
|
array[ptr++] = vertex.w;
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._tangentBuffer, offset * TANGENT_SIZE | 0, array);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
writeTypedArray(offset: number, {
|
|
position,
|
|
texCoord,
|
|
lightTexCoord,
|
|
normal,
|
|
tangent,
|
|
}: VertexBufferWriteTypedArrayProps): VertexBuffer {
|
|
|
|
if (position !== undefined) {
|
|
this._renderer._device.queue.writeBuffer(this._positionBuffer, offset * POSITION_SIZE | 0, position);
|
|
}
|
|
|
|
if (texCoord !== undefined) {
|
|
if (this._texCoordBuffer === null) {
|
|
throw new Error(`Cannot write typed array to a missing vertex attribute. Tried writing texture coordinates and vertex buffer [${this._name}] does not have texture coordinates.`);
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._texCoordBuffer, offset * TEX_COORD_SIZE | 0, texCoord);
|
|
}
|
|
|
|
if (lightTexCoord !== undefined) {
|
|
if (this._lightTexCoordBuffer === null) {
|
|
throw new Error(`Cannot write typed array to a missing vertex attribute. Tried writing light texture coordinates and vertex buffer [${this._name}] does not have light texture coordinates.`);
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._lightTexCoordBuffer, offset * LIGHT_TEX_COORD_SIZE | 0, lightTexCoord);
|
|
}
|
|
|
|
if (normal !== undefined) {
|
|
if (this._normalBuffer === null) {
|
|
throw new Error(`Cannot write typed array to a missing vertex attribute. Tried writing normals and vertex buffer [${this._name}] does not have normals.`);
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._normalBuffer, offset * NORMAL_SIZE | 0, normal);
|
|
}
|
|
|
|
if (tangent !== undefined) {
|
|
if (this._tangentBuffer === null) {
|
|
throw new Error(`Cannot write typed array to a missing vertex attribute. Tried writing tangents and vertex buffer [${this._name}] does not have tangents.`);
|
|
}
|
|
this._renderer._device.queue.writeBuffer(this._tangentBuffer, offset * TANGENT_SIZE | 0, tangent);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Resize the vertex buffer and/or add or remove vertex attributes,
|
|
* discarding currently stored data.
|
|
* @param props Desired buffer properties. Any unspecified property will
|
|
* stay unchanged.
|
|
* @returns `this` for chaining
|
|
*/
|
|
resizeDiscard({
|
|
vertexCount = this.vertexCount,
|
|
texCoord = this.hasTexCoord,
|
|
lightTexCoord = this.hasLightTexCoord,
|
|
normal = this.hasNormal,
|
|
tangent = this.hasTangent,
|
|
}: VertexBufferResizeProps): VertexBuffer {
|
|
|
|
this._positionBuffer.destroy();
|
|
this._texCoordBuffer?.destroy();
|
|
this._lightTexCoordBuffer?.destroy();
|
|
this._normalBuffer?.destroy();
|
|
this._tangentBuffer?.destroy();
|
|
|
|
this._positionBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * POSITION_SIZE,
|
|
label: `${this._name}.position`,
|
|
});
|
|
|
|
this._texCoordBuffer = texCoord ? this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TEX_COORD_SIZE,
|
|
label: `${this._name}.texCoord`,
|
|
}) : null;
|
|
|
|
this._lightTexCoordBuffer = lightTexCoord ? this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * LIGHT_TEX_COORD_SIZE,
|
|
label: `${this._name}.lightTexCoord`,
|
|
}) : null;
|
|
|
|
this._normalBuffer = normal ? this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * NORMAL_SIZE,
|
|
label: `${this._name}.normal`,
|
|
}) : null;
|
|
|
|
this._tangentBuffer = tangent ? this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TANGENT_SIZE,
|
|
label: `${this._name}.tangent`,
|
|
}) : null;
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Resize the vertex buffer and/or add vertex attributes if it can't hold
|
|
* provided number of vertices or doesn't have provided attributes,
|
|
* potentially discarding currently stored data.
|
|
* @param props Desired buffer properties. Any unspecified property will be
|
|
* ignored.
|
|
* @returns `this` for chaining
|
|
*/
|
|
ensureSizeDiscard({
|
|
vertexCount = this.vertexCount,
|
|
texCoord = this.hasTexCoord,
|
|
lightTexCoord = this.hasLightTexCoord,
|
|
normal = this.hasNormal,
|
|
tangent = this.hasTangent,
|
|
}): VertexBuffer {
|
|
const currentVertexCount = this.vertexCount;
|
|
|
|
if (currentVertexCount < vertexCount) {
|
|
this._positionBuffer.destroy();
|
|
this._positionBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * POSITION_SIZE,
|
|
label: `${this._name}.position`,
|
|
});
|
|
}
|
|
|
|
if (currentVertexCount < vertexCount || texCoord && !this.hasTexCoord) {
|
|
this._texCoordBuffer?.destroy();
|
|
this._texCoordBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TEX_COORD_SIZE,
|
|
label: `${this._name}.texCoord`,
|
|
});
|
|
}
|
|
|
|
if (currentVertexCount < vertexCount || lightTexCoord && !this.hasLightTexCoord) {
|
|
this._lightTexCoordBuffer?.destroy();
|
|
this._lightTexCoordBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * LIGHT_TEX_COORD_SIZE,
|
|
label: `${this._name}.lightTexCoord`,
|
|
});
|
|
}
|
|
|
|
if (currentVertexCount < vertexCount || normal && !this.hasNormal) {
|
|
this._normalBuffer?.destroy();
|
|
this._normalBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * NORMAL_SIZE,
|
|
label: `${this._name}.normal`,
|
|
});
|
|
}
|
|
|
|
if (currentVertexCount < vertexCount || tangent && !this.hasTangent) {
|
|
this._tangentBuffer?.destroy();
|
|
this._tangentBuffer = this._renderer._device.createBuffer({
|
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
size: vertexCount * TANGENT_SIZE,
|
|
label: `${this._name}.tangent`,
|
|
});
|
|
}
|
|
|
|
return this;
|
|
}
|
|
}
|
|
|
|
Object.defineProperty(VertexBuffer.prototype, "type", { value: "VertexBuffer" });
|
|
|
|
export function isVertexBuffer(value: unknown): value is VertexBuffer {
|
|
return Boolean(value) && (value as VertexBuffer).type === "VertexBuffer";
|
|
}
|