From ba36c69583e1312708ad18c3854528562f7c3ef9 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 11 Sep 2024 09:00:06 +0100 Subject: [PATCH] Use ARGB32 to store palette colours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we used an RGBA byte array. However, this comes with some overhead (extra memory reads, bounds checks). Minecraft 1.21+ uses ARGB32 colours for rendering (well, in the public code — internaly it converts to ABGR), so it makes sense to match that here. We also add some helper functions for dealing with ARGB32 colours. These can be removed in 1.21, as Minecraft will have these builtin. --- .../client/render/PocketItemRenderer.java | 8 +-- .../text/DirectFixedWidthFontRenderer.java | 54 ++++++++++--------- .../render/text/FixedWidthFontRenderer.java | 11 ++-- .../computercraft/shared/util/ARGB32.java | 38 +++++++++++++ .../computercraft/core/terminal/Palette.java | 32 +++++------ .../computercraft/core/util/Colour.java | 11 ---- .../client/integration/IrisShaderMod.java | 9 ++-- .../client/integration/IrisShaderMod.java | 9 ++-- 8 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/util/ARGB32.java diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java index 676f13556..2f4337aee 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java @@ -13,6 +13,7 @@ import dan200.computercraft.core.util.Colour; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import dan200.computercraft.shared.util.ARGB32; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.world.item.ItemStack; import org.joml.Matrix4f; @@ -92,16 +93,11 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer { } private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) { - var r = (byte) ((colour >>> 16) & 0xFF); - var g = (byte) ((colour >>> 8) & 0xFF); - var b = (byte) (colour & 0xFF); - var c = new byte[]{ r, g, b, (byte) 255 }; - var buffer = render.getBuffer(RenderTypes.TERMINAL); FixedWidthFontRenderer.drawQuad( FixedWidthFontRenderer.toVertexConsumer(transform, buffer), width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT, - c, RenderTypes.FULL_BRIGHT_LIGHTMAP + ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP ); } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java index 2c60efb58..195d02857 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java @@ -13,6 +13,7 @@ import dan200.computercraft.core.terminal.Palette; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.util.Colour; +import net.minecraft.util.FastColor; import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; @@ -41,7 +42,7 @@ public final class DirectFixedWidthFontRenderer { private DirectFixedWidthFontRenderer() { } - private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour) { + private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour) { // Short circuit to avoid the common case - the texture should be blank here after all. if (index == '\0' || index == ' ') return; @@ -158,8 +159,8 @@ public final class DirectFixedWidthFontRenderer { return (terminal.getHeight() + 2) * (terminal.getWidth() + 2) * 2; } - private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - buffer.quad(x1, y1, x2, y2, z, rgba, u1, v1, u2, v2); + private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + buffer.quad(x1, y1, x2, y2, z, colour, u1, v1, u2, v2); } public interface QuadEmitter { @@ -167,7 +168,7 @@ public final class DirectFixedWidthFontRenderer { ByteBuffer buffer(); - void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2); + void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2); } public record ByteBufferEmitter(ByteBuffer buffer) implements QuadEmitter { @@ -177,12 +178,12 @@ public final class DirectFixedWidthFontRenderer { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, rgba, u1, v1, u2, v2); + 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(buffer, x1, y1, x2, y2, z, colour, u1, v1, u2, v2); } } - static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { + 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. @@ -196,16 +197,19 @@ public final class DirectFixedWidthFontRenderer { if (position < 0 || 112 > buffer.limit() - position) throw new IndexOutOfBoundsException(); // Require the pointer to be aligned to a 32-bit boundary. if ((addr & 3) != 0) throw new IllegalStateException("Memory is not aligned"); - // Also assert the length of the array. This appears to help elide bounds checks on the array in some circumstances. - if (rgba.length != 4) throw new IllegalStateException(); + + var red = (byte) FastColor.ARGB32.red(colour); + var green = (byte) FastColor.ARGB32.green(colour); + var blue = (byte) FastColor.ARGB32.blue(colour); + var alpha = (byte) FastColor.ARGB32.alpha(colour); memPutFloat(addr + 0, x1); memPutFloat(addr + 4, y1); memPutFloat(addr + 8, z); - memPutByte(addr + 12, rgba[0]); - memPutByte(addr + 13, rgba[1]); - memPutByte(addr + 14, rgba[2]); - memPutByte(addr + 15, (byte) 255); + memPutByte(addr + 12, red); + memPutByte(addr + 13, green); + memPutByte(addr + 14, blue); + memPutByte(addr + 15, alpha); memPutFloat(addr + 16, u1); memPutFloat(addr + 20, v1); memPutShort(addr + 24, (short) 0xF0); @@ -214,10 +218,10 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 28, x1); memPutFloat(addr + 32, y2); memPutFloat(addr + 36, z); - memPutByte(addr + 40, rgba[0]); - memPutByte(addr + 41, rgba[1]); - memPutByte(addr + 42, rgba[2]); - memPutByte(addr + 43, (byte) 255); + memPutByte(addr + 40, red); + memPutByte(addr + 41, green); + memPutByte(addr + 42, blue); + memPutByte(addr + 43, alpha); memPutFloat(addr + 44, u1); memPutFloat(addr + 48, v2); memPutShort(addr + 52, (short) 0xF0); @@ -226,10 +230,10 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 56, x2); memPutFloat(addr + 60, y2); memPutFloat(addr + 64, z); - memPutByte(addr + 68, rgba[0]); - memPutByte(addr + 69, rgba[1]); - memPutByte(addr + 70, rgba[2]); - memPutByte(addr + 71, (byte) 255); + memPutByte(addr + 68, red); + memPutByte(addr + 69, green); + memPutByte(addr + 70, blue); + memPutByte(addr + 71, alpha); memPutFloat(addr + 72, u2); memPutFloat(addr + 76, v2); memPutShort(addr + 80, (short) 0xF0); @@ -238,10 +242,10 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 84, x2); memPutFloat(addr + 88, y1); memPutFloat(addr + 92, z); - memPutByte(addr + 96, rgba[0]); - memPutByte(addr + 97, rgba[1]); - memPutByte(addr + 98, rgba[2]); - memPutByte(addr + 99, (byte) 255); + memPutByte(addr + 96, red); + memPutByte(addr + 97, green); + memPutByte(addr + 98, blue); + memPutByte(addr + 99, alpha); memPutFloat(addr + 100, u2); memPutFloat(addr + 104, v1); memPutShort(addr + 108, (short) 0xF0); diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java index 1111d06d3..d2ecfc05d 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java @@ -12,6 +12,7 @@ import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.util.Colour; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -41,7 +42,7 @@ public final class FixedWidthFontRenderer { static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH; static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH; - private static final byte[] BLACK = new byte[]{ byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), (byte) 255 }; + private static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR())); private static final float Z_OFFSET = 1e-3f; private FixedWidthFontRenderer() { @@ -59,7 +60,7 @@ public final class FixedWidthFontRenderer { return 15 - Terminal.getColour(c, def); } - private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour, int light) { + private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour, int light) { // Short circuit to avoid the common case - the texture should be blank here after all. if (index == '\0' || index == ' ') return; @@ -75,7 +76,7 @@ public final class FixedWidthFontRenderer { ); } - public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light) { + public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, int colour, int light) { quad(emitter, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light); } @@ -216,10 +217,10 @@ public final class FixedWidthFontRenderer { return new QuadEmitter(transform.last().pose(), consumer); } - private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2, int light) { + private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) { var poseMatrix = c.poseMatrix(); var consumer = c.consumer(); - byte r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3]; + int r = FastColor.ARGB32.red(colour), g = FastColor.ARGB32.green(colour), b = FastColor.ARGB32.blue(colour), a = FastColor.ARGB32.alpha(colour); consumer.vertex(poseMatrix, x1, y1, z).color(r, g, b, a).uv(u1, v1).uv2(light).endVertex(); consumer.vertex(poseMatrix, x1, y2, z).color(r, g, b, a).uv(u1, v2).uv2(light).endVertex(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ARGB32.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ARGB32.java new file mode 100644 index 000000000..0eab49649 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ARGB32.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.util; + +import net.minecraft.util.FastColor; + +/** + * Utilities for working with 32-bit ARGB colours. + * + * @see FastColor.ARGB32 + */ +public final class ARGB32 { + private ARGB32() { + } + + /** + * Set the alpha channel to be fully opaque. + * + * @param colour The colour to make opaque. + * @return The fully-opaque colour + */ + public static int opaque(int colour) { + return 0xFF000000 | colour; + } + + /** + * Convert an ARGB32 colour to a {@linkplain FastColor.ABGR32 ABGR32} one. + * + * @param colour The colour to convert. + * @return The converted colour. + */ + public static int toABGR32(int colour) { + // Swap B and R components, converting ARGB32 to ABGR32. + return colour & 0xFF00FF00 | (colour & 0xFF0000) >> 16 | (colour & 0xFF) << 16; + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java b/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java index 0dea4b521..50d75db97 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java +++ b/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java @@ -12,15 +12,13 @@ public class Palette { private final boolean colour; private final double[][] colours = new double[PALETTE_SIZE][3]; - private final byte[][] byteColours = new byte[PALETTE_SIZE][4]; + private final int[] byteColours = new int[PALETTE_SIZE]; public static final Palette DEFAULT = new Palette(true); public Palette(boolean colour) { this.colour = colour; resetColours(); - - for (var i = 0; i < PALETTE_SIZE; i++) byteColours[i][3] = (byte) 255; } public void setColour(int i, double r, double g, double b) { @@ -30,15 +28,17 @@ public class Palette { colours[i][2] = b; if (colour) { - byteColours[i][0] = (byte) (int) (r * 255); - byteColours[i][1] = (byte) (int) (g * 255); - byteColours[i][2] = (byte) (int) (b * 255); + byteColours[i] = packColour((int) (r * 255), (int) (g * 255), (int) (b * 255)); } else { - var grey = (byte) (int) ((r + g + b) / 3 * 255); - byteColours[i][0] = byteColours[i][1] = byteColours[i][2] = grey; + var grey = (int) ((r + g + b) / 3 * 255); + byteColours[i] = packColour(grey, grey, grey); } } + private static int packColour(int r, int g, int b) { + return 255 << 24 | (r & 255) << 16 | (g & 255) << 8 | b & 255; + } + public void setColour(int i, Colour colour) { setColour(i, colour.getR(), colour.getG(), colour.getB()); } @@ -48,26 +48,20 @@ public class Palette { } /** - * Get the colour as a set of RGB values suitable for rendering. Colours are automatically converted to greyscale + * Get the colour as a set of ARGB values suitable for rendering. Colours are automatically converted to greyscale * when using a black and white palette. *

- * This returns a byte array, suitable for being used directly by our terminal vertex format. + * This returns a packed 32-bit ARGB colour. * * @param i The colour index. - * @return The number as a tuple of bytes. + * @return The actual RGB colour. */ - public byte[] getRenderColours(int i) { + public int getRenderColours(int i) { return byteColours[i]; } - public void resetColour(int i) { - if (i >= 0 && i < PALETTE_SIZE) setColour(i, Colour.VALUES[i]); - } - public void resetColours() { - for (var i = 0; i < Colour.VALUES.length; i++) { - resetColour(i); - } + for (var i = 0; i < Colour.VALUES.length; i++) setColour(i, Colour.VALUES[i]); } public static int encodeRGB8(double[] rgb) { diff --git a/projects/core/src/main/java/dan200/computercraft/core/util/Colour.java b/projects/core/src/main/java/dan200/computercraft/core/util/Colour.java index fded29173..3c7236e41 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/util/Colour.java +++ b/projects/core/src/main/java/dan200/computercraft/core/util/Colour.java @@ -4,8 +4,6 @@ package dan200.computercraft.core.util; -import javax.annotation.Nullable; - public enum Colour { BLACK(0x111111), RED(0xcc4c4c), @@ -30,15 +28,6 @@ public enum Colour { return Colour.VALUES[colour]; } - @Nullable - public static Colour fromHex(int colour) { - for (var entry : VALUES) { - if (entry.getHex() == colour) return entry; - } - - return null; - } - private final int hex; private final float red, green, blue; diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java b/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java index 6ac77e078..c4a8c7663 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java @@ -8,6 +8,7 @@ import com.google.auto.service.AutoService; import com.mojang.blaze3d.vertex.VertexFormat; import dan200.computercraft.client.render.RenderTypes; import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; +import dan200.computercraft.shared.util.ARGB32; import net.fabricmc.loader.api.FabricLoader; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.api.v0.IrisTextVertexSink; @@ -54,12 +55,8 @@ public class IrisShaderMod implements ShaderMod.Provider { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - sink.quad(x1, y1, x2, y2, z, pack(rgba[0], rgba[1], rgba[2], rgba[3]), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); - } - - private static int pack(int r, int g, int b, int a) { - return (a & 255) << 24 | (b & 255) << 16 | (g & 255) << 8 | r & 255; + public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + sink.quad(x1, y1, x2, y2, z, ARGB32.toABGR32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); } } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java b/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java index 948a70589..3eb2e0065 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java @@ -8,6 +8,7 @@ import com.google.auto.service.AutoService; import com.mojang.blaze3d.vertex.VertexFormat; import dan200.computercraft.client.render.RenderTypes; import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; +import dan200.computercraft.shared.util.ARGB32; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.api.v0.IrisTextVertexSink; import net.minecraftforge.fml.ModList; @@ -57,12 +58,8 @@ public class IrisShaderMod implements ShaderMod.Provider { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - sink.quad(x1, y1, x2, y2, z, pack(rgba[0], rgba[1], rgba[2], rgba[3]), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); - } - - private static int pack(int r, int g, int b, int a) { - return (a & 255) << 24 | (b & 255) << 16 | (g & 255) << 8 | r & 255; + public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + sink.quad(x1, y1, x2, y2, z, ARGB32.toABGR32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); } } }