Fix poor design choices resulting in memory leaks

- Add ShadeSeparatedBufferedData which is returned by buffering
utilities and can be released
- Remove ShadeSeparatedBufferBuilder and add ModelUtil#endAndCombine
- Add VertexList#delete
- Replace all usage of MemoryTracker with MemoryUtil
- Add FlwUtil#copyBuffer
- Turn most BlockModel constructors into static methods
- Add BakedModelBuilder#toModel
This commit is contained in:
PepperCode1 2023-07-03 13:19:37 -07:00
parent 873facfd34
commit e79f6bcbf6
14 changed files with 188 additions and 131 deletions

View file

@ -25,11 +25,11 @@ public interface Material<D extends InstanceData> {
Instancer<D> model(Object key, Supplier<Model> modelSupplier);
default Instancer<D> getModel(PartialModel partial, BlockState referenceState) {
return model(partial, () -> new BlockModel(partial.get(), referenceState));
return model(partial, () -> BlockModel.of(partial.get(), referenceState));
}
default Instancer<D> getModel(PartialModel partial) {
return model(partial, () -> new BlockModel(partial.get(), Blocks.AIR.defaultBlockState()));
return model(partial, () -> BlockModel.of(partial.get(), Blocks.AIR.defaultBlockState()));
}
default Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir) {
@ -37,10 +37,10 @@ public interface Material<D extends InstanceData> {
}
default Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir, Supplier<PoseStack> modelTransform) {
return model(Pair.of(dir, partial), () -> new BlockModel(partial.get(), referenceState, modelTransform.get()));
return model(Pair.of(dir, partial), () -> BlockModel.of(partial.get(), referenceState, modelTransform.get()));
}
default Instancer<D> getModel(BlockState toRender) {
return model(toRender, () -> new BlockModel(toRender));
return model(toRender, () -> BlockModel.of(toRender));
}
}

View file

@ -41,4 +41,7 @@ public interface VertexList {
default boolean isEmpty() {
return getVertexCount() == 0;
}
default void delete() {
}
}

View file

@ -3,10 +3,10 @@ package com.jozufozu.flywheel.backend.instancing;
import java.nio.ByteBuffer;
import org.jetbrains.annotations.ApiStatus;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.backend.model.BufferBuilderExtension;
import com.jozufozu.flywheel.backend.model.DirectVertexConsumer;
import com.mojang.blaze3d.platform.MemoryTracker;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.RenderType;
@ -48,10 +48,10 @@ public class DrawBuffer {
int byteSize = format.getVertexSize() * (vertexCount + 1);
if (backingBuffer == null) {
backingBuffer = MemoryTracker.create(byteSize);
backingBuffer = MemoryUtil.memAlloc(byteSize);
}
if (byteSize > backingBuffer.capacity()) {
backingBuffer = MemoryTracker.resize(backingBuffer, byteSize);
backingBuffer = MemoryUtil.memRealloc(backingBuffer, byteSize);
}
return new DirectVertexConsumer(backingBuffer, format, vertexCount);

View file

@ -10,7 +10,6 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.core.vertex.PosTexNormalWriterUnsafe;
import com.mojang.blaze3d.platform.MemoryTracker;
public class ModelPart implements Model {
@ -29,7 +28,7 @@ public class ModelPart implements Model {
this.vertices = vertices;
}
ByteBuffer buffer = MemoryTracker.create(size());
ByteBuffer buffer = MemoryUtil.memAlloc(size());
PosTexNormalWriterUnsafe writer = Formats.POS_TEX_NORMAL.createWriter(buffer);
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
cuboid.buffer(writer);
@ -65,12 +64,6 @@ public class ModelPart implements Model {
@Override
public void delete() {
if (reader instanceof AutoCloseable closeable) {
try {
closeable.close();
} catch (Exception e) {
//
}
}
reader.delete();
}
}

View file

@ -42,7 +42,15 @@ public final class BakedModelBuilder implements Bufferable {
}
@Override
public void bufferInto(ModelBlockRenderer blockRenderer, VertexConsumer consumer, Random random) {
public void bufferInto(VertexConsumer consumer, ModelBlockRenderer blockRenderer, Random random) {
blockRenderer.tesselateBlock(renderWorld, model, referenceState, BlockPos.ZERO, poseStack, consumer, false, random, 42, OverlayTexture.NO_OVERLAY, VirtualEmptyModelData.INSTANCE);
}
public BlockModel toModel(String name) {
return BlockModel.of(this, name);
}
public BlockModel toModel() {
return toModel(referenceState.toString());
}
}

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.core.model;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.Formats;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
@ -17,28 +21,55 @@ public class BlockModel implements Model {
private final VertexList reader;
private final String name;
public BlockModel(BlockState state) {
this(Minecraft.getInstance()
public BlockModel(ByteBuffer vertexBuffer, BufferBuilder.DrawState drawState, int unshadedStartVertex, String name) {
if (drawState.format() != DefaultVertexFormat.BLOCK) {
throw new RuntimeException("Cannot use buffered data with non-block format '" + drawState.format() + "'");
}
reader = Formats.BLOCK.createReader(vertexBuffer, drawState.vertexCount(), unshadedStartVertex);
this.name = name;
}
public BlockModel(ByteBuffer vertexBuffer, BufferBuilder.DrawState drawState, String name) {
if (drawState.format() != DefaultVertexFormat.BLOCK) {
throw new RuntimeException("Cannot use buffered data with non-block format '" + drawState.format() + "'");
}
reader = Formats.BLOCK.createReader(vertexBuffer, drawState.vertexCount());
this.name = name;
}
public BlockModel(ShadeSeparatedBufferedData data, String name) {
this(data.vertexBuffer(), data.drawState(), data.unshadedStartVertex(), name);
}
public static BlockModel of(Bufferable bufferable, String name) {
ShadeSeparatedBufferedData data = bufferable.build();
BlockModel model = new BlockModel(data, name);
data.release();
return model;
}
public static BlockModel of(BakedModel model, BlockState referenceState) {
ShadeSeparatedBufferedData data = ModelUtil.getBufferedData(model, referenceState);
BlockModel blockModel = new BlockModel(data, referenceState.toString());
data.release();
return blockModel;
}
public static BlockModel of(BlockState state) {
return of(Minecraft.getInstance()
.getBlockRenderer()
.getBlockModel(state), state);
}
public BlockModel(BakedModel model, BlockState referenceState) {
this(new BakedModelBuilder(model).withReferenceState(referenceState), referenceState.toString());
}
public BlockModel(BakedModel model, BlockState referenceState, PoseStack ms) {
this(new BakedModelBuilder(model).withReferenceState(referenceState)
.withPoseStack(ms), referenceState.toString());
}
public BlockModel(Bufferable bufferable, String name) {
this(bufferable.build(), name);
}
public BlockModel(ShadeSeparatedBufferBuilder bufferBuilder, String name) {
this.name = name;
reader = Formats.BLOCK.createReader(bufferBuilder);
public static BlockModel of(BakedModel model, BlockState referenceState, PoseStack ms) {
ShadeSeparatedBufferedData data = ModelUtil.getBufferedData(model, referenceState, ms);
BlockModel blockModel = new BlockModel(data, referenceState.toString());
data.release();
return blockModel;
}
@Override
@ -63,12 +94,6 @@ public class BlockModel implements Model {
@Override
public void delete() {
if (reader instanceof AutoCloseable closeable) {
try {
closeable.close();
} catch (Exception e) {
//
}
}
reader.delete();
}
}

View file

@ -8,12 +8,12 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
/**
* An interface for objects that can "rendered" into a BufferBuilder.
* An interface for objects that can buffered into a VertexConsumer.
*/
public interface Bufferable {
void bufferInto(ModelBlockRenderer renderer, VertexConsumer consumer, Random random);
void bufferInto(VertexConsumer consumer, ModelBlockRenderer renderer, Random random);
default ShadeSeparatedBufferBuilder build() {
return ModelUtil.getBufferBuilder(this);
default ShadeSeparatedBufferedData build() {
return ModelUtil.getBufferedData(this);
}
}

View file

@ -1,16 +1,20 @@
package com.jozufozu.flywheel.core.model;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Random;
import java.util.function.Supplier;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.model.BufferBuilderExtension;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.BufferBuilder.DrawState;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
@ -48,39 +52,52 @@ public class ModelUtil {
return dispatcher;
}
public static ShadeSeparatedBufferBuilder getBufferBuilder(Bufferable bufferable) {
public static ShadeSeparatedBufferedData endAndCombine(BufferBuilder shadedBuilder, BufferBuilder unshadedBuilder) {
int unshadedStartVertex = ((BufferBuilderExtension) shadedBuilder).flywheel$getVertices();
unshadedBuilder.end();
Pair<DrawState, ByteBuffer> unshadedData = unshadedBuilder.popNextBuffer();
((BufferBuilderExtension) shadedBuilder).flywheel$appendBufferUnsafe(unshadedData.getSecond());
shadedBuilder.end();
Pair<DrawState, ByteBuffer> data = shadedBuilder.popNextBuffer();
return new ShadeSeparatedBufferedData.NativeImpl(data.getSecond(), data.getFirst(), unshadedStartVertex);
}
public static ShadeSeparatedBufferedData getBufferedData(Bufferable bufferable) {
ModelBlockRenderer blockRenderer = VANILLA_RENDERER.getModelRenderer();
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
objects.begin();
bufferable.bufferInto(blockRenderer, objects.shadeSeparatingWrapper, objects.random);
bufferable.bufferInto(objects.shadeSeparatingWrapper, blockRenderer, objects.random);
objects.end();
return objects.separatedBufferBuilder;
return objects.end();
}
public static ShadeSeparatedBufferBuilder getBufferBuilder(BakedModel model, BlockState referenceState, PoseStack poseStack) {
public static ShadeSeparatedBufferedData getBufferedData(BakedModel model, BlockState referenceState) {
return new BakedModelBuilder(model).withReferenceState(referenceState)
.build();
}
public static ShadeSeparatedBufferedData getBufferedData(BakedModel model, BlockState referenceState, PoseStack poseStack) {
return new BakedModelBuilder(model).withReferenceState(referenceState)
.withPoseStack(poseStack)
.build();
}
public static ShadeSeparatedBufferBuilder getBufferBuilder(BlockAndTintGetter renderWorld, BakedModel model, BlockState referenceState, PoseStack poseStack) {
public static ShadeSeparatedBufferedData getBufferedData(BlockAndTintGetter renderWorld, BakedModel model, BlockState referenceState, PoseStack poseStack) {
return new BakedModelBuilder(model).withReferenceState(referenceState)
.withPoseStack(poseStack)
.withRenderWorld(renderWorld)
.build();
}
public static ShadeSeparatedBufferBuilder getBufferBuilderFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection<StructureTemplate.StructureBlockInfo> blocks) {
public static ShadeSeparatedBufferedData getBufferedDataFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection<StructureTemplate.StructureBlockInfo> blocks) {
return new WorldModelBuilder(layer).withRenderWorld(renderWorld)
.withBlocks(blocks)
.build();
}
public static ShadeSeparatedBufferBuilder getBufferBuilderFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection<StructureTemplate.StructureBlockInfo> blocks, PoseStack poseStack) {
public static ShadeSeparatedBufferedData getBufferedDataFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection<StructureTemplate.StructureBlockInfo> blocks, PoseStack poseStack) {
return new WorldModelBuilder(layer).withRenderWorld(renderWorld)
.withBlocks(blocks)
.withPoseStack(poseStack)
@ -101,20 +118,18 @@ public class ModelUtil {
private static class ThreadLocalObjects {
public final Random random = new Random();
public final ShadeSeparatingVertexConsumer shadeSeparatingWrapper = new ShadeSeparatingVertexConsumer();
public final ShadeSeparatedBufferBuilder separatedBufferBuilder = new ShadeSeparatedBufferBuilder(512);
public final BufferBuilder shadedBuilder = new BufferBuilder(512);
public final BufferBuilder unshadedBuilder = new BufferBuilder(512);
private void begin() {
this.separatedBufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
this.shadedBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
this.unshadedBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
this.shadeSeparatingWrapper.prepare(this.separatedBufferBuilder, this.unshadedBuilder);
this.shadeSeparatingWrapper.prepare(this.shadedBuilder, this.unshadedBuilder);
}
private void end() {
private ShadeSeparatedBufferedData end() {
this.shadeSeparatingWrapper.clear();
this.unshadedBuilder.end();
this.separatedBufferBuilder.appendUnshadedVertices(this.unshadedBuilder);
this.separatedBufferBuilder.end();
return ModelUtil.endAndCombine(shadedBuilder, unshadedBuilder);
}
}
}

View file

@ -1,25 +0,0 @@
package com.jozufozu.flywheel.core.model;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.backend.model.BufferBuilderExtension;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.datafixers.util.Pair;
public class ShadeSeparatedBufferBuilder extends BufferBuilder {
protected int unshadedStartVertex;
public ShadeSeparatedBufferBuilder(int capacity) {
super(capacity);
}
public void appendUnshadedVertices(BufferBuilder unshadedBuilder) {
Pair<DrawState, ByteBuffer> data = unshadedBuilder.popNextBuffer();
unshadedStartVertex = ((BufferBuilderExtension) this).flywheel$getVertices();
((BufferBuilderExtension) this).flywheel$appendBufferUnsafe(data.getSecond());
}
public int getUnshadedStartVertex() {
return unshadedStartVertex;
}
}

View file

@ -0,0 +1,51 @@
package com.jozufozu.flywheel.core.model;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferBuilder.DrawState;
public interface ShadeSeparatedBufferedData {
ByteBuffer vertexBuffer();
BufferBuilder.DrawState drawState();
int unshadedStartVertex();
void release();
static final class NativeImpl implements ShadeSeparatedBufferedData {
private final ByteBuffer vertexBuffer;
private final BufferBuilder.DrawState drawState;
private final int unshadedStartVertex;
public NativeImpl(ByteBuffer vertexBuffer, BufferBuilder.DrawState drawState, int unshadedStartVertex) {
this.vertexBuffer = FlwUtil.copyBuffer(vertexBuffer);
this.drawState = drawState;
this.unshadedStartVertex = unshadedStartVertex;
}
@Override
public ByteBuffer vertexBuffer() {
return vertexBuffer;
}
@Override
public DrawState drawState() {
return drawState;
}
@Override
public int unshadedStartVertex() {
return unshadedStartVertex;
}
@Override
public void release() {
MemoryUtil.memFree(vertexBuffer);
}
}
}

View file

@ -35,7 +35,7 @@ public final class WorldModelBuilder implements Bufferable {
}
@Override
public void bufferInto(ModelBlockRenderer modelRenderer, VertexConsumer consumer, Random random) {
public void bufferInto(VertexConsumer consumer, ModelBlockRenderer modelRenderer, Random random) {
ForgeHooksClient.setRenderType(this.layer);
ModelBlockRenderer.enableCaching();
for (StructureTemplate.StructureBlockInfo info : this.blocks) {
@ -80,7 +80,7 @@ public final class WorldModelBuilder implements Bufferable {
return this;
}
public BlockModel intoMesh(String name) {
return new BlockModel(this, name);
public BlockModel toModel(String name) {
return BlockModel.of(this, name);
}
}

View file

@ -1,39 +1,31 @@
package com.jozufozu.flywheel.core.vertex;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.mojang.blaze3d.platform.MemoryTracker;
import com.jozufozu.flywheel.util.FlwUtil;
public abstract class AbstractVertexList implements VertexList, AutoCloseable {
public abstract class AbstractVertexList implements VertexList {
protected final ByteBuffer contents;
protected final long base;
protected final int vertexCount;
protected AbstractVertexList(ByteBuffer copyFrom, int vertexCount) {
this.contents = MemoryTracker.create(copyFrom.capacity());
this.contents = FlwUtil.copyBuffer(copyFrom);
this.vertexCount = vertexCount;
this.base = MemoryUtil.memAddress(this.contents);
init(copyFrom);
}
private void init(ByteBuffer copyFrom) {
this.contents.order(copyFrom.order());
this.contents.put(copyFrom);
((Buffer) this.contents).flip();
}
@Override
public void close() {
MemoryUtil.memFree(contents);
}
@Override
public int getVertexCount() {
return vertexCount;
}
@Override
public void delete() {
MemoryUtil.memFree(contents);
}
}

View file

@ -2,14 +2,9 @@ package com.jozufozu.flywheel.core.vertex;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.layout.CommonItems;
import com.jozufozu.flywheel.core.model.ShadeSeparatedBufferBuilder;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.datafixers.util.Pair;
public class BlockVertex implements VertexType {
@ -37,6 +32,10 @@ public class BlockVertex implements VertexType {
return new BlockVertexListUnsafe(buffer, vertexCount);
}
public BlockVertexListUnsafe.Shaded createReader(ByteBuffer buffer, int vertexCount, int unshadedStartVertex) {
return new BlockVertexListUnsafe.Shaded(buffer, vertexCount, unshadedStartVertex);
}
@Override
public String getShaderHeader() {
return """
@ -57,24 +56,4 @@ Vertex FLWCreateVertex() {
}
""";
}
public BlockVertexListUnsafe.Shaded createReader(ByteBuffer buffer, int vertexCount, int unshadedStartVertex) {
return new BlockVertexListUnsafe.Shaded(buffer, vertexCount, unshadedStartVertex);
}
public VertexList createReader(BufferBuilder bufferBuilder) {
// TODO: try to avoid virtual model rendering
Pair<BufferBuilder.DrawState, ByteBuffer> pair = bufferBuilder.popNextBuffer();
BufferBuilder.DrawState drawState = pair.getFirst();
if (drawState.format() != DefaultVertexFormat.BLOCK) {
throw new RuntimeException("Cannot use BufferBuilder with " + drawState.format());
}
if (bufferBuilder instanceof ShadeSeparatedBufferBuilder separated) {
return createReader(pair.getSecond(), drawState.vertexCount(), separated.getUnshadedStartVertex());
} else {
return createReader(pair.getSecond(), drawState.vertexCount());
}
}
}

View file

@ -1,9 +1,12 @@
package com.jozufozu.flywheel.util;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Stream;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.mixin.BlockEntityRenderDispatcherAccessor;
import net.minecraft.client.Minecraft;
@ -73,4 +76,17 @@ public class FlwUtil {
public static <R> Stream<R> mapValues(Map<?, R> map) {
return map.values().stream();
}
/**
* The returned buffer is backed by native memory and will cause a memory leak if not freed using {@link MemoryUtil#memFree(java.nio.Buffer)}.
*/
public static ByteBuffer copyBuffer(ByteBuffer buffer) {
int pos = buffer.position();
ByteBuffer copy = MemoryUtil.memAlloc(buffer.remaining());
copy.order(buffer.order());
copy.put(buffer);
buffer.position(pos);
copy.flip();
return copy;
}
}