From 6dd471bc32355f6affda373380acea52be644aaf Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Thu, 14 Apr 2022 19:10:04 +0100 Subject: [PATCH] Pre-allocate voxel mesh buffer, ~3x faster --- src/block_mesh.ts | 59 +++++++++++++++++----- src/buffer.ts | 9 ++++ src/renderer.ts | 39 +++++++++------ src/voxel_mesh.ts | 121 +++++++++++++++++++++++++++++----------------- 4 files changed, 156 insertions(+), 72 deletions(-) diff --git a/src/block_mesh.ts b/src/block_mesh.ts index 39fec2f..1aa149f 100644 --- a/src/block_mesh.ts +++ b/src/block_mesh.ts @@ -1,7 +1,8 @@ import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner'; -import { Voxel, VoxelMesh } from './voxel_mesh'; +import { FACES_PER_VOXEL, VERTICES_PER_FACE, Voxel, VoxelMesh, VoxelMeshBufferComponentOffsets } from './voxel_mesh'; import { BlockAtlas, BlockInfo } from './block_atlas'; -import { ColourSpace, AppError } from './util'; +import { ColourSpace, AppError, ASSERT } from './util'; +import { ComponentSize } from './buffer'; import { Renderer } from './renderer'; interface Block { @@ -70,21 +71,53 @@ export class BlockMesh { } public createBuffer() { - const buffer = Renderer.Get._voxelBuffer.copy(); + ASSERT(this._blocks.length === this._voxelMesh.getVoxelCount()); - const blockTexcoords: number[] = []; - for (const block of this._blocks) { - const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west']; - for (const face of faceOrder) { - for (let i = 0; i < 4; ++i) { - const texcoord = block.blockInfo.faces[face].texcoord; - blockTexcoords.push(texcoord.u, texcoord.v); + const numBlocks = this._blocks.length; + const newBuffer = { + position: { + numComponents: ComponentSize.POSITION, + data: Renderer.Get._voxelBufferRaw!.position.data, + }, + colour: { + numComponents: ComponentSize.COLOUR, + data: Renderer.Get._voxelBufferRaw!.colour.data, + }, + occlusion: { + numComponents: ComponentSize.OCCLUSION, + data: Renderer.Get._voxelBufferRaw!.occlusion.data, + }, + texcoord: { + numComponents: ComponentSize.TEXCOORD, + data: Renderer.Get._voxelBufferRaw!.texcoord.data, + }, + normal: { + numComponents: ComponentSize.NORMAL, + data: Renderer.Get._voxelBufferRaw!.normal.data, + }, + indices: { + numComponents: ComponentSize.INDICES, + data: Renderer.Get._voxelBufferRaw!.indices.data, + }, + blockTexcoord: { + numComponents: ComponentSize.TEXCOORD, + data: new Float32Array(numBlocks * VoxelMeshBufferComponentOffsets.TEXCOORD), + }, + }; + + const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west']; + let insertIndex = 0; + for (let i = 0; i < numBlocks; ++i) { + for (let f = 0; f < FACES_PER_VOXEL; ++f) { + const faceName = faceOrder[f]; + const texcoord = this._blocks[i].blockInfo.faces[faceName].texcoord; + for (let v = 0; v < VERTICES_PER_FACE; ++v) { + newBuffer.blockTexcoord.data[insertIndex++] = texcoord.u; + newBuffer.blockTexcoord.data[insertIndex++] = texcoord.v; } } } - buffer.attachNewAttribute({ name: 'blockTexcoord', numComponents: 2 }, blockTexcoords); - buffer.removeAttribute('colour'); - return buffer; + return newBuffer; } } diff --git a/src/buffer.ts b/src/buffer.ts index bb9524b..531aa13 100644 --- a/src/buffer.ts +++ b/src/buffer.ts @@ -3,6 +3,15 @@ import { ASSERT } from './util'; import * as twgl from 'twgl.js'; +export namespace ComponentSize { + export const TEXCOORD = 2; + export const POSITION = 3; + export const COLOUR = 3; + export const NORMAL = 3; + export const INDICES = 3; + export const OCCLUSION = 4; +} + export interface Attribute { name: string, numComponents: number diff --git a/src/renderer.ts b/src/renderer.ts index d2ac049..48c8075 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -48,8 +48,9 @@ export class Renderer { buffer: RenderBuffer, material: (SolidMaterial | (TexturedMaterial & { texture: WebGLTexture })) }>; - public _voxelBuffer: RenderBuffer; - private _blockBuffer: RenderBuffer; + public _voxelBuffer?: twgl.BufferInfo; + public _voxelBufferRaw?: any; + private _blockBuffer?: twgl.BufferInfo; private _debugBuffers: { [meshType: string]: { [bufferComponent: string]: RenderBuffer } }; private _isGridComponentEnabled: { [bufferComponent: string]: boolean }; @@ -67,8 +68,6 @@ export class Renderer { this._modelsAvailable = 0; this._materialBuffers = []; - this._voxelBuffer = new RenderBuffer([]); - this._blockBuffer = new RenderBuffer([]); this._debugBuffers = {}; this._debugBuffers[MeshType.None] = {}; @@ -191,7 +190,9 @@ export class Renderer { LOG('Using voxel mesh'); LOG(voxelMesh); - this._voxelBuffer = voxelMesh.createBuffer(ambientOcclusionEnabled); + + this._voxelBufferRaw = voxelMesh.createBuffer(ambientOcclusionEnabled); + this._voxelBuffer = twgl.createBufferInfoFromArrays(this._gl, this._voxelBufferRaw); this._voxelSize = voxelMesh?.getVoxelSize(); // this._translate = new Vector3(0, voxelMesh.getBounds().getDimensions().y/2 * voxelMesh.getVoxelSize(), 0); @@ -217,7 +218,7 @@ export class Renderer { LOG('Using block mesh'); LOG(blockMesh); - this._blockBuffer = blockMesh.createBuffer(); + this._blockBuffer = twgl.createBufferInfoFromArrays(this._gl, blockMesh.createBuffer()); this._voxelSize = blockMesh.getVoxelMesh().getVoxelSize(); this._atlasTexture = twgl.createTexture(this._gl, { @@ -275,21 +276,35 @@ export class Renderer { } private _drawVoxelMesh() { - this._drawRegister(this._voxelBuffer, ShaderManager.Get.voxelProgram, { + const shader = ShaderManager.Get.voxelProgram; + const uniforms = { u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(), u_voxelSize: this._voxelSize, u_gridOffset: this._gridOffset.toArray(), - }); + }; + if (this._voxelBuffer) { + this._gl.useProgram(shader.program); + twgl.setBuffersAndAttributes(this._gl, shader, this._voxelBuffer); + twgl.setUniforms(shader, uniforms); + this._gl.drawElements(this._gl.TRIANGLES, this._voxelBuffer.numElements, this._gl.UNSIGNED_INT, 0); + } } private _drawBlockMesh() { - this._drawRegister(this._blockBuffer, ShaderManager.Get.blockProgram, { + const shader = ShaderManager.Get.blockProgram; + const uniforms = { u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(), u_texture: this._atlasTexture, u_voxelSize: this._voxelSize, u_atlasSize: BlockAtlas.Get.getAtlasSize(), u_gridOffset: this._gridOffset.toArray(), - }); + }; + if (this._blockBuffer) { + this._gl.useProgram(shader.program); + twgl.setBuffersAndAttributes(this._gl, shader, this._blockBuffer); + twgl.setUniforms(shader, uniforms); + this._gl.drawElements(this._gl.TRIANGLES, this._blockBuffer.numElements, this._gl.UNSIGNED_INT, 0); + } } // ///////////////////////////////////////////////////////////////////////// @@ -306,10 +321,6 @@ export class Renderer { } } - private static _getNeighbourIndex(neighbour: Vector3) { - return 9*(neighbour.x+1) + 3*(neighbour.y+1) + (neighbour.z+1); - } - private _setupScene() { twgl.resizeCanvasToDisplaySize( this._gl.canvas); this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height); diff --git a/src/voxel_mesh.ts b/src/voxel_mesh.ts index 9a818d2..e2523d9 100644 --- a/src/voxel_mesh.ts +++ b/src/voxel_mesh.ts @@ -1,5 +1,4 @@ -import { RenderBuffer, AttributeData } from './buffer'; -import { AppConfig } from './config'; +import { RenderBuffer, AttributeData, ComponentSize } from './buffer'; import { GeometryTemplates } from './geometry'; import { HashMap } from './hash_map'; import { Mesh } from './mesh'; @@ -8,6 +7,20 @@ import { TextureFiltering } from './texture'; import { Bounds, RGB } from './util'; import { Vector3 } from './vector'; +export const FACES_PER_VOXEL = 6; +export const VERTICES_PER_FACE = 4; +const INDICES_PER_VOXEL = 24; +const COMPONENT_PER_SIZE_OFFSET = FACES_PER_VOXEL * VERTICES_PER_FACE; + +export namespace VoxelMeshBufferComponentOffsets { + export const TEXCOORD = ComponentSize.TEXCOORD * COMPONENT_PER_SIZE_OFFSET; + export const POSITION = ComponentSize.POSITION * COMPONENT_PER_SIZE_OFFSET; + export const COLOUR = ComponentSize.COLOUR * COMPONENT_PER_SIZE_OFFSET; + export const NORMAL = ComponentSize.NORMAL * COMPONENT_PER_SIZE_OFFSET; + export const INDICES = ComponentSize.INDICES * COMPONENT_PER_SIZE_OFFSET; + export const OCCLUSION = ComponentSize.OCCLUSION * COMPONENT_PER_SIZE_OFFSET; +} + export interface Voxel { position: Vector3; colour: RGB; @@ -94,57 +107,75 @@ export class VoxelMesh { return this._bounds; } + public getVoxelCount() { + return this._voxels.length; + } + // ////////////////////////////////////////////////////////////////////////// public createBuffer(ambientOcclusionEnabled: boolean) { - const buffer = new RenderBuffer([ - { name: 'position', numComponents: 3 }, - { name: 'colour', numComponents: 3 }, - { name: 'occlusion', numComponents: 4 }, - { name: 'texcoord', numComponents: 2 }, - { name: 'normal', numComponents: 3 }, - ]); + const numVoxels = this._voxels.length; + const newBuffer = { + position: { + numComponents: ComponentSize.POSITION, + data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.POSITION), + }, + colour: { + numComponents: ComponentSize.COLOUR, + data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.COLOUR), + }, + occlusion: { + numComponents: ComponentSize.OCCLUSION, + data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.OCCLUSION).fill(1.0), + }, + texcoord: { + numComponents: ComponentSize.TEXCOORD, + data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.TEXCOORD), + }, + normal: { + numComponents: ComponentSize.NORMAL, + data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.NORMAL), + }, + indices: { + numComponents: ComponentSize.INDICES, + data: new Uint32Array(numVoxels * VoxelMeshBufferComponentOffsets.INDICES), + }, + }; - for (const voxel of this._voxels) { - // Each vertex of a face needs the occlusion data for the other 3 vertices - // in it's face, not just itself. Also flatten occlusion data. - let occlusions: number[]; + const cube: AttributeData = GeometryTemplates.getBoxBufferData(new Vector3(0, 0, 0)); + for (let i = 0; i < numVoxels; ++i) { + const voxel = this._voxels[i]; + const voxelColourArray = voxel.colour.toArray(); + const voxelPositionArray = voxel.position.toArray(); + + for (let j = 0; j < VoxelMeshBufferComponentOffsets.POSITION; ++j) { + newBuffer.position.data[i * VoxelMeshBufferComponentOffsets.POSITION + j] = cube.custom.position[j] + voxelPositionArray[j % 3]; + } + + for (let j = 0; j < VoxelMeshBufferComponentOffsets.COLOUR; ++j) { + newBuffer.colour.data[i * VoxelMeshBufferComponentOffsets.COLOUR + j] = voxelColourArray[j % 3]; + } + + for (let j = 0; j < VoxelMeshBufferComponentOffsets.NORMAL; ++j) { + newBuffer.normal.data[i * VoxelMeshBufferComponentOffsets.NORMAL + j] = cube.custom.normal[j]; + } + + for (let j = 0; j < VoxelMeshBufferComponentOffsets.TEXCOORD; ++j) { + newBuffer.texcoord.data[i * VoxelMeshBufferComponentOffsets.TEXCOORD + j] = cube.custom.texcoord[j]; + } + + for (let j = 0; j < VoxelMeshBufferComponentOffsets.INDICES; ++j) { + newBuffer.indices.data[i * VoxelMeshBufferComponentOffsets.INDICES + j] = cube.indices[j] + (i * INDICES_PER_VOXEL); + } + if (ambientOcclusionEnabled) { - occlusions = OcclusionManager.Get.getOcclusions(voxel.position, this); - } else { - occlusions = OcclusionManager.Get.getBlankOcclusions(); - } - - const data: AttributeData = GeometryTemplates.getBoxBufferData(voxel.position); - data.custom.occlusion = occlusions; - - data.custom.colour = []; - for (let i = 0; i < 24; ++i) { - data.custom.colour.push(voxel.colour.r, voxel.colour.g, voxel.colour.b); - } - - const faceNormals = OcclusionManager.Get.getFaceNormals(); - if (AppConfig.FACE_CULLING) { - // TODO: Optimise, enabling FACE_CULLING is slower than not bothering - for (let i = 0; i < 6; ++i) { - if (!this.isVoxelAt(Vector3.add(voxel.position, faceNormals[i]))) { - buffer.add({ - custom: { - position: data.custom.position.slice(i * 12, (i+1) * 12), - occlusion: data.custom.occlusion.slice(i * 16, (i+1) * 16), - normal: data.custom.normal.slice(i * 12, (i+1) * 12), - texcoord: data.custom.texcoord.slice(i * 8, (i+1) * 8), - colour: data.custom.colour.slice(i * 12, (i+1) * 12), - }, - indices: data.indices.slice(0, 6), - }); - } + const voxelOcclusionArray = OcclusionManager.Get.getOcclusions(voxel.position, this); + for (let j = 0; j < VoxelMeshBufferComponentOffsets.OCCLUSION; ++j) { + newBuffer.occlusion.data[i * VoxelMeshBufferComponentOffsets.OCCLUSION + j] = voxelOcclusionArray[j]; } - } else { - buffer.add(data); } } - return buffer; + return newBuffer; } }