Make generic object updating async

This commit is contained in:
James Seibel
2026-01-31 17:33:03 -06:00
parent 6de41cd384
commit bbf69c7911
4 changed files with 276 additions and 213 deletions
@@ -48,8 +48,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAcce
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
@@ -476,6 +474,18 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
continue;
}
// update instanced data if needed
if (useInstancedRendering)
{
boxGroup.tryUpdateInstancedDataAsync();
// skip groups that haven't been uploaded yet
if (boxGroup.instancedVbos.state != InstancedVboContainer.EState.RENDER)
{
continue;
}
}
// render //
@@ -535,7 +545,6 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
// update instance data //
profiler.push("vertex setup");
boxGroup.updateVertexAttributeData();
DhApiRenderableBoxGroupShading shading = boxGroup.shading;
if (shading == null)
@@ -553,27 +562,27 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
// Bind instance data //
profiler.popPush("binding");
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceColorVbo);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instancedVbos.color);
GL32.glEnableVertexAttribArray(1);
GL32.glVertexAttribPointer(1, 4, GL32.GL_FLOAT, false, 4 * Float.BYTES, 0);
this.vertexAttribDivisor(1, 1);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceScaleVbo);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instancedVbos.scale);
GL32.glEnableVertexAttribArray(2);
this.vertexAttribDivisor(2, 1);
GL32.glVertexAttribPointer(2, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceChunkPosVbo);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instancedVbos.chunkPos);
GL32.glEnableVertexAttribArray(3);
this.vertexAttribDivisor(3, 1);
GL32.glVertexAttribIPointer(3, 3, GL32.GL_INT, 3 * Integer.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceSubChunkPosVbo);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instancedVbos.subChunkPos);
GL32.glEnableVertexAttribArray(4);
this.vertexAttribDivisor(4, 1);
GL32.glVertexAttribPointer(4, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceMaterialVbo);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instancedVbos.material);
GL32.glEnableVertexAttribArray(5);
this.vertexAttribDivisor(5, 1);
GL32.glVertexAttribIPointer(5, 1, GL32.GL_BYTE, Byte.BYTES, 0);
@@ -581,9 +590,9 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
// Draw instanced
profiler.popPush("render");
if (boxGroup.uploadedBoxCount > 0)
if (boxGroup.instancedVbos.uploadedBoxCount > 0)
{
GL32.glDrawElementsInstanced(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0, boxGroup.uploadedBoxCount);
GL32.glDrawElementsInstanced(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0, boxGroup.instancedVbos.uploadedBoxCount);
}
@@ -0,0 +1,193 @@
package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.util.List;
/**
* For use by {@link RenderableBoxGroup}
*
* @see RenderableBoxGroup
*/
public class InstancedVboContainer implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public int chunkPos = 0;
public int subChunkPos = 0;
public int scale = 0;
public int color = 0;
public int material = 0;
public int[] chunkPosData = new int[0];
public float[] subChunkPosData = new float[0];
public float[] scalingData = new float[0];
public float[] colorData = new float[0];
public int[] materialData = new int[0];
public int uploadedBoxCount = 0;
public EState state = EState.NEW;
//===========================//
// render building/uploading //
//===========================//
//region
/** needs to be done on the render thread */
public void tryRunRenderThreadSetup()
{
if (this.chunkPos == 0)
{
this.chunkPos = GLMC.glGenBuffers();
this.subChunkPos = GLMC.glGenBuffers();
this.scale = GLMC.glGenBuffers();
this.color = GLMC.glGenBuffers();
this.material = GLMC.glGenBuffers();
}
}
public void updateVertexData(List<DhApiRenderableBox> uploadBoxList)
{
int boxCount = uploadBoxList.size();
// recreate the data arrays if their size is different
if (this.uploadedBoxCount != boxCount)
{
this.uploadedBoxCount = boxCount;
this.chunkPosData = new int[boxCount * 3]; // 3 elements XYZ
this.subChunkPosData = new float[boxCount * 3]; // 3 elements XYZ
this.scalingData = new float[boxCount * 3]; // 3 elements XYZ
this.colorData = new float[boxCount * 4]; // 4 elements, RGBA
this.materialData = new int[boxCount];
}
// transformation / scaling //
for (int i = 0; i < boxCount; i++)
{
DhApiRenderableBox box = uploadBoxList.get(i);
int dataIndex = i * 3;
this.chunkPosData[dataIndex] = LodUtil.getChunkPosFromDouble(box.minPos.x);
this.chunkPosData[dataIndex + 1] = LodUtil.getChunkPosFromDouble(box.minPos.y);
this.chunkPosData[dataIndex + 2] = LodUtil.getChunkPosFromDouble(box.minPos.z);
this.subChunkPosData[dataIndex] = LodUtil.getSubChunkPosFromDouble(box.minPos.x);
this.subChunkPosData[dataIndex + 1] = LodUtil.getSubChunkPosFromDouble(box.minPos.y);
this.subChunkPosData[dataIndex + 2] = LodUtil.getSubChunkPosFromDouble(box.minPos.z);
this.scalingData[dataIndex] = (float) (box.maxPos.x - box.minPos.x);
this.scalingData[dataIndex + 1] = (float) (box.maxPos.y - box.minPos.y);
this.scalingData[dataIndex + 2] = (float) (box.maxPos.z - box.minPos.z);
}
// colors/materials //
for (int i = 0; i < boxCount; i++)
{
DhApiRenderableBox box = uploadBoxList.get(i);
Color color = box.color;
int colorIndex = i * 4;
this.colorData[colorIndex] = color.getRed() / 255.0f;
this.colorData[colorIndex + 1] = color.getGreen() / 255.0f;
this.colorData[colorIndex + 2] = color.getBlue() / 255.0f;
this.colorData[colorIndex + 3] = color.getAlpha() / 255.0f;
this.materialData[i] = box.material;
}
this.state = InstancedVboContainer.EState.READY_TO_UPLOAD;
}
public void uploadDataToGpu()
{
// Upload transformation matrices
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.chunkPos);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.chunkPosData, GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.subChunkPos);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.subChunkPosData, GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.scale);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.scalingData, GL32.GL_DYNAMIC_DRAW);
// Upload colors
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.color);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.colorData, GL32.GL_DYNAMIC_DRAW);
// Upload materials
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.material);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.materialData, GL32.GL_DYNAMIC_DRAW);
this.state = EState.RENDER;
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public void close()
{
tryDeleteBuffer(this.chunkPos);
tryDeleteBuffer(this.subChunkPos);
tryDeleteBuffer(this.scale);
tryDeleteBuffer(this.color);
tryDeleteBuffer(this.material);
}
private static void tryDeleteBuffer(int bufferId)
{
// usually unnecessary, but just in case
if (bufferId != 0)
{
GLMC.glDeleteBuffers(bufferId);
}
}
//endregion
//================//
// helper classes //
//================//
//region
public enum EState
{
NEW,
UPDATING_DATA,
READY_TO_UPLOAD,
RENDER,
ERROR,
}
//endregion
}
@@ -1,91 +0,0 @@
package com.seibel.distanthorizons.core.render.renderer.generic;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.ConcurrentMap;
/**
* @see RenderableBoxGroup
*/
public class RenderBoxArrayCache
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final int ARRAY_LENGTH_WIDTH = 24;
public static final int ARRAY_ID_WIDTH = 8;
public static final int ARRAY_LENGTH_OFFSET = 0;
public static final int ARRAY_ID_OFFSET = ARRAY_LENGTH_OFFSET + ARRAY_LENGTH_WIDTH;
public static final int ARRAY_LENGTH_MASK = (int) Math.pow(2, ARRAY_LENGTH_WIDTH) - 1;
public static final int ARRAY_ID_MASK = (int) Math.pow(2, ARRAY_ID_WIDTH) - 1;
private static final ConcurrentMap<Integer, float[]> FLOAT_ARRAY_BY_KEY = CacheBuilder.newBuilder()
// This number needs to be high enough so that
// the number of generic object groups won't cause array thrashing.
// For now 512 should be way more than needed, unless
// someone adds a boatload of random generic objects.
.maximumSize(512)
.removalListener((RemovalNotification<Integer, float[]> notification) -> { /* TODO log a warning if arrays start getting removed, that means we may need to re-think how the caching here works */ })
.build().asMap();
private static final ConcurrentMap<Integer, int[]> INT_ARRAY_BY_KEY = CacheBuilder.newBuilder()
.maximumSize(512)
.removalListener((RemovalNotification<Integer, int[]> notification) -> {})
.build().asMap();
//============//
// get arrays //
//============//
/**
* The ID parameter is to prevent returning the same array
* multiple times when the same length is requested.
*/
public static float[] getCachedFloatArray(int length, int id)
{
int key = encodeKey(length, id);
return FLOAT_ARRAY_BY_KEY.computeIfAbsent(key, (newKey) ->
{
int newLength = getLengthFromKey(newKey);
return new float[newLength];
});
}
public static int[] getCachedIntArray(int length, int id)
{
int key = encodeKey(length, id);
return INT_ARRAY_BY_KEY.computeIfAbsent(key, (newKey) ->
{
int newLength = getLengthFromKey(newKey);
return new int[newLength];
});
}
//==============//
// key encoding //
//==============//
private static int encodeKey(int arrayLength, int id)
{
if (id > Byte.MAX_VALUE)
{
throw new IndexOutOfBoundsException("The array's ID can only be 8 bytes long.");
}
int data = 0;
data |= (arrayLength & ARRAY_LENGTH_MASK);
data |= (id & ARRAY_ID_MASK) << ARRAY_ID_OFFSET;
return data;
}
private static int getLengthFromKey(int key) { return (key & ARRAY_LENGTH_MASK); }
private static int getIdFromKey(int key) { return ((key >> ARRAY_ID_OFFSET) & ARRAY_ID_MASK); }
}
@@ -5,28 +5,23 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.io.Closeable;
import java.util.*;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
@@ -36,6 +31,8 @@ public class RenderableBoxGroup
extends AbstractList<DhApiRenderableBox>
implements IDhApiRenderableBoxGroup, Closeable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@@ -70,13 +67,10 @@ public class RenderableBoxGroup
public Consumer<DhApiRenderParam> afterRenderFunc;
// instance data
public int instanceColorVbo = 0;
public int instanceMaterialVbo = 0;
public int instanceScaleVbo = 0;
public int instanceChunkPosVbo = 0;
public int instanceSubChunkPosVbo = 0;
public InstancedVboContainer instancedVbos = new InstancedVboContainer();
/** double buffering for thread safety and to prevent locking the render thread during update */
private InstancedVboContainer altInstancedVbos = new InstancedVboContainer();
public int uploadedBoxCount = -1;
//=================//
@@ -194,24 +188,52 @@ public class RenderableBoxGroup
@Override
public void triggerBoxChange() { this.vertexDataDirty = true; }
/** Does nothing if the vertex data is already up-to-date */
public void updateVertexAttributeData()
/**
* Does nothing if the vertex data is already up-to-date
* and is meaningless if using direct rendering.
*/
public void tryUpdateInstancedDataAsync()
{
// if the alt container is done, swap it in
if (this.altInstancedVbos.state == InstancedVboContainer.EState.READY_TO_UPLOAD)
{
this.altInstancedVbos.uploadDataToGpu();
// swap VBO references for rendering
InstancedVboContainer temp = this.instancedVbos;
this.instancedVbos = this.altInstancedVbos;
this.altInstancedVbos = temp;
this.vertexDataDirty = false;
return;
}
// if the vertex data is already up to date, do nothing
if (!this.vertexDataDirty)
{
return;
}
this.vertexDataDirty = false;
if (this.instanceChunkPosVbo == 0)
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor == null || executor.isTerminated())
{
this.instanceChunkPosVbo = GLMC.glGenBuffers();
this.instanceSubChunkPosVbo = GLMC.glGenBuffers();
this.instanceScaleVbo = GLMC.glGenBuffers();
this.instanceColorVbo = GLMC.glGenBuffers();
this.instanceMaterialVbo = GLMC.glGenBuffers();
return;
}
// if the alternate container is already updating, don't double-queue it
if (this.altInstancedVbos.state == InstancedVboContainer.EState.UPDATING_DATA)
{
return;
}
this.altInstancedVbos.state = InstancedVboContainer.EState.UPDATING_DATA;
this.altInstancedVbos.tryRunRenderThreadSetup();
// copy over the box list so we can upload without concurrent modification issues
this.uploadBoxList.clear();
synchronized (this.uploadBoxList)
@@ -219,69 +241,26 @@ public class RenderableBoxGroup
this.uploadBoxList.addAll(this.boxList);
}
int boxCount = this.uploadBoxList.size();
this.uploadedBoxCount = boxCount;
// transformation / scaling //
int[] chunkPosData = RenderBoxArrayCache.getCachedIntArray(boxCount * 3, 0);
float[] subChunkPosData = RenderBoxArrayCache.getCachedFloatArray(boxCount * 3, 1);
float[] scalingData = RenderBoxArrayCache.getCachedFloatArray(boxCount * 3, 2);
for (int i = 0; i < boxCount; i++)
try
{
DhApiRenderableBox box = this.uploadBoxList.get(i);
int dataIndex = i * 3;
chunkPosData[dataIndex] = LodUtil.getChunkPosFromDouble(box.minPos.x);
chunkPosData[dataIndex + 1] = LodUtil.getChunkPosFromDouble(box.minPos.y);
chunkPosData[dataIndex + 2] = LodUtil.getChunkPosFromDouble(box.minPos.z);
subChunkPosData[dataIndex] = LodUtil.getSubChunkPosFromDouble(box.minPos.x);
subChunkPosData[dataIndex + 1] = LodUtil.getSubChunkPosFromDouble(box.minPos.y);
subChunkPosData[dataIndex + 2] = LodUtil.getSubChunkPosFromDouble(box.minPos.z);
scalingData[dataIndex] = (float) (box.maxPos.x - box.minPos.x);
scalingData[dataIndex + 1] = (float) (box.maxPos.y - box.minPos.y);
scalingData[dataIndex + 2] = (float) (box.maxPos.z - box.minPos.z);
executor.runTask(() ->
{
try
{
this.altInstancedVbos.updateVertexData(this.uploadBoxList);
}
catch (Exception e)
{
LOGGER.error("Unexpected error updating instanced VBO data for: ["+this+"], error: ["+e.getMessage()+"].", e);
this.altInstancedVbos.state = InstancedVboContainer.EState.ERROR;
}
});
}
// colors/materials //
float[] colorData = RenderBoxArrayCache.getCachedFloatArray(boxCount * 4, 3);
int[] materialData = RenderBoxArrayCache.getCachedIntArray(boxCount, 4);
for (int i = 0; i < boxCount; i++)
catch (RejectedExecutionException ignore)
{
DhApiRenderableBox box = this.uploadBoxList.get(i);
Color color = box.color;
int colorIndex = i * 4;
colorData[colorIndex] = color.getRed() / 255.0f;
colorData[colorIndex + 1] = color.getGreen() / 255.0f;
colorData[colorIndex + 2] = color.getBlue() / 255.0f;
colorData[colorIndex + 3] = color.getAlpha() / 255.0f;
materialData[i] = box.material;
// the executor was shut down, it should be back up shortly and able to accept new jobs
this.altInstancedVbos.state = InstancedVboContainer.EState.NEW;
}
// Upload transformation matrices
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceChunkPosVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, chunkPosData, GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceSubChunkPosVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, subChunkPosData, GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceScaleVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, scalingData, GL32.GL_DYNAMIC_DRAW);
// Upload colors
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceColorVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, colorData, GL32.GL_DYNAMIC_DRAW);
// Upload materials
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceMaterialVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, materialData, GL32.GL_DYNAMIC_DRAW);
}
//endregion
@@ -362,42 +341,15 @@ public class RenderableBoxGroup
//region
@Override
public String toString() { return "ID:["+this.id+"], pos:["+this.originBlockPos.x+","+this.originBlockPos.y+","+this.originBlockPos.z+"], size:["+this.size()+"], active:["+this.active+"]"; }
public String toString() { return "["+this.resourceLocationNamespace+":"+this.resourceLocationPath+"] ID:["+this.id+"], pos:["+this.originBlockPos.x+","+this.originBlockPos.y+","+this.originBlockPos.z+"], size:["+this.size()+"], active:["+this.active+"]"; }
@Override
public void close()
{
GLProxy.queueRunningOnRenderThread(() ->
{
if (this.instanceChunkPosVbo != 0)
{
GLMC.glDeleteBuffers(this.instanceChunkPosVbo);
this.instanceChunkPosVbo = 0;
}
if (this.instanceSubChunkPosVbo != 0)
{
GLMC.glDeleteBuffers(this.instanceSubChunkPosVbo);
this.instanceSubChunkPosVbo = 0;
}
if (this.instanceScaleVbo != 0)
{
GLMC.glDeleteBuffers(this.instanceScaleVbo);
this.instanceScaleVbo = 0;
}
if (this.instanceColorVbo != 0)
{
GLMC.glDeleteBuffers(this.instanceColorVbo);
this.instanceColorVbo = 0;
}
if (this.instanceMaterialVbo != 0)
{
GLMC.glDeleteBuffers(this.instanceMaterialVbo);
this.instanceMaterialVbo = 0;
}
this.instancedVbos.close();
this.altInstancedVbos.close();
});
}