From fae888e87e6a1b8d811c902bb2201a2a7003398b Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 28 Nov 2023 19:46:29 -0600 Subject: [PATCH] Optimize the internals of BufferBuilder This should be compatible with all normal mods which do not hack into BufferBuilder or BufferVertexConsumer. Note: The behavior of the VertexConsumer interface is changed slightly with these patches, as elements can now be written in any order. However, this has no consequences, and will not change the behavior of any mod. --- .../consumer/BufferBuilderMixin.java | 440 +++++++++++++++++- .../buffer_builder/BufferBuilderMixin.java | 47 -- src/main/resources/sodium.mixins.json | 1 - 3 files changed, 421 insertions(+), 67 deletions(-) delete mode 100644 src/main/java/me/jellysquid/mods/sodium/mixin/features/render/immediate/buffer_builder/BufferBuilderMixin.java diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java b/src/main/java/me/jellysquid/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java index 42c8916792..ae92d9006c 100644 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java +++ b/src/main/java/me/jellysquid/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java @@ -1,16 +1,20 @@ package me.jellysquid.mods.sodium.mixin.core.render.immediate.consumer; import net.caffeinemc.mods.sodium.api.memory.MemoryIntrinsics; +import net.caffeinemc.mods.sodium.api.util.ColorABGR; +import net.caffeinemc.mods.sodium.api.util.NormI8; +import net.caffeinemc.mods.sodium.api.vertex.attributes.CommonVertexAttribute; +import net.caffeinemc.mods.sodium.api.vertex.attributes.common.*; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.vertex.format.VertexFormatDescription; import net.caffeinemc.mods.sodium.api.vertex.format.VertexFormatRegistry; import net.caffeinemc.mods.sodium.api.vertex.serializer.VertexSerializerRegistry; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.*; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -20,7 +24,22 @@ import java.nio.ByteBuffer; @Mixin(BufferBuilder.class) -public abstract class BufferBuilderMixin implements VertexBufferWriter { +public abstract class BufferBuilderMixin extends FixedColorVertexConsumer implements VertexBufferWriter { + @Shadow + protected abstract void grow(int size); + + @Unique + private static final int ATTRIBUTE_NOT_PRESENT = -1; + + @Unique + private static final int + ATTRIBUTE_POSITION_BIT = 1 << CommonVertexAttribute.POSITION.ordinal(), + ATTRIBUTE_COLOR_BIT = 1 << CommonVertexAttribute.COLOR.ordinal(), + ATTRIBUTE_TEXTURE_BIT = 1 << CommonVertexAttribute.TEXTURE.ordinal(), + ATTRIBUTE_OVERLAY_BIT = 1 << CommonVertexAttribute.OVERLAY.ordinal(), + ATTRIBUTE_LIGHT_BIT = 1 << CommonVertexAttribute.LIGHT.ordinal(), + ATTRIBUTE_NORMAL_BIT = 1 << CommonVertexAttribute.NORMAL.ordinal(); + @Shadow private ByteBuffer buffer; @@ -31,39 +50,417 @@ public abstract class BufferBuilderMixin implements VertexBufferWriter { private int elementOffset; @Shadow - protected abstract void grow(int size); + private VertexFormat.DrawMode drawMode; + + @Unique + private VertexFormatDescription formatDescription; @Unique - private VertexFormatDescription format; + private int vertexStride; @Unique - private int stride; + private int + attributeOffsetPosition, + attributeOffsetColor, + attributeOffsetTexture, + attributeOffsetOverlay, + attributeOffsetLight, + attributeOffsetNormal; - @Inject(method = "setFormat", - at = @At( - value = "FIELD", - target = "Lnet/minecraft/client/render/BufferBuilder;format:Lnet/minecraft/client/render/VertexFormat;", - opcode = Opcodes.PUTFIELD - ) + @Unique + private int requiredAttributes, writtenAttributes; + + @Inject(method = "", at = @At("RETURN")) + private void onInit(int initialCapacity, CallbackInfo ci) { + this.resetAttributeBindings(); + } + + @Unique + private void resetAttributeBindings() { + this.requiredAttributes = 0; + + this.attributeOffsetPosition = ATTRIBUTE_NOT_PRESENT; + this.attributeOffsetColor = ATTRIBUTE_NOT_PRESENT; + this.attributeOffsetTexture = ATTRIBUTE_NOT_PRESENT; + this.attributeOffsetOverlay = ATTRIBUTE_NOT_PRESENT; + this.attributeOffsetLight = ATTRIBUTE_NOT_PRESENT; + this.attributeOffsetNormal = ATTRIBUTE_NOT_PRESENT; + } + + @Inject( + method = "setFormat", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/render/BufferBuilder;format:Lnet/minecraft/client/render/VertexFormat;", + opcode = Opcodes.PUTFIELD + ) ) private void onFormatChanged(VertexFormat format, CallbackInfo ci) { - this.format = VertexFormatRegistry.instance() + this.formatDescription = VertexFormatRegistry.instance() .get(format); - this.stride = format.getVertexSizeByte(); + this.vertexStride = this.formatDescription.stride(); + + this.updateAttributeBindings(this.formatDescription); + } + + @Unique + private void updateAttributeBindings(VertexFormatDescription desc) { + this.resetAttributeBindings(); + + if (desc.containsElement(CommonVertexAttribute.POSITION)) { + this.requiredAttributes |= ATTRIBUTE_POSITION_BIT; + this.attributeOffsetPosition = desc.getElementOffset(CommonVertexAttribute.POSITION); + } + + if (desc.containsElement(CommonVertexAttribute.COLOR)) { + this.requiredAttributes |= ATTRIBUTE_COLOR_BIT; + this.attributeOffsetColor = desc.getElementOffset(CommonVertexAttribute.COLOR); + } + + if (desc.containsElement(CommonVertexAttribute.TEXTURE)) { + this.requiredAttributes |= ATTRIBUTE_TEXTURE_BIT; + this.attributeOffsetTexture = desc.getElementOffset(CommonVertexAttribute.TEXTURE); + } + + if (desc.containsElement(CommonVertexAttribute.OVERLAY)) { + this.requiredAttributes |= ATTRIBUTE_OVERLAY_BIT; + this.attributeOffsetOverlay = desc.getElementOffset(CommonVertexAttribute.OVERLAY); + } + + if (desc.containsElement(CommonVertexAttribute.LIGHT)) { + this.requiredAttributes |= ATTRIBUTE_LIGHT_BIT; + this.attributeOffsetLight = desc.getElementOffset(CommonVertexAttribute.LIGHT); + } + + if (desc.containsElement(CommonVertexAttribute.NORMAL)) { + this.requiredAttributes |= ATTRIBUTE_NORMAL_BIT; + this.attributeOffsetNormal = desc.getElementOffset(CommonVertexAttribute.NORMAL); + } + } + + @Inject( + method = "begin", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/BufferBuilder;setFormat(Lnet/minecraft/client/render/VertexFormat;)V" + ) + ) + private void onBegin(VertexFormat.DrawMode drawMode, VertexFormat format, CallbackInfo ci) { + this.writtenAttributes = 0; + } + + @Inject(method = "resetBuilding", at = @At("RETURN")) + private void onResetBuilding(CallbackInfo ci) { + this.writtenAttributes = 0; + } + + @Inject(method = "reset", at = @At("RETURN")) + private void onReset(CallbackInfo ci) { + this.writtenAttributes = 0; + } + + @Unique + private void putPositionAttribute(float x, float y, float z) { + if (this.attributeOffsetPosition == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetPosition); + PositionAttribute.put(offset, x, y, z); + + this.writtenAttributes |= ATTRIBUTE_POSITION_BIT; + } + + + @Unique + private void putColorAttribute(int rgba) { + if (this.attributeOffsetColor == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetColor); + ColorAttribute.set(offset, rgba); + + this.writtenAttributes |= ATTRIBUTE_COLOR_BIT; + } + + @Unique + private void putTextureAttribute(float u, float v) { + if (this.attributeOffsetTexture == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetTexture); + TextureAttribute.put(offset, u, v); + + this.writtenAttributes |= ATTRIBUTE_TEXTURE_BIT; + } + + @Unique + private void putOverlayAttribute(int uv) { + if (this.attributeOffsetOverlay == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetOverlay); + OverlayAttribute.set(offset, uv); + + this.writtenAttributes |= ATTRIBUTE_OVERLAY_BIT; + } + + @Unique + private void putLightAttribute(int uv) { + if (this.attributeOffsetLight == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetLight); + LightAttribute.set(offset, uv); + + this.writtenAttributes |= ATTRIBUTE_LIGHT_BIT; + } + + @Unique + private void putNormalAttribute(int normal) { + if (this.attributeOffsetNormal == ATTRIBUTE_NOT_PRESENT) { + return; + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset + this.attributeOffsetNormal); + NormalAttribute.set(offset, normal); + + this.writtenAttributes |= ATTRIBUTE_NORMAL_BIT; + } + + @Override + public void vertex(float x, float y, float z, + float red, float green, float blue, float alpha, + float u, float v, + int overlay, int light, + float normalX, float normalY, float normalZ + ) { + if (this.colorFixed) { + throw new IllegalStateException(); + } + + final long offset = MemoryUtil.memAddress(this.buffer, this.elementOffset); + + if (this.attributeOffsetPosition != ATTRIBUTE_NOT_PRESENT) { + PositionAttribute.put(offset + this.attributeOffsetPosition, x, y, z); + } + + if (this.attributeOffsetColor != ATTRIBUTE_NOT_PRESENT) { + ColorAttribute.set(offset + this.attributeOffsetColor, ColorABGR.pack(red, green, blue, alpha)); + } + + if (this.attributeOffsetTexture != ATTRIBUTE_NOT_PRESENT) { + TextureAttribute.put(offset + this.attributeOffsetTexture, u, v); + } + + if (this.attributeOffsetOverlay != ATTRIBUTE_NOT_PRESENT) { + OverlayAttribute.set(offset + this.attributeOffsetOverlay, overlay); + } + + if (this.attributeOffsetLight != ATTRIBUTE_NOT_PRESENT) { + LightAttribute.set(offset + this.attributeOffsetLight, light); + } + + if (this.attributeOffsetNormal != ATTRIBUTE_NOT_PRESENT) { + NormalAttribute.set(offset + this.attributeOffsetNormal, NormI8.pack(normalX, normalY, normalZ)); + } + + // It's okay to mark elements as "written to" even if the vertex format does not specify those elements. + this.writtenAttributes = ATTRIBUTE_POSITION_BIT | ATTRIBUTE_COLOR_BIT | ATTRIBUTE_TEXTURE_BIT | + ATTRIBUTE_OVERLAY_BIT | ATTRIBUTE_LIGHT_BIT | ATTRIBUTE_NORMAL_BIT; + + this.next(); + } + + @Override + public VertexConsumer vertex(double x, double y, double z) { + this.putPositionAttribute((float) x, (float) y, (float) z); + + return this; + } + + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) { + if (this.colorFixed) { + throw new IllegalStateException(); + } + + this.putColorAttribute(ColorABGR.pack(red, green, blue, alpha)); + + return this; + } + + @Override + public VertexConsumer color(int rgba) { + if (this.colorFixed) { + throw new IllegalStateException(); + } + + this.putColorAttribute(rgba); + + return this; + } + + @Override + public VertexConsumer texture(float u, float v) { + this.putTextureAttribute(u, v); + + return this; + } + + @Override + public VertexConsumer overlay(int uv) { + this.putOverlayAttribute(uv); + + return this; + } + + + @Override + public VertexConsumer light(int uv) { + this.putLightAttribute(uv); + + return this; + } + @Override + public VertexConsumer normal(float x, float y, float z) { + this.putNormalAttribute(NormI8.pack(x, y, z)); + + return this; + } + + @Override + public VertexConsumer light(int u, int v) { + return this.light(packU16x2(u, v)); + } + + @Override + public VertexConsumer overlay(int u, int v) { + return this.overlay(packU16x2(u, v)); + } + + @Unique + private static int packU16x2(int u, int v) { + return (u & 0xFFFF) << 0 | + (v & 0xFFFF) << 16; + } + + /** + * @author JellySquid + * @reason Only used by BufferVertexConsumer's default implementations, which our patches remove + */ + @Overwrite + public VertexFormatElement getCurrentElement() { + throw createBlockedUpcallException(); + } + + /** + * @author JellySquid + * @reason Only used by BufferVertexConsumer's default implementations, which our patches remove + */ + @Overwrite + public void nextElement() { + throw createBlockedUpcallException(); + } + + /** + * @author JellySquid + * @reason Only used by BufferVertexConsumer's default implementations, which our patches remove + */ + @Overwrite + public void putByte(int index, byte value) { + throw createBlockedUpcallException(); + } + + /** + * @author JellySquid + * @reason Only used by BufferVertexConsumer's default implementations, which our patches remove + */ + @Overwrite + public void putShort(int index, short value) { + throw createBlockedUpcallException(); + } + + /** + * @author JellySquid + * @reason Only used by BufferVertexConsumer's default implementations, which our patches remove + */ + @Overwrite + public void putFloat(int index, float value) { + throw createBlockedUpcallException(); + } + + /** + * @author JellySquid + * @reason The implementation no longer cares about the current element + */ + @Overwrite + @Override + public void next() { + if (this.colorFixed) { + this.writeFixedColor(); + } + + if (!this.isVertexFinished()) { + throw new IllegalStateException("Not filled all elements of the vertex"); + } + + this.vertexCount++; + this.elementOffset += this.vertexStride; + + this.writtenAttributes = 0; + + this.grow(this.vertexStride); + + if (this.shouldDuplicateVertices()) { + this.duplicateVertex(); + } + } + + @Unique + private boolean isVertexFinished() { + return (this.writtenAttributes & this.requiredAttributes) == this.requiredAttributes; + } + + @Unique + private void writeFixedColor() { + this.putColorAttribute(ColorABGR.pack(this.fixedRed, this.fixedGreen, this.fixedBlue, this.fixedAlpha)); + } + + @Unique + private boolean shouldDuplicateVertices() { + return this.drawMode == VertexFormat.DrawMode.LINES || this.drawMode == VertexFormat.DrawMode.LINE_STRIP; + } + + @Unique + private void duplicateVertex() { + MemoryIntrinsics.copyMemory( + MemoryUtil.memAddress(this.buffer, this.elementOffset - this.vertexStride), + MemoryUtil.memAddress(this.buffer, this.elementOffset), + this.vertexStride); + + this.elementOffset += this.vertexStride; + this.vertexCount++; + + this.grow(this.vertexStride); } @Override public void push(MemoryStack stack, long src, int count, VertexFormatDescription format) { - var length = count * this.stride; + var length = count * this.vertexStride; // Ensure that there is always space for 1 more vertex; see BufferBuilder.next() - this.grow(length + this.stride); + this.grow(length + this.vertexStride); // The buffer may change in the even, so we need to make sure that the // pointer is retrieved *after* the resize var dst = MemoryUtil.memAddress(this.buffer, this.elementOffset); - if (format == this.format) { + if (format == this.formatDescription) { // The layout is the same, so we can just perform a memory copy // The stride of a vertex format is always 4 bytes, so this aligned copy is always safe MemoryIntrinsics.copyMemory(src, dst, length); @@ -79,7 +476,12 @@ public void push(MemoryStack stack, long src, int count, VertexFormatDescription @Unique private void copySlow(long src, long dst, int count, VertexFormatDescription format) { VertexSerializerRegistry.instance() - .get(format, this.format) + .get(format, this.formatDescription) .serialize(src, dst, count); } + + @Unique + private static RuntimeException createBlockedUpcallException() { + return new UnsupportedOperationException("The internal methods provided by BufferVertexConsumer (as used to upcall into BufferBuilder) are unsupported"); + } } diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/features/render/immediate/buffer_builder/BufferBuilderMixin.java b/src/main/java/me/jellysquid/mods/sodium/mixin/features/render/immediate/buffer_builder/BufferBuilderMixin.java deleted file mode 100644 index 72391680d1..0000000000 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/features/render/immediate/buffer_builder/BufferBuilderMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.jellysquid.mods.sodium.mixin.features.render.immediate.buffer_builder; - -import com.google.common.collect.ImmutableList; -import net.minecraft.client.render.*; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(BufferBuilder.class) -public abstract class BufferBuilderMixin extends FixedColorVertexConsumer implements BufferVertexConsumer { - @Shadow - private VertexFormat format; - - @Shadow - private VertexFormatElement currentElement; - - @Shadow - private int elementOffset; - - @Shadow - private int currentElementId; - - /** - * @author JellySquid - * @reason Remove modulo operations and recursion - */ - @Override - @Overwrite - public void nextElement() { - ImmutableList elements = this.format.getElements(); - - do { - this.elementOffset += this.currentElement.getByteLength(); - - // Wrap around the element pointer without using modulo - if (++this.currentElementId >= elements.size()) { - this.currentElementId -= elements.size(); - } - - this.currentElement = elements.get(this.currentElementId); - } while (this.currentElement.getType() == VertexFormatElement.Type.PADDING); - - if (this.colorFixed && this.currentElement.getType() == VertexFormatElement.Type.COLOR) { - BufferVertexConsumer.super.color(this.fixedRed, this.fixedGreen, this.fixedBlue, this.fixedAlpha); - } - } -} diff --git a/src/main/resources/sodium.mixins.json b/src/main/resources/sodium.mixins.json index faa6773239..138e4e0038 100644 --- a/src/main/resources/sodium.mixins.json +++ b/src/main/resources/sodium.mixins.json @@ -65,7 +65,6 @@ "features.render.gui.font.GlyphRendererMixin", "features.render.gui.outlines.WorldRendererMixin", "features.render.immediate.DirectionMixin", - "features.render.immediate.buffer_builder.BufferBuilderMixin", "features.render.immediate.buffer_builder.intrinsics.BufferBuilderMixin", "features.render.immediate.buffer_builder.sorting.BufferBuilderMixin", "features.render.immediate.buffer_builder.sorting.VertexSorterMixin",