1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-24 22:42:18 +00:00

Construct ByteBuffer manually in monitor renderer

In the 1.21.4 update (9277aa33e97456c94b5f7d1aa54e00e204a316dd), we
removed our DirectVertexBuffer class, and switched to vanilla's
VertexBuffer. This forced us to use MeshData and thus
ByteBufferBuilder instead of allocating the ByteBuffer ourselves.

One thing I'd missed with this is that Iris's text vertex sink API
requires us to allocate the whole buffer up-front and so the resulting
buffer has a limit of the *maximum* number of vertices rendered, not the
actual one.

This wasn't an issue on 1.21.4, as we didn't check this (I guess we just
silently rendered junk??), but for the 1.21.5 update
(a1df19667361601c7f37a71074ea7755ccc4019d) we added some extra
assertions here, which now fail on Iris.

Typically, the whole original change was is now entirely redundant, as
Vanilla has removed VertexBuffer entirely, and so we can/should work in
terms of raw ByteBuffers again.

Fixes #2219.
This commit is contained in:
Jonathan Coates 2025-06-15 18:03:59 +01:00
parent f3f43191ab
commit ec3dd328b3
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
4 changed files with 89 additions and 57 deletions

View File

@ -5,18 +5,16 @@
package dan200.computercraft.client.integration;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.irisshaders.iris.api.v0.IrisApi;
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
import net.minecraft.client.renderer.LightTexture;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.function.IntFunction;
@AutoService(ShaderMod.Provider.class)
public class IrisShaderMod implements ShaderMod.Provider {
@ -32,21 +30,20 @@ public class IrisShaderMod implements ShaderMod.Provider {
}
@Override
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder makeBuffer) {
return IrisApi.getInstance().getMinorApiRevision() >= 1
? new IrisQuadEmitter(vertexCount, makeBuffer)
: super.getQuadEmitter(vertexCount, makeBuffer);
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int quadCount, IntFunction<ByteBuffer> makeBuffer) {
return new IrisQuadEmitter(quadCount, makeBuffer);
}
private static final class IrisQuadEmitter extends DirectFixedWidthFontRenderer.QuadEmitter {
private final IrisTextVertexSink sink;
private @Nullable ByteBuffer buffer;
private IrisQuadEmitter(int vertexCount, ByteBufferBuilder builder) {
sink = IrisApi.getInstance().createTextVertexSink(vertexCount, i -> {
if (buffer != null) throw new IllegalStateException("Allocated multiple buffers");
return buffer = MemoryUtil.memByteBuffer(builder.reserve(i), i);
});
private IrisQuadEmitter(int vertexCount, IntFunction<ByteBuffer> builder) {
sink = IrisApi.getInstance().createTextVertexSink(vertexCount, builder);
}
@Override
public ByteBuffer byteBuffer() {
return sink.getUnderlyingByteBuffer();
}
@Override

View File

@ -4,11 +4,14 @@
package dan200.computercraft.client.integration;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.IntFunction;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.TERMINAL_TEXT;
/**
* Find the currently loaded shader mod (if present) and provides utilities for interacting with it.
@ -30,12 +33,12 @@ public class ShaderMod {
/**
* Get an appropriate quad emitter for use with a vertex buffer and {@link DirectFixedWidthFontRenderer} .
*
* @param vertexCount The number of vertices.
* @param buffer A function to allocate a temporary buffer.
* @param quadCount The number of quads.
* @param makeBuffer A function to allocate a temporary buffer.
* @return The quad emitter.
*/
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder buffer) {
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(buffer);
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int quadCount, IntFunction<ByteBuffer> makeBuffer) {
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(makeBuffer.apply(TERMINAL_TEXT.format().getVertexSize() * quadCount * 4));
}
public interface Provider {

View File

@ -7,7 +7,6 @@ package dan200.computercraft.client.render.monitor;
import com.mojang.blaze3d.buffers.BufferType;
import com.mojang.blaze3d.buffers.BufferUsage;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.annotations.ForgeOverride;
@ -28,7 +27,10 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.OptionalDouble;
import java.util.OptionalInt;
@ -42,7 +44,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
*/
private static final float MARGIN = (float) (MonitorBlockEntity.RENDER_MARGIN * 1.1);
private static final ByteBufferBuilder backingBufferBuilder = new ByteBufferBuilder(0x4000);
private static @Nullable ByteBuffer backingBuffer;
public MonitorBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
}
@ -129,9 +131,9 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
if (redraw) {
// Cursor, Foreground, Background+Margin
var maxVertexCount = 4 * (1 + (terminal.getWidth() * terminal.getHeight()) + ((terminal.getWidth() + 2) * (terminal.getHeight() + 2)));
backingBufferBuilder.clear();
var sink = ShaderMod.get().getQuadEmitter(maxVertexCount, backingBufferBuilder);
var maxQuadCount = 1 + (terminal.getWidth() * terminal.getHeight()) + ((terminal.getWidth() + 2) * (terminal.getHeight() + 2));
var maxVertexCount = 4 * maxQuadCount;
var sink = ShaderMod.get().getQuadEmitter(maxQuadCount, MonitorBlockEntityRenderer::getBuffer);
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin);
var vertexCountAfterBackground = sink.vertexCount();
@ -146,39 +148,48 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
throw new IllegalStateException("Drew too many vertices. Expected " + maxVertexCount + ", drew " + vertexCountAfterCursor);
}
try (var result = backingBufferBuilder.build()) {
if (result == null) {
// If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader.
renderState.indexAfterBackground = renderState.indexAfterForeground = renderState.indexAfterCursor = 0;
} else {
renderState.register();
final int indexAfterBackground, indexAfterForeground, indexAfterCursor;
if (vertexCountAfterCursor == 0) {
// If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader.
indexAfterBackground = indexAfterForeground = indexAfterCursor = 0;
} else {
renderState.register();
var commandEncoder = RenderSystem.getDevice().createCommandEncoder();
var commandEncoder = RenderSystem.getDevice().createCommandEncoder();
var resultBuffer = result.byteBuffer();
if (resultBuffer.limit() / sink.format().getVertexSize() != vertexCountAfterCursor) {
throw new IllegalStateException("Mismatched vertex count");
}
var resultBuffer = sink.byteBuffer().flip();
if (renderState.vertexBuffer == null || resultBuffer.limit() > renderState.vertexBuffer.size()) {
if (renderState.vertexBuffer != null) {
renderState.vertexBuffer.close();
renderState.vertexBuffer = null;
}
renderState.vertexBuffer = RenderSystem.getDevice().createBuffer(
() -> "Monitor at " + monitor.getOrigin().getBlockPos(),
BufferType.VERTICES, BufferUsage.STATIC_WRITE, resultBuffer
);
} else if (!renderState.vertexBuffer.isClosed()) {
commandEncoder.writeToBuffer(renderState.vertexBuffer, resultBuffer, 0);
}
// Ensure our buffer contains the correct number of vertices.
if (resultBuffer.remaining() != sink.format().getVertexSize() * vertexCountAfterCursor) {
throw new IllegalStateException(String.format(
"Mismatched vertex count. Buffer is %d bytes long, but was expected to be %d (vertex size) * %d (vertex count) = %d bytes.",
resultBuffer.limit(), sink.format().getVertexSize(), vertexCountAfterCursor, sink.format().getVertexSize() * vertexCountAfterCursor
));
}
// Upload the buffer, reallocating if required.
if (renderState.vertexBuffer == null || resultBuffer.remaining() > renderState.vertexBuffer.size()) {
if (renderState.vertexBuffer != null) {
renderState.vertexBuffer.close();
renderState.vertexBuffer = null;
}
renderState.vertexBuffer = RenderSystem.getDevice().createBuffer(
() -> "Monitor at " + monitor.getOrigin().getBlockPos(),
BufferType.VERTICES, BufferUsage.STATIC_WRITE, resultBuffer
);
} else if (!renderState.vertexBuffer.isClosed()) {
commandEncoder.writeToBuffer(renderState.vertexBuffer, resultBuffer, 0);
}
var mode = FixedWidthFontRenderer.TERMINAL_TEXT.mode();
indexAfterBackground = mode.indexCount(vertexCountAfterBackground);
indexAfterForeground = mode.indexCount(vertexCountAfterForeground);
indexAfterCursor = mode.indexCount(vertexCountAfterCursor);
}
var mode = FixedWidthFontRenderer.TERMINAL_TEXT.mode();
renderState.indexAfterBackground = mode.indexCount(vertexCountAfterBackground);
renderState.indexAfterForeground = mode.indexCount(vertexCountAfterForeground);
renderState.indexAfterCursor = mode.indexCount(vertexCountAfterCursor);
renderState.indexAfterForeground = indexAfterForeground;
renderState.indexAfterBackground = indexAfterBackground;
renderState.indexAfterCursor = indexAfterCursor;
}
if (renderState.indexAfterCursor == 0) return;
@ -249,4 +260,14 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
return monitor.getRenderBoundingBox();
}
private static ByteBuffer getBuffer(int capacity) {
var buffer = backingBuffer;
if (buffer == null || buffer.capacity() < capacity) {
buffer = backingBuffer = buffer == null ? MemoryUtil.memAlloc(capacity) : MemoryUtil.memRealloc(buffer, capacity);
}
buffer.clear();
return buffer;
}
}

View File

@ -4,7 +4,6 @@
package dan200.computercraft.client.render.text;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
@ -15,6 +14,7 @@ import dan200.computercraft.core.util.Colour;
import net.minecraft.util.ARGB;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
@ -164,6 +164,8 @@ public final class DirectFixedWidthFontRenderer {
public abstract static class QuadEmitter {
private int vertexCount;
public abstract ByteBuffer byteBuffer();
public abstract VertexFormat format();
protected abstract void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2);
@ -174,10 +176,15 @@ public final class DirectFixedWidthFontRenderer {
}
public static final class ByteBufferEmitter extends QuadEmitter {
private final ByteBufferBuilder builder;
private final ByteBuffer buffer;
public ByteBufferEmitter(ByteBufferBuilder builder) {
this.builder = builder;
public ByteBufferEmitter(ByteBuffer buffer) {
this.buffer = buffer;
}
@Override
public ByteBuffer byteBuffer() {
return buffer;
}
@Override
@ -187,17 +194,18 @@ public final class DirectFixedWidthFontRenderer {
@Override
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
DirectFixedWidthFontRenderer.quad(builder, x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
}
}
static void quad(ByteBufferBuilder builder, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
private static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
// Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the
// underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write.
// This provides significant performance gains, at the cost of well, using Unsafe.
// Each vertex is 28 bytes, giving 112 bytes in total. Vertices are of the form (xyz:FFF)(abgr:BBBB)(uv1:FF)(uv2:SS),
// which matches the POSITION_COLOR_TEX_LIGHTMAP vertex format.
var addr = builder.reserve(112);
var position = buffer.position();
var addr = MemoryUtil.memAddress(buffer);
// We're doing terrible unsafe hacks below, so let's be really sure that what we're doing is reasonable.
// Require the pointer to be aligned to a 32-bit boundary.
@ -246,6 +254,9 @@ public final class DirectFixedWidthFontRenderer {
memPutShort(addr + 108, (short) 0xF0);
memPutShort(addr + 110, (short) 0xF0);
// Finally increment the position.
buffer.position(position + 112);
// Well done for getting to the end of this method. I recommend you take a break and go look at cute puppies.
}
}