probably fix the NPEs

start work on adaptive backend
extra sanity stuff for the render manager
massive speedup when rendering a lot of tile entities
This commit is contained in:
JozsefA 2021-01-28 14:39:34 -08:00
parent 04ccf6e738
commit b63466774b
15 changed files with 176 additions and 106 deletions

View file

@ -247,7 +247,7 @@ public abstract class KineticTileEntity extends SmartTileEntity
effects.triggerOverStressedEffect();
if (clientPacket)
CreateClient.kineticRenderer.update(this);
FastRenderDispatcher.enqueueUpdate(this);
}
public float getGeneratedSpeed() {

View file

@ -27,8 +27,8 @@ public abstract class KineticTileInstance<T extends KineticTileEntity> extends T
protected final Consumer<RotatingData> setupFunc(float speed, Direction.Axis axis) {
return data -> {
data.setBlockLight(tile.getWorld().getLightLevel(LightType.BLOCK, pos))
.setSkyLight(tile.getWorld().getLightLevel(LightType.SKY, pos))
data.setBlockLight(world.getLightLevel(LightType.BLOCK, pos))
.setSkyLight(world.getLightLevel(LightType.SKY, pos))
.setTileEntity(tile)
.setRotationalSpeed(speed)
.setRotationOffset(getRotationOffset(axis))

View file

@ -45,7 +45,6 @@ public class SingleRotatingInstance extends KineticTileInstance<KineticTileEntit
@Override
public void remove() {
rotatingModelKey.delete();
rotatingModelKey = null;
}
protected BlockState getRenderedBlockState() {

View file

@ -112,6 +112,5 @@ public class FanInstance extends KineticTileInstance<EncasedFanTileEntity> {
public void remove() {
shaft.delete();
fan.delete();
shaft = fan = null;
}
}

View file

@ -44,14 +44,13 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
@Override
protected void init() {
BlockState blockState = tile.getBlockState();
if (!AllBlocks.BELT.has(blockState))
if (!AllBlocks.BELT.has(lastState))
return;
keys = new ArrayList<>(2);
beltSlope = blockState.get(BeltBlock.SLOPE);
facing = blockState.get(BeltBlock.HORIZONTAL_FACING);
beltSlope = lastState.get(BeltBlock.SLOPE);
facing = lastState.get(BeltBlock.HORIZONTAL_FACING);
upward = beltSlope == BeltSlope.UPWARD;
diagonal = beltSlope.isDiagonal();
sideways = beltSlope == BeltSlope.SIDEWAYS;
@ -59,7 +58,7 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
alongX = facing.getAxis() == Direction.Axis.X;
alongZ = facing.getAxis() == Direction.Axis.Z;
BeltPart part = blockState.get(BeltBlock.PART);
BeltPart part = lastState.get(BeltBlock.PART);
boolean start = part == BeltPart.START;
boolean end = part == BeltPart.END;
@ -67,7 +66,7 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
AllBlockPartials beltPartial = BeltRenderer.getBeltPartial(diagonal, start, end, bottom);
SpriteShiftEntry spriteShift = BeltRenderer.getSpriteShiftEntry(diagonal, bottom);
InstancedModel<BeltData> beltModel = beltPartial.renderOnBelt(modelManager, blockState);
InstancedModel<BeltData> beltModel = beltPartial.renderOnBelt(modelManager, lastState);
Consumer<BeltData> setupFunc = setupFunc(spriteShift);
keys.add(beltModel.setupInstance(setupFunc));
@ -76,7 +75,7 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
}
if (tile.hasPulley()) {
InstancedModel<RotatingData> pulleyModel = getPulleyModel(blockState);
InstancedModel<RotatingData> pulleyModel = getPulleyModel();
pulleyKey = pulleyModel.setupInstance(setupFunc(tile.getSpeed(), getRotationAxis()));
}
@ -107,7 +106,6 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
keys.forEach(InstanceKey::delete);
keys.clear();
if (pulleyKey != null) pulleyKey.delete();
pulleyKey = null;
}
private float getScrollSpeed() {
@ -122,8 +120,8 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
return speed;
}
private InstancedModel<RotatingData> getPulleyModel(BlockState blockState) {
Direction dir = getOrientation(blockState);
private InstancedModel<RotatingData> getPulleyModel() {
Direction dir = getOrientation();
Direction.Axis axis = dir.getAxis();
@ -141,11 +139,11 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
return modelTransform;
};
return rotatingMaterial().getModel(AllBlockPartials.BELT_PULLEY, blockState, dir, ms);
return rotatingMaterial().getModel(AllBlockPartials.BELT_PULLEY, lastState, dir, ms);
}
private Direction getOrientation(BlockState blockState) {
Direction dir = blockState.get(BeltBlock.HORIZONTAL_FACING)
private Direction getOrientation() {
Direction dir = lastState.get(BeltBlock.HORIZONTAL_FACING)
.rotateY();
if (beltSlope == BeltSlope.SIDEWAYS)
dir = Direction.UP;
@ -161,8 +159,8 @@ public class BeltInstance extends KineticTileInstance<BeltTileEntity> {
BlockPos pos = tile.getPos();
data.setTileEntity(tile)
.setBlockLight(tile.getWorld().getLightLevel(LightType.BLOCK, pos))
.setSkyLight(tile.getWorld().getLightLevel(LightType.SKY, pos))
.setBlockLight(world.getLightLevel(LightType.BLOCK, pos))
.setSkyLight(world.getLightLevel(LightType.SKY, pos))
.setRotation(rotX, rotY, rotZ)
.setRotationalSpeed(getScrollSpeed())
.setRotationOffset(0)

View file

@ -35,15 +35,14 @@ public class SplitShaftInstance extends KineticTileInstance<SplitShaftTileEntity
protected void init() {
keys = new ArrayList<>(2);
BlockState state = tile.getBlockState();
Block block = state.getBlock();
final Direction.Axis boxAxis = ((IRotate) block).getRotationAxis(state);
Block block = lastState.getBlock();
final Direction.Axis boxAxis = ((IRotate) block).getRotationAxis(lastState);
float speed = tile.getSpeed();
for (Direction dir : Iterate.directionsInAxis(boxAxis)) {
InstancedModel<RotatingData> half = AllBlockPartials.SHAFT_HALF.renderOnDirectionalSouthRotating(modelManager, state, dir);
InstancedModel<RotatingData> half = AllBlockPartials.SHAFT_HALF.renderOnDirectionalSouthRotating(modelManager, lastState, dir);
float splitSpeed = speed * tile.getRotationSpeedModifier(dir);
@ -53,9 +52,8 @@ public class SplitShaftInstance extends KineticTileInstance<SplitShaftTileEntity
@Override
public void onUpdate() {
BlockState state = tile.getBlockState();
Block block = state.getBlock();
final Direction.Axis boxAxis = ((IRotate) block).getRotationAxis(state);
Block block = lastState.getBlock();
final Direction.Axis boxAxis = ((IRotate) block).getRotationAxis(lastState);
Direction[] directions = Iterate.directionsInAxis(boxAxis);
@ -80,7 +78,6 @@ public class SplitShaftInstance extends KineticTileInstance<SplitShaftTileEntity
protected void updateRotation(InstanceKey<RotatingData> key, Direction dir) {
key.modifyInstance(data -> {
Direction.Axis axis = dir.getAxis();
final BlockPos pos = tile.getPos();
data.setRotationalSpeed(tile.getSpeed() * tile.getRotationSpeedModifier(dir))
.setRotationOffset(getRotationOffset(axis))

View file

@ -37,13 +37,10 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
protected void init() {
keys = new EnumMap<>(Direction.class);
BlockState state = tile.getBlockState();
final Direction.Axis boxAxis = lastState.get(BlockStateProperties.AXIS);
final Direction.Axis boxAxis = state.get(BlockStateProperties.AXIS);
BlockPos pos = tile.getPos();
int blockLight = tile.getWorld().getLightLevel(LightType.BLOCK, pos);
int skyLight = tile.getWorld().getLightLevel(LightType.SKY, pos);
int blockLight = world.getLightLevel(LightType.BLOCK, pos);
int skyLight = world.getLightLevel(LightType.SKY, pos);
updateSourceFacing();
for (Direction direction : Iterate.directions) {
@ -51,7 +48,7 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
if (boxAxis == axis)
continue;
InstancedModel<RotatingData> shaft = AllBlockPartials.SHAFT_HALF.renderOnDirectionalSouthRotating(modelManager, state, direction);
InstancedModel<RotatingData> shaft = AllBlockPartials.SHAFT_HALF.renderOnDirectionalSouthRotating(modelManager, lastState, direction);
InstanceKey<RotatingData> key = shaft.setupInstance(data -> {
data.setBlockLight(blockLight)
@ -79,7 +76,7 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
protected void updateSourceFacing() {
if (tile.hasSource()) {
BlockPos source = tile.source.subtract(tile.getPos());
BlockPos source = tile.source.subtract(pos);
sourceFacing = Direction.getFacingFromVector(source.getX(), source.getY(), source.getZ());
} else {
sourceFacing = null;
@ -89,7 +86,6 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
@Override
public void onUpdate() {
updateSourceFacing();
BlockPos pos = tile.getPos();
for (Map.Entry<Direction, InstanceKey<RotatingData>> key : keys.entrySet()) {
key.getValue().modifyInstance(data -> {
Direction direction = key.getKey();
@ -104,7 +100,6 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
@Override
public void updateLight() {
BlockPos pos = tile.getPos();
int blockLight = tile.getWorld().getLightLevel(LightType.BLOCK, pos);
int skyLight = tile.getWorld().getLightLevel(LightType.SKY, pos);
@ -115,10 +110,7 @@ public class GearboxInstance extends KineticTileInstance<GearboxTileEntity> {
@Override
public void remove() {
for (InstanceKey<RotatingData> key : keys.values()) {
key.delete();
}
keys.values().forEach(InstanceKey::delete);
keys.clear();
}
}

View file

@ -27,18 +27,6 @@ public class CancelTileEntityRenderMixin {
private void noRenderInstancedTiles(CallbackInfoReturnable<List<TileEntity>> cir) {
List<TileEntity> tiles = cir.getReturnValue();
List<TileEntity> out = new ArrayList<>(tiles.size());
for (TileEntity tile : tiles) {
if (tile instanceof IInstanceRendered) {
IInstanceRendered instanceRendered = (IInstanceRendered) tile;
if (!instanceRendered.shouldRenderAsTE()) continue;
}
out.add(tile);
}
cir.setReturnValue(out);
tiles.removeIf(tile -> tile instanceof IInstanceRendered && !((IInstanceRendered) tile).shouldRenderAsTE());
}
}

View file

@ -1,11 +1,12 @@
package com.simibubi.create.foundation.render;
import com.simibubi.create.foundation.render.gl.Backend;
import com.simibubi.create.foundation.render.gl.GlBuffer;
import com.simibubi.create.foundation.render.gl.GlVertexArray;
import com.simibubi.create.foundation.render.instancing.VertexFormat;
import net.minecraft.client.renderer.BufferBuilder;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@ -35,7 +36,7 @@ public abstract class BufferedModel extends TemplateBuffer {
int numAttributes = getTotalShaderAttributeCount();
for (int i = 0; i <= numAttributes; i++) {
GL40.glEnableVertexAttribArray(i);
GL20.glEnableVertexAttribArray(i);
}
invariantVBO.bind(GL15.GL_ARRAY_BUFFER);
@ -44,13 +45,11 @@ public abstract class BufferedModel extends TemplateBuffer {
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, invariantSize, GL15.GL_STATIC_DRAW);
// mirror it in system memory so we can write to it
ByteBuffer constant = GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.GL_WRITE_ONLY);
Backend.MAP_BUFFER.mapBuffer(GL15.GL_ARRAY_BUFFER, invariantSize, buffer -> {
for (int i = 0; i < vertexCount; i++) {
copyVertex(constant, i);
copyVertex(buffer, i);
}
constant.rewind();
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
});
getModelFormat().informAttributes(0);

View file

@ -27,16 +27,16 @@ import net.minecraft.world.chunk.Chunk;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class FastRenderDispatcher {
public static WorldAttached<ConcurrentLinkedQueue<TileEntity>> queuedUpdates = new WorldAttached<>(ConcurrentLinkedQueue::new);
public static WorldAttached<ConcurrentLinkedQueue<TileEntity>> queuedRemovals = new WorldAttached<>(ConcurrentLinkedQueue::new);
public static WorldAttached<ConcurrentLinkedQueue<TileEntityInstance<?>>> addedLastTick = new WorldAttached<>(ConcurrentLinkedQueue::new);
public static WorldAttached<ConcurrentHashMap.KeySetView<TileEntity, Boolean>> queuedUpdates = new WorldAttached<>(ConcurrentHashMap::newKeySet);
public static WorldAttached<ConcurrentHashMap.KeySetView<TileEntity, Boolean>> queuedRemovals = new WorldAttached<>(ConcurrentHashMap::newKeySet);
public static WorldAttached<ConcurrentHashMap.KeySetView<TileEntityInstance<?>, Boolean>> addedLastTick = new WorldAttached<>(ConcurrentHashMap::newKeySet);
private static Matrix4f projectionMatrixThisFrame = null;
@ -56,18 +56,21 @@ public class FastRenderDispatcher {
ClientWorld world = Minecraft.getInstance().world;
runQueue(addedLastTick.get(world), TileEntityInstance::updateLight);
runQueue(queuedUpdates.get(world), CreateClient.kineticRenderer::update);
runQueue(queuedRemovals.get(world), CreateClient.kineticRenderer::remove);
CreateClient.kineticRenderer.clean();
runQueue(queuedUpdates.get(world), CreateClient.kineticRenderer::update);
}
private static <T> void runQueue(@Nullable Queue<T> q, Consumer<T> action) {
if (q == null) return;
private static <T> void runQueue(@Nullable ConcurrentHashMap.KeySetView<T, Boolean> changed, Consumer<T> action) {
if (changed == null) return;
while (!q.isEmpty()) {
T t = q.poll();
// because of potential concurrency issues, we make a copy of what's in the set at the time we get here
ArrayList<T> tiles = new ArrayList<>(changed);
action.accept(t);
}
tiles.forEach(action);
changed.removeAll(tiles);
}
public static void renderLayer(RenderType type, MatrixStack stack, double cameraX, double cameraY, double cameraZ) {

View file

@ -4,13 +4,16 @@ import com.simibubi.create.foundation.render.gl.shader.Shader;
import com.simibubi.create.foundation.render.gl.shader.ShaderCallback;
import com.simibubi.create.foundation.render.gl.shader.ShaderHelper;
import com.simibubi.create.foundation.render.instancing.*;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.tileentity.TileEntity;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class InstancedTileRenderer {
protected Map<TileEntity, TileEntityInstance<?>> renderers = new HashMap<>();
@ -31,12 +34,17 @@ public class InstancedTileRenderer {
return (RenderMaterial<M>) materials.get(materialType);
}
@Nullable
public <T extends TileEntity> TileEntityInstance<? super T> getInstance(T tile) {
return getInstance(tile, true);
}
@SuppressWarnings("unchecked")
@Nullable
public <T extends TileEntity> TileEntityInstance<? super T> getRenderer(T tile) {
public <T extends TileEntity> TileEntityInstance<? super T> getInstance(T tile, boolean create) {
if (renderers.containsKey(tile)) {
return (TileEntityInstance<? super T>) renderers.get(tile);
} else {
} else if (create) {
TileEntityInstance<? super T> renderer = InstancedTileRenderRegistry.instance.create(this, tile);
if (renderer != null) {
@ -45,38 +53,49 @@ public class InstancedTileRenderer {
}
return renderer;
} else {
return null;
}
}
public <T extends TileEntity> void onLightUpdate(T tile) {
if (tile instanceof IInstanceRendered) {
TileEntityInstance<? super T> renderer = getRenderer(tile);
TileEntityInstance<? super T> instance = getInstance(tile);
if (renderer != null)
renderer.updateLight();
if (instance != null)
instance.updateLight();
}
}
public <T extends TileEntity> void update(T tile) {
if (tile instanceof IInstanceRendered) {
TileEntityInstance<? super T> renderer = getRenderer(tile);
TileEntityInstance<? super T> instance = getInstance(tile);
if (renderer != null)
renderer.update();
if (instance != null)
instance.update();
}
}
public <T extends TileEntity> void remove(T tile) {
if (tile instanceof IInstanceRendered) {
TileEntityInstance<? super T> renderer = getRenderer(tile);
TileEntityInstance<? super T> instance = getInstance(tile, false);
if (renderer != null) {
renderer.remove();
if (instance != null) {
instance.remove();
renderers.remove(tile);
}
}
}
public void clean() {
// Clean up twice a second. This doesn't have to happen every tick,
// but this does need to be run to ensure we don't miss anything.
if (AnimationTickHolder.ticks % 10 == 0) {
List<TileEntity> removed = renderers.keySet().stream().filter(TileEntity::isRemoved).collect(Collectors.toList());
removed.forEach(renderers::remove);
}
}
public void invalidate() {
for (RenderMaterial<?> material : materials.values()) {
material.delete();

View file

@ -91,7 +91,7 @@ public class RenderedContraption {
if (!tileEntities.isEmpty()) {
for (TileEntity te : tileEntities) {
if (te instanceof IInstanceRendered) {
kinetics.getRenderer(te); // this is enough to instantiate the model instance
kinetics.getInstance(te); // this is enough to instantiate the model instance
}
}
}

View file

@ -0,0 +1,7 @@
package com.simibubi.create.foundation.render.gl;
import com.simibubi.create.foundation.render.gl.backend.MapBuffer;
public class Backend {
public static final MapBuffer MAP_BUFFER = MapBuffer.GL30_RANGE;
}

View file

@ -0,0 +1,58 @@
package com.simibubi.create.foundation.render.gl.backend;
import org.lwjgl.opengl.ARBMapBufferRange;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.util.function.Consumer;
public enum MapBuffer {
GL30_RANGE {
@Override
public void mapBuffer(int target, int offset, int length, Consumer<ByteBuffer> upload) {
ByteBuffer buffer = GL30.glMapBufferRange(target, offset, length, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT);
upload.accept(buffer);
buffer.rewind();
GL30.glUnmapBuffer(target);
}
},
ARB_RANGE {
@Override
public void mapBuffer(int target, int offset, int length, Consumer<ByteBuffer> upload) {
ByteBuffer buffer = ARBMapBufferRange.glMapBufferRange(target, offset, length, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT);
upload.accept(buffer);
buffer.rewind();
GL30.glUnmapBuffer(target);
}
},
GL15_MAP {
@Override
public void mapBuffer(int target, int offset, int length, Consumer<ByteBuffer> upload) {
ByteBuffer buffer = GL15.glMapBuffer(target, GL15.GL_WRITE_ONLY);
buffer.position(offset);
upload.accept(buffer);
buffer.rewind();
GL15.glUnmapBuffer(target);
}
},
UNSUPPORTED {
@Override
public void mapBuffer(int target, int offset, int length, Consumer<ByteBuffer> upload) {
throw new UnsupportedOperationException("glMapBuffer not supported");
}
};
public abstract void mapBuffer(int target, int offset, int length, Consumer<ByteBuffer> upload);
public final void mapBuffer(int target, int size, Consumer<ByteBuffer> upload) {
mapBuffer(target, 0, size, upload);
}
}

View file

@ -1,11 +1,16 @@
package com.simibubi.create.foundation.render.instancing;
import com.google.common.collect.Range;
import com.simibubi.create.foundation.render.BufferedModel;
import com.simibubi.create.foundation.render.RenderMath;
import com.simibubi.create.foundation.render.gl.Backend;
import com.simibubi.create.foundation.render.gl.GlBuffer;
import net.minecraft.client.renderer.BufferBuilder;
import org.lwjgl.opengl.*;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -23,6 +28,7 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
protected final ArrayList<InstanceKey<D>> keys = new ArrayList<>();
protected final ArrayList<D> data = new ArrayList<>();
protected int minIndexChanged = -1;
protected int maxIndexChanged = -1;
public InstancedModel(BufferBuilder buf) {
super(buf);
@ -67,10 +73,6 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
}
public void markDirty() {
minIndexChanged = 0;
}
protected void deleteInternal() {
super.deleteInternal();
instanceVBO.delete();
@ -91,7 +93,8 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
keys.get(i).index--;
}
setMinIndexChanged(key.index);
markIndexChanged(index - 1);
maxIndexChanged = keys.size() - 1;
key.invalidate();
}
@ -103,7 +106,7 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
edit.accept(data);
setMinIndexChanged(key.index);
markIndexChanged(key.index);
}
public synchronized InstanceKey<D> setupInstance(Consumer<D> setup) {
@ -114,16 +117,22 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
data.add(instanceData);
keys.add(key);
setMinIndexChanged(key.index);
markIndexChanged(key.index);
return key;
}
protected void setMinIndexChanged(int index) {
protected void markIndexChanged(int index) {
if (minIndexChanged < 0) {
minIndexChanged = index;
} else {
minIndexChanged = Math.min(minIndexChanged, index);
} else if (index < minIndexChanged) {
minIndexChanged = index;
}
if (maxIndexChanged < 0) {
maxIndexChanged = index;
} else if (index > maxIndexChanged) {
maxIndexChanged = index;
}
}
@ -161,16 +170,17 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, instanceSize, GL15.GL_STATIC_DRAW);
glBufferSize = instanceSize;
minIndexChanged = 0;
maxIndexChanged = data.size() - 1;
}
ByteBuffer buffer = GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.GL_WRITE_ONLY);
int offset = minIndexChanged * stride;
int length = (1 + maxIndexChanged - minIndexChanged) * stride;
buffer.position(stride * minIndexChanged);
for (int i = minIndexChanged; i < data.size(); i++) {
Backend.MAP_BUFFER.mapBuffer(GL15.GL_ARRAY_BUFFER, offset, length, buffer -> {
for (int i = minIndexChanged; i <= maxIndexChanged; i++) {
data.get(i).write(buffer);
}
buffer.rewind();
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
});
glInstanceCount = data.size();
@ -184,5 +194,6 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
instanceVBO.unbind(GL15.GL_ARRAY_BUFFER);
minIndexChanged = -1;
maxIndexChanged = -1;
}
}