From 4eb998822dfa66293b98e022ec9d81321eccac28 Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 19:58:37 +0200 Subject: [PATCH 1/6] Move everything over to ByteBuffer --- src/main/java/be/twofold/tinybcdec/BC1.java | 4 +- src/main/java/be/twofold/tinybcdec/BC2.java | 8 +- src/main/java/be/twofold/tinybcdec/BC3.java | 4 +- src/main/java/be/twofold/tinybcdec/BC4S.java | 6 +- src/main/java/be/twofold/tinybcdec/BC4U.java | 6 +- src/main/java/be/twofold/tinybcdec/BC5S.java | 4 +- src/main/java/be/twofold/tinybcdec/BC5U.java | 4 +- src/main/java/be/twofold/tinybcdec/BC6H.java | 9 ++- src/main/java/be/twofold/tinybcdec/BC7.java | 11 ++- src/main/java/be/twofold/tinybcdec/BPTC.java | 8 +- .../be/twofold/tinybcdec/BlockDecoder.java | 78 +++++++++---------- .../java/be/twofold/tinybcdec/ByteArrays.java | 28 ++++--- .../be/twofold/tinybcdec/BC1Benchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC1Test.java | 13 ++-- .../be/twofold/tinybcdec/BC2Benchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC2Test.java | 7 +- .../be/twofold/tinybcdec/BC3Benchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC3Test.java | 7 +- .../java/be/twofold/tinybcdec/BC4STest.java | 7 +- .../be/twofold/tinybcdec/BC4UBenchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC4UTest.java | 7 +- .../java/be/twofold/tinybcdec/BC5STest.java | 15 ++-- .../be/twofold/tinybcdec/BC5UBenchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC5UTest.java | 15 ++-- .../be/twofold/tinybcdec/BC6HBenchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC6HTest.java | 21 ++--- .../be/twofold/tinybcdec/BC7Benchmark.java | 9 ++- .../java/be/twofold/tinybcdec/BC7Test.java | 13 ++-- .../be/twofold/tinybcdec/BCTestUtils.java | 35 ++++----- .../twofold/tinybcdec/BlockDecoderTest.java | 35 +++++---- .../be/twofold/tinybcdec/PlatformTest.java | 6 +- 31 files changed, 227 insertions(+), 187 deletions(-) diff --git a/src/main/java/be/twofold/tinybcdec/BC1.java b/src/main/java/be/twofold/tinybcdec/BC1.java index 80ebba1..3a8fd38 100644 --- a/src/main/java/be/twofold/tinybcdec/BC1.java +++ b/src/main/java/be/twofold/tinybcdec/BC1.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC1 extends BlockDecoder { private static final int BPP = 4; @@ -13,7 +15,7 @@ final class BC1 extends BlockDecoder { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { long block = ByteArrays.getLong(src, srcPos); int c0 = (int) (block/* */) & 0xFFFF; diff --git a/src/main/java/be/twofold/tinybcdec/BC2.java b/src/main/java/be/twofold/tinybcdec/BC2.java index cf96fb6..65fac38 100644 --- a/src/main/java/be/twofold/tinybcdec/BC2.java +++ b/src/main/java/be/twofold/tinybcdec/BC2.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC2 extends BlockDecoder { private static final int BPP = 4; private static final BC1 COLOR_DECODER = new BC1(BC1Mode.BC2OR3); @@ -11,18 +13,18 @@ private BC2() { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { COLOR_DECODER.decodeBlock(src, srcPos + 8, dst, dstPos, stride); decodeAlpha(src, srcPos, dst, dstPos + 3, stride); } - private void decodeAlpha(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + private void decodeAlpha(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { long alphas = ByteArrays.getLong(src, srcPos); for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = (byte) ((alphas & 15) * 17); - dst[dstPos + x * BPP] = alpha; + ByteArrays.setByte(dst, dstPos + x * BPP, alpha); alphas >>>= 4; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC3.java b/src/main/java/be/twofold/tinybcdec/BC3.java index 2a83227..e034a30 100644 --- a/src/main/java/be/twofold/tinybcdec/BC3.java +++ b/src/main/java/be/twofold/tinybcdec/BC3.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC3 extends BlockDecoder { private static final int BPP = 4; private static final BC1 COLOR_DECODER = new BC1(BC1Mode.BC2OR3); @@ -12,7 +14,7 @@ private BC3() { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { COLOR_DECODER.decodeBlock(src, srcPos + 8, dst, dstPos/**/, stride); ALPHA_DECODER.decodeBlock(src, srcPos/**/, dst, dstPos + 3, stride); } diff --git a/src/main/java/be/twofold/tinybcdec/BC4S.java b/src/main/java/be/twofold/tinybcdec/BC4S.java index 3e19664..8798e41 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4S.java +++ b/src/main/java/be/twofold/tinybcdec/BC4S.java @@ -1,12 +1,14 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC4S extends BlockDecoder { BC4S(int pixelStride) { super(pixelStride, 8); } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { long block = ByteArrays.getLong(src, srcPos); int a0 = Math.max(-127, (byte) (block/* */)); @@ -31,7 +33,7 @@ public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stri for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = alphas[(int) (indices & 0x07)]; - dst[dstPos + x * bytesPerPixel] = alpha; + ByteArrays.setByte(dst, dstPos + x * bytesPerPixel, alpha); indices >>>= 3; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC4U.java b/src/main/java/be/twofold/tinybcdec/BC4U.java index b5fba2b..e335744 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4U.java +++ b/src/main/java/be/twofold/tinybcdec/BC4U.java @@ -1,12 +1,14 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC4U extends BlockDecoder { BC4U(int pixelStride) { super(pixelStride, 8); } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { long block = ByteArrays.getLong(src, srcPos); int a0 = (int) (block/* */) & 0xFF; @@ -31,7 +33,7 @@ public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stri for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = alphas[(int) (indices & 0x07)]; - dst[dstPos + x * bytesPerPixel] = alpha; + ByteArrays.setByte(dst, dstPos + x * bytesPerPixel, alpha); indices >>>= 3; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC5S.java b/src/main/java/be/twofold/tinybcdec/BC5S.java index 60e5fec..697f320 100644 --- a/src/main/java/be/twofold/tinybcdec/BC5S.java +++ b/src/main/java/be/twofold/tinybcdec/BC5S.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC5S extends BlockDecoder { private static final int BPP = 2; private static final BC4S DECODER = new BC4S(BPP); @@ -11,7 +13,7 @@ private BC5S() { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { DECODER.decodeBlock(src, srcPos/**/, dst, dstPos/**/, stride); DECODER.decodeBlock(src, srcPos + 8, dst, dstPos + 1, stride); } diff --git a/src/main/java/be/twofold/tinybcdec/BC5U.java b/src/main/java/be/twofold/tinybcdec/BC5U.java index ce45506..6d908b1 100644 --- a/src/main/java/be/twofold/tinybcdec/BC5U.java +++ b/src/main/java/be/twofold/tinybcdec/BC5U.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + final class BC5U extends BlockDecoder { private static final int BPP = 2; private static final BC4U DECODER = new BC4U(BPP); @@ -11,7 +13,7 @@ private BC5U() { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { DECODER.decodeBlock(src, srcPos/**/, dst, dstPos/**/, stride); DECODER.decodeBlock(src, srcPos + 8, dst, dstPos + 1, stride); } diff --git a/src/main/java/be/twofold/tinybcdec/BC6H.java b/src/main/java/be/twofold/tinybcdec/BC6H.java index e6dc49e..7e2d2dc 100644 --- a/src/main/java/be/twofold/tinybcdec/BC6H.java +++ b/src/main/java/be/twofold/tinybcdec/BC6H.java @@ -1,5 +1,6 @@ package be.twofold.tinybcdec; +import java.nio.*; import java.util.*; final class BC6H extends BPTC { @@ -30,7 +31,7 @@ final class BC6H extends BPTC { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { Bits bits = Bits.from(src, srcPos); int modeIndex = mode(bits); @@ -184,9 +185,11 @@ private int transformInverse(int value, int value0, int bits, boolean signed) { return signed ? extendSign(value, bits) : value; } - private static void fillInvalidBlock(byte[] dst, int dstPos, int stride) { + private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { - Arrays.fill(dst, dstPos, dstPos + BLOCK_WIDTH * BPP, (byte) 0); + for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { + dst.put(dstPos + i, (byte) 0); + } dstPos += stride; } } diff --git a/src/main/java/be/twofold/tinybcdec/BC7.java b/src/main/java/be/twofold/tinybcdec/BC7.java index 18886fa..fb5a7d5 100644 --- a/src/main/java/be/twofold/tinybcdec/BC7.java +++ b/src/main/java/be/twofold/tinybcdec/BC7.java @@ -1,5 +1,6 @@ package be.twofold.tinybcdec; +import java.nio.*; import java.util.*; final class BC7 extends BPTC { @@ -23,8 +24,8 @@ private BC7() { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { - int modeIndex = Integer.numberOfTrailingZeros(src[srcPos]); + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { + int modeIndex = Integer.numberOfTrailingZeros(src.get(srcPos)); if (modeIndex >= MODES.size()) { fillInvalidBlock(dst, dstPos, stride); return; @@ -155,9 +156,11 @@ public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stri } } - private static void fillInvalidBlock(byte[] dst, int dstPos, int stride) { + private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { - Arrays.fill(dst, dstPos, dstPos + BLOCK_WIDTH * BPP, (byte) 0); + for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { + dst.put(dstPos + i, (byte) 0); + } dstPos += stride; } } diff --git a/src/main/java/be/twofold/tinybcdec/BPTC.java b/src/main/java/be/twofold/tinybcdec/BPTC.java index 7f09caa..9050eab 100644 --- a/src/main/java/be/twofold/tinybcdec/BPTC.java +++ b/src/main/java/be/twofold/tinybcdec/BPTC.java @@ -1,5 +1,7 @@ package be.twofold.tinybcdec; +import java.nio.*; + abstract class BPTC extends BlockDecoder { private static final int[][] PARTITIONS = { {}, @@ -115,9 +117,9 @@ private Bits(long lo, long hi) { this.hi = hi; } - static Bits from(byte[] array, int index) { - long lo = ByteArrays.getLong(array, index); - long hi = ByteArrays.getLong(array, index + 8); + static Bits from(ByteBuffer buffer, int index) { + long lo = ByteArrays.getLong(buffer, index); + long hi = ByteArrays.getLong(buffer, index + 8); return new Bits(lo, hi); } diff --git a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java index f18ab93..18a06f4 100644 --- a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java +++ b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java @@ -1,16 +1,16 @@ package be.twofold.tinybcdec; -import java.util.*; +import java.nio.*; /** * This is the main class for decoding block compressed textures. *

* To create a new instance, use one of the static factory methods starting with {@code bc}. *

- * To decode an entire image, use the {@link #decode(byte[], int, int, int)} or {@link #decode(byte[], int, int, int, byte[], int)} method. + * To decode an entire image, use the {@link #decode(ByteBuffer, int, int)} or {@link #decode(ByteBuffer, int, int, ByteBuffer)} method. * Depending on if you want to allocate a new byte array or use an existing one. *

- * To decode a single block, use the {@link #decodeBlock(byte[], int, byte[], int, int)} method. + * To decode a single block, use the {@link #decodeBlock(ByteBuffer, int, ByteBuffer, int, int)} method. */ public abstract class BlockDecoder { static final int BLOCK_WIDTH = 4; @@ -18,7 +18,7 @@ public abstract class BlockDecoder { final int bytesPerPixel; final int bytesPerBlock; - private byte[] scratch; + private ByteBuffer scratch; BlockDecoder(int bytesPerPixel, int bytesPerBlock) { this.bytesPerPixel = bytesPerPixel; @@ -105,22 +105,22 @@ public static BlockDecoder bc7() { * @param dstPos The position in the destination data. * @param stride The number of bytes per line in the destination data. */ - public abstract void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride); + public abstract void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride); /** * Decode an entire image, allocating a new byte array as the destination. * * @param src The source data. - * @param srcPos The position in the source data. * @param srcWidth The width of the image. * @param srcHeight The height of the image. * @return The newly allocated decoded image. * @throws IllegalArgumentException If the width or height is less than or equal to 0. * @throws IndexOutOfBoundsException If the source data is too small. */ - public byte[] decode(byte[] src, int srcPos, int srcWidth, int srcHeight) { - byte[] dst = new byte[srcWidth * srcHeight * bytesPerPixel]; - decode(src, srcPos, srcWidth, srcHeight, dst, 0); + public ByteBuffer decode(ByteBuffer src, int srcWidth, int srcHeight) { + ByteBuffer dst = ByteBuffer + .allocate(srcWidth * srcHeight * bytesPerPixel); + decode(src, srcWidth, srcHeight, dst); return dst; } @@ -128,17 +128,15 @@ public byte[] decode(byte[] src, int srcPos, int srcWidth, int srcHeight) { * Decode an entire image, using an existing byte array as the destination. * * @param src The source data. - * @param srcPos The position in the source data. * @param srcWidth The width of the source image. * @param srcHeight The height of the source image. * @param dst The destination data. - * @param dstPos The position in the destination data. * The destination data must have enough room to store the entire image. * @throws IllegalArgumentException If the width or height is less than or equal to 0. * @throws IndexOutOfBoundsException If the source or destination data is too small. */ - public void decode(byte[] src, int srcPos, int srcWidth, int srcHeight, byte[] dst, int dstPos) { - decode(src, srcPos, srcWidth, srcHeight, dst, dstPos, srcWidth, srcHeight); + public void decode(ByteBuffer src, int srcWidth, int srcHeight, ByteBuffer dst) { + decode(src, srcWidth, srcHeight, dst, srcWidth, srcHeight); } /** @@ -147,21 +145,19 @@ public void decode(byte[] src, int srcPos, int srcWidth, int srcHeight, byte[] d * The destination data must have enough room to store the entire image. * * @param src The source data. - * @param srcPos The position in the source data. * @param srcWidth The width of the source data. * @param srcHeight The height of the source data. * @param dst The destination data. - * @param dstPos The position in the destination data. * @param dstWidth The width of the destination image. * @param dstHeight The height of the destination image. * @throws IllegalArgumentException If the width or height is less than or equal to 0, * or if the destination width or height is less than the source width or height. * @throws IndexOutOfBoundsException If the source or destination data is too small. */ - public void decode(byte[] src, int srcPos, int srcWidth, int srcHeight, byte[] dst, int dstPos, int dstWidth, int dstHeight) { + public void decode(ByteBuffer src, int srcWidth, int srcHeight, ByteBuffer dst, int dstWidth, int dstHeight) { decode( - src, srcPos, 0, 0, srcWidth, srcHeight, - dst, dstPos, 0, 0, dstWidth, dstHeight, + src, 0, 0, srcWidth, srcHeight, + dst, 0, 0, dstWidth, dstHeight, dstWidth, dstHeight ); } @@ -172,13 +168,11 @@ public void decode(byte[] src, int srcPos, int srcWidth, int srcHeight, byte[] d * to contain the required data for the specified regions. * * @param src The source byte array containing the encoded image data. - * @param srcPos The starting position in the source byte array. * @param srcX The x-coordinate of the source region to decode. * @param srcY The y-coordinate of the source region to decode. * @param srcWidth The width of the source region to decode. * @param srcHeight The height of the source region to decode. * @param dst The destination byte array where the decoded image data will be stored. - * @param dstPos The starting position in the destination byte array. * @param dstX The x-coordinate of the destination region where decoded data will be placed. * @param dstY The y-coordinate of the destination region where decoded data will be placed. * @param dstWidth The width of the destination region. @@ -189,24 +183,29 @@ public void decode(byte[] src, int srcPos, int srcWidth, int srcHeight, byte[] d * @throws IndexOutOfBoundsException If the source or destination buffer is too small for the specified regions. */ public void decode( - byte[] src, int srcPos, int srcX, int srcY, int srcWidth, int srcHeight, - byte[] dst, int dstPos, int dstX, int dstY, int dstWidth, int dstHeight, + ByteBuffer src, int srcX, int srcY, int srcWidth, int srcHeight, + ByteBuffer dst, int dstX, int dstY, int dstWidth, int dstHeight, int width, int height ) { validateRegion("src", srcX, srcY, srcWidth, srcHeight, width, height); validateRegion("dst", dstX, dstY, dstWidth, dstHeight, width, height); - int srcBlocksW = (srcWidth + (BLOCK_WIDTH - 1)) / BLOCK_WIDTH; - int srcBlocksH = (srcHeight + (BLOCK_HEIGHT - 1)) / BLOCK_HEIGHT; - Objects.checkFromIndexSize(srcPos, srcBlocksW * srcBlocksH * bytesPerBlock, src.length); - Objects.checkFromIndexSize(dstPos, dstWidth * dstHeight * bytesPerPixel, dst.length); + if (src.remaining() < byteSize(srcWidth, srcHeight)) { + throw new IndexOutOfBoundsException("Not enough data in src buffer"); + } + if (dst.remaining() < dstWidth * dstHeight * bytesPerPixel) { + throw new IndexOutOfBoundsException("Not enough data in dst buffer"); + } + int srcBlocksW = (srcWidth + (BLOCK_WIDTH - 1)) / BLOCK_WIDTH; int srcLineStride = srcBlocksW * bytesPerBlock; int dstLineStride = dstWidth * bytesPerPixel; + var srcBuf = src.slice().order(ByteOrder.LITTLE_ENDIAN); + var dstBuf = dst.slice().order(ByteOrder.LITTLE_ENDIAN); for (int y = 0; y < height; ) { - int srcRowStart = srcPos + ((srcY + y) / BLOCK_HEIGHT * srcLineStride); - int dstRowStart = dstPos + ((dstY + y) * dstLineStride); + int srcRowStart = ((srcY + y) / BLOCK_HEIGHT * srcLineStride); + int dstRowStart = ((dstY + y) * dstLineStride); int blockY = (srcY + y) % BLOCK_HEIGHT; int blockH = Math.min(BLOCK_HEIGHT - blockY, height - y); @@ -217,11 +216,11 @@ public void decode( int blockW = Math.min(BLOCK_WIDTH - blockX, width - x); if (blockX == 0 && x + BLOCK_WIDTH <= width && blockY == 0 && y + BLOCK_HEIGHT <= height) { - decodeBlock(src, srcPosStart, dst, dstPosStart, dstLineStride); + decodeBlock(srcBuf, srcPosStart, dstBuf, dstPosStart, dstLineStride); x += BLOCK_WIDTH; } else { partialBlock( - src, srcPosStart, dst, dstPosStart, dstLineStride, + srcBuf, srcPosStart, dstBuf, dstPosStart, dstLineStride, blockX, blockY, blockW, blockH); x += blockW; } @@ -256,24 +255,25 @@ private void validateRegion(String label, int x, int y, int w, int h, int width, } private void partialBlock( - byte[] src, int srcPos, - byte[] dst, int dstPos, int lineStride, + ByteBuffer src, int srcPos, + ByteBuffer dst, int dstPos, int lineStride, int blockX, int blockY, int blockW, int blockH ) { if (this.scratch == null) { - this.scratch = new byte[BLOCK_WIDTH * BLOCK_HEIGHT * bytesPerPixel]; + this.scratch = ByteBuffer + .allocate(BLOCK_WIDTH * BLOCK_HEIGHT * bytesPerPixel); } - byte[] scratch = this.scratch; + ByteBuffer scratch = this.scratch; int stride = BLOCK_WIDTH * bytesPerPixel; decodeBlock(src, srcPos, scratch, 0, stride); int offset = blockY * stride + blockX * bytesPerPixel; for (int row = 0; row < blockH; row++) { - System.arraycopy( - scratch, offset + (row * stride), - dst, dstPos + (row * lineStride), - blockW * bytesPerPixel - ); + var srcOff = offset + (row * stride); + var dstOff = dstPos + (row * lineStride); + for (int i = 0; i < blockW * bytesPerPixel; i++) { + dst.put(dstOff + i, scratch.get(srcOff + i)); + } } } } diff --git a/src/main/java/be/twofold/tinybcdec/ByteArrays.java b/src/main/java/be/twofold/tinybcdec/ByteArrays.java index 341f72a..39ff0dd 100644 --- a/src/main/java/be/twofold/tinybcdec/ByteArrays.java +++ b/src/main/java/be/twofold/tinybcdec/ByteArrays.java @@ -16,27 +16,31 @@ final class ByteArrays { private ByteArrays() { } - static short getShort(byte[] array, int index) { - return (short) ShortVarHandle.get(array, index); + static void setByte(ByteBuffer buffer, int index, byte value) { + buffer.put(index, value); } - static void setShort(byte[] array, int index, short value) { - ShortVarHandle.set(array, index, value); + static short getShort(ByteBuffer buffer, int index) { + return buffer.getShort(index); } - static int getInt(byte[] array, int index) { - return (int) IntVarHandle.get(array, index); + static void setShort(ByteBuffer buffer, int index, short value) { + buffer.putShort(index, value); } - static void setInt(byte[] array, int index, int value) { - IntVarHandle.set(array, index, value); + static int getInt(ByteBuffer buffer, int index) { + return buffer.getInt(index); } - static long getLong(byte[] array, int index) { - return (long) LongVarHandle.get(array, index); + static void setInt(ByteBuffer buffer, int index, int value) { + buffer.putInt(index, value); } - static void setFloat(byte[] array, int index, float value) { - FloatVarHandle.set(array, index, value); + static long getLong(ByteBuffer buffer, int index) { + return buffer.getLong(index); + } + + static void setFloat(ByteBuffer buffer, int index, float value) { + buffer.putFloat(index, value); } } diff --git a/src/test/java/be/twofold/tinybcdec/BC1Benchmark.java b/src/test/java/be/twofold/tinybcdec/BC1Benchmark.java index 175f351..80164d9 100644 --- a/src/test/java/be/twofold/tinybcdec/BC1Benchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC1Benchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC1Benchmark { @State(Scope.Thread) public static class BC1State { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC1State() { try { decoder = BlockDecoder.bc1(true); src = BCTestUtils.readResource("/bc1.dds"); - dst = new byte[256 * 256 * 4]; + dst = ByteBuffer.allocate(256 * 256 * 4); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC1State() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC1State state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC1Test.java b/src/test/java/be/twofold/tinybcdec/BC1Test.java index 8895d97..59ca44d 100644 --- a/src/test/java/be/twofold/tinybcdec/BC1Test.java +++ b/src/test/java/be/twofold/tinybcdec/BC1Test.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,20 +13,20 @@ class BC1Test { @Test void testBC1() throws IOException { - byte[] src = BCTestUtils.readResource("/bc1a.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc1a.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc1a.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc1a.png"); assertThat(actual).isEqualTo(expected); } @Test void testBC1NoAlpha() throws IOException { - byte[] src = BCTestUtils.readResource("/bc1.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc1.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc1.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc1.png"); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/be/twofold/tinybcdec/BC2Benchmark.java b/src/test/java/be/twofold/tinybcdec/BC2Benchmark.java index 753c46c..19a4fe1 100644 --- a/src/test/java/be/twofold/tinybcdec/BC2Benchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC2Benchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC2Benchmark { @State(Scope.Thread) public static class BC2State { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC2State() { try { decoder = BlockDecoder.bc2(); src = BCTestUtils.readResource("/bc2.dds"); - dst = new byte[256 * 256 * 4]; + dst = ByteBuffer.allocate(256 * 256 * 4); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC2State() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC2State state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC2Test.java b/src/test/java/be/twofold/tinybcdec/BC2Test.java index d8221a5..3cf7f20 100644 --- a/src/test/java/be/twofold/tinybcdec/BC2Test.java +++ b/src/test/java/be/twofold/tinybcdec/BC2Test.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,10 +13,10 @@ class BC2Test { @Test void testBC2() throws IOException { - byte[] src = BCTestUtils.readResource("/bc2.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc2.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc2.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc2.png"); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/be/twofold/tinybcdec/BC3Benchmark.java b/src/test/java/be/twofold/tinybcdec/BC3Benchmark.java index 4a7ab16..b0e6dc9 100644 --- a/src/test/java/be/twofold/tinybcdec/BC3Benchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC3Benchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC3Benchmark { @State(Scope.Thread) public static class BC3State { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC3State() { try { decoder = BlockDecoder.bc3(); src = BCTestUtils.readResource("/bc3.dds"); - dst = new byte[256 * 256 * 4]; + dst = ByteBuffer.allocate(256 * 256 * 4); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC3State() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC3State state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC3Test.java b/src/test/java/be/twofold/tinybcdec/BC3Test.java index b0e63c8..40f0f25 100644 --- a/src/test/java/be/twofold/tinybcdec/BC3Test.java +++ b/src/test/java/be/twofold/tinybcdec/BC3Test.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,10 +13,10 @@ class BC3Test { @Test void testBC3() throws IOException { - byte[] src = BCTestUtils.readResource("/bc3.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc3.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc3.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc3.png"); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/be/twofold/tinybcdec/BC4STest.java b/src/test/java/be/twofold/tinybcdec/BC4STest.java index e201c7e..e966667 100644 --- a/src/test/java/be/twofold/tinybcdec/BC4STest.java +++ b/src/test/java/be/twofold/tinybcdec/BC4STest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,10 +13,10 @@ class BC4STest { @Test void testBC4S() throws IOException { - byte[] src = BCTestUtils.readResource("/bc4s.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc4s.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc4s.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc4s.png"); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/be/twofold/tinybcdec/BC4UBenchmark.java b/src/test/java/be/twofold/tinybcdec/BC4UBenchmark.java index 98bade1..c3bd62c 100644 --- a/src/test/java/be/twofold/tinybcdec/BC4UBenchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC4UBenchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC4UBenchmark { @State(Scope.Thread) public static class BC4UState { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC4UState() { try { decoder = BlockDecoder.bc4(false); src = BCTestUtils.readResource("/bc4u.dds"); - dst = new byte[256 * 256]; + dst = ByteBuffer.allocate(256 * 256); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC4UState() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC4UState state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC4UTest.java b/src/test/java/be/twofold/tinybcdec/BC4UTest.java index 600e11f..a6bbfcc 100644 --- a/src/test/java/be/twofold/tinybcdec/BC4UTest.java +++ b/src/test/java/be/twofold/tinybcdec/BC4UTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,10 +13,10 @@ class BC4UTest { @Test void testBC4U() throws IOException { - byte[] src = BCTestUtils.readResource("/bc4u.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc4u.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc4u.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc4u.png"); assertThat(actual).isEqualTo(expected); } diff --git a/src/test/java/be/twofold/tinybcdec/BC5STest.java b/src/test/java/be/twofold/tinybcdec/BC5STest.java index 45e71e5..def79ca 100644 --- a/src/test/java/be/twofold/tinybcdec/BC5STest.java +++ b/src/test/java/be/twofold/tinybcdec/BC5STest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -10,14 +11,14 @@ class BC5STest { @Test void testBC5S() throws IOException { - byte[] src = BCTestUtils.readResource("/bc5s.dds"); - byte[] actual = BlockDecoder.bc5(true) - .decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc5s.png"); + ByteBuffer src = BCTestUtils.readResource("/bc5s.dds"); + ByteBuffer actual = BlockDecoder.bc5(true) + .decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc5s.png"); - for (int i = 0, o = 0; i < expected.length; i += 3, o += 2) { - assertThat(Math.abs((actual[o/**/] & 0xFF) - (expected[i/**/] & 0xFF))).isLessThanOrEqualTo(1); - assertThat(Math.abs((actual[o + 1] & 0xFF) - (expected[i + 1] & 0xFF))).isLessThanOrEqualTo(1); + for (int i = 0, o = 0; i < expected.remaining(); i += 3, o += 2) { + assertThat(Math.abs((actual.get(o/**/) & 0xFF) - (expected.get(i/**/) & 0xFF))).isLessThanOrEqualTo(1); + assertThat(Math.abs((actual.get(o + 1) & 0xFF) - (expected.get(i + 1) & 0xFF))).isLessThanOrEqualTo(1); } } diff --git a/src/test/java/be/twofold/tinybcdec/BC5UBenchmark.java b/src/test/java/be/twofold/tinybcdec/BC5UBenchmark.java index aa5b9b9..13f2edc 100644 --- a/src/test/java/be/twofold/tinybcdec/BC5UBenchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC5UBenchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC5UBenchmark { @State(Scope.Thread) public static class BC5UState { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC5UState() { try { decoder = BlockDecoder.bc5(false); src = BCTestUtils.readResource("/bc5u.dds"); - dst = new byte[256 * 256 * 3]; + dst = ByteBuffer.allocate(256 * 256 * 3); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC5UState() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC5UState state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC5UTest.java b/src/test/java/be/twofold/tinybcdec/BC5UTest.java index f3dbdfc..00af5ce 100644 --- a/src/test/java/be/twofold/tinybcdec/BC5UTest.java +++ b/src/test/java/be/twofold/tinybcdec/BC5UTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -10,15 +11,15 @@ class BC5UTest { @Test void testBC5U() throws IOException { - byte[] src = BCTestUtils.readResource("/bc5u.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc5u.dds"); - byte[] actual = BlockDecoder.bc5(false) - .decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc5u.png"); + ByteBuffer actual = BlockDecoder.bc5(false) + .decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc5u.png"); - for (int i = 0, o = 0; i < expected.length; i += 3, o += 2) { - assertThat(actual[o/**/]).isEqualTo(expected[i/**/]); - assertThat(actual[o + 1]).isEqualTo(expected[i + 1]); + for (int i = 0, o = 0; i < expected.remaining(); i += 3, o += 2) { + assertThat(actual.get(o/**/)).isEqualTo(expected.get(i/**/)); + assertThat(actual.get(o + 1)).isEqualTo(expected.get(i + 1)); } } diff --git a/src/test/java/be/twofold/tinybcdec/BC6HBenchmark.java b/src/test/java/be/twofold/tinybcdec/BC6HBenchmark.java index d215a07..79515a2 100644 --- a/src/test/java/be/twofold/tinybcdec/BC6HBenchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC6HBenchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC6HBenchmark { @State(Scope.Thread) public static class BC6HState { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC6HState() { try { decoder = BlockDecoder.bc6h(false); src = BCTestUtils.readResource("/bc6h_uf16.dds"); - dst = new byte[256 * 256 * 6]; + dst = ByteBuffer.allocate(256 * 256 * 6); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC6HState() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC6HState state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC6HTest.java b/src/test/java/be/twofold/tinybcdec/BC6HTest.java index 6a9cffb..35914c6 100644 --- a/src/test/java/be/twofold/tinybcdec/BC6HTest.java +++ b/src/test/java/be/twofold/tinybcdec/BC6HTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -10,35 +11,35 @@ class BC6HTest { @Test void testBC6H_UF16() throws IOException { - byte[] src = BCTestUtils.readResource("/bc6h_uf16.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc6h_uf16.dds"); BlockDecoder decoder = BlockDecoder.bc6h(false); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readDDSFP16("/bc6h_uf16_16.dds", 256, 256); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readDDSFP16("/bc6h_uf16_16.dds", 256, 256); assertThat(actual).isEqualTo(expected); } @Test void testBC6H_SF16() throws IOException { - byte[] src = BCTestUtils.readResource("/bc6h_sf16.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc6h_sf16.dds"); BlockDecoder decoder = BlockDecoder.bc6h(true); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readDDSFP16("/bc6h_sf16_16.dds", 256, 256); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readDDSFP16("/bc6h_sf16_16.dds", 256, 256); assertThat(actual).isEqualTo(expected); } @Test void testBC6HInvalidBlock() { - byte[] src = new byte[16]; + ByteBuffer src = ByteBuffer.allocate(16); byte[] invalidModes = {0b10011, 0b10111, 0b11011, 0b11111}; for (byte invalidMode : invalidModes) { - src[0] = invalidMode; + src.put(0, invalidMode); BlockDecoder decoder = BlockDecoder.bc6h(false); - byte[] actual = decoder.decode(src, 0, 4, 4); - byte[] expected = new byte[16 * 2 * 3]; + ByteBuffer actual = decoder.decode(src, 4, 4); + ByteBuffer expected = ByteBuffer.allocate(16 * 2 * 3); assertThat(actual).isEqualTo(expected); } } diff --git a/src/test/java/be/twofold/tinybcdec/BC7Benchmark.java b/src/test/java/be/twofold/tinybcdec/BC7Benchmark.java index 1bdac14..43d696b 100644 --- a/src/test/java/be/twofold/tinybcdec/BC7Benchmark.java +++ b/src/test/java/be/twofold/tinybcdec/BC7Benchmark.java @@ -5,19 +5,20 @@ import org.openjdk.jmh.runner.options.*; import java.io.*; +import java.nio.*; public class BC7Benchmark { @State(Scope.Thread) public static class BC7State { private final BlockDecoder decoder; - private final byte[] src; - private final byte[] dst; + private final ByteBuffer src; + private final ByteBuffer dst; public BC7State() { try { decoder = BlockDecoder.bc7(); src = BCTestUtils.readResource("/bc7.dds"); - dst = new byte[256 * 256 * 4]; + dst = ByteBuffer.allocate(256 * 256 * 4); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -28,7 +29,7 @@ public BC7State() { @Warmup(iterations = 2, time = 5) @Measurement(iterations = 2, time = 5) public void benchmark(BC7State state) { - state.decoder.decode(state.src, BCTestUtils.DDS_HEADER_SIZE, 256, 256, state.dst, 0); + state.decoder.decode(state.src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256, state.dst); } public static void main(String[] args) throws RunnerException { diff --git a/src/test/java/be/twofold/tinybcdec/BC7Test.java b/src/test/java/be/twofold/tinybcdec/BC7Test.java index 3559445..4b272f7 100644 --- a/src/test/java/be/twofold/tinybcdec/BC7Test.java +++ b/src/test/java/be/twofold/tinybcdec/BC7Test.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -12,19 +13,19 @@ class BC7Test { @Test void testBC7() throws IOException { - byte[] src = BCTestUtils.readResource("/bc7.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc7.dds"); - byte[] actual = decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, 256, 256); - byte[] expected = BCTestUtils.readPng("/bc7.png"); + ByteBuffer actual = decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 256, 256); + ByteBuffer expected = BCTestUtils.readPng("/bc7.png"); assertThat(actual).isEqualTo(expected); } @Test void testBC7InvalidBlock() { - byte[] src = new byte[16]; - byte[] actual = decoder.decode(src, 0, 4, 4); - assertThat(actual).isEqualTo(new byte[16 * 4]); + ByteBuffer src = ByteBuffer.allocate(16); + ByteBuffer actual = decoder.decode(src, 4, 4); + assertThat(actual).isEqualTo(ByteBuffer.allocate(16 * 4)); } } diff --git a/src/test/java/be/twofold/tinybcdec/BCTestUtils.java b/src/test/java/be/twofold/tinybcdec/BCTestUtils.java index 425d386..1f8e43c 100644 --- a/src/test/java/be/twofold/tinybcdec/BCTestUtils.java +++ b/src/test/java/be/twofold/tinybcdec/BCTestUtils.java @@ -3,7 +3,7 @@ import javax.imageio.*; import java.awt.image.*; import java.io.*; -import java.util.*; +import java.nio.*; public final class BCTestUtils { public static final int DDS_HEADER_SIZE = 148; @@ -11,37 +11,32 @@ public final class BCTestUtils { private BCTestUtils() { } - public static byte[] readResource(String path) throws IOException { + public static ByteBuffer readResource(String path) throws IOException { try (InputStream in = BCTestUtils.class.getResourceAsStream(path)) { - return in.readAllBytes(); + return ByteBuffer.wrap(in.readAllBytes()); } } - static byte[] readPng(String path) throws IOException { + static ByteBuffer readPng(String path) throws IOException { try (InputStream in = BCTestUtils.class.getResourceAsStream(path)) { BufferedImage image = ImageIO.read(in); byte[] rawImage = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); - return decodeImage(rawImage, image.getType()); + return ByteBuffer.wrap(decodeImage(rawImage, image.getType())); } } - static byte[] readDDSFP16(String path, int width, int height) throws IOException { - byte[] rawImage = Arrays.copyOfRange(readResource(path), DDS_HEADER_SIZE, DDS_HEADER_SIZE + width * height * 8); - byte[] result = new byte[rawImage.length * 6 / 8]; + static ByteBuffer readDDSFP16(String path, int width, int height) throws IOException { + ByteBuffer rawImage = readResource(path) + .position(DDS_HEADER_SIZE) + .limit(DDS_HEADER_SIZE + width * height * 8) + .slice(); + ByteBuffer result = ByteBuffer.allocate(rawImage.remaining() * 6 / 8); - for (int i = 0, o = 0; i < rawImage.length; i += 8, o += 6) { - System.arraycopy(rawImage, i, result, o, 6); - } - return result; - } - - static byte[] readDDSFP32(String path, int width, int height) throws IOException { - byte[] rawImage = Arrays.copyOfRange(readResource(path), DDS_HEADER_SIZE, DDS_HEADER_SIZE + width * height * 16); - byte[] result = new byte[rawImage.length * 12 / 16]; - - for (int i = 0, o = 0; i < rawImage.length; i += 16, o += 12) { - System.arraycopy(rawImage, i, result, o, 12); + for (int i = 0, o = 0; i < rawImage.remaining(); i += 8, o += 6) { + for (int j = 0; j < 6; j++) { + result.put(o + j, rawImage.get(i + j)); + } } return result; } diff --git a/src/test/java/be/twofold/tinybcdec/BlockDecoderTest.java b/src/test/java/be/twofold/tinybcdec/BlockDecoderTest.java index 65c5207..bc3bd93 100644 --- a/src/test/java/be/twofold/tinybcdec/BlockDecoderTest.java +++ b/src/test/java/be/twofold/tinybcdec/BlockDecoderTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.*; import java.io.*; +import java.nio.*; import static org.assertj.core.api.Assertions.*; @@ -10,34 +11,34 @@ class BlockDecoderTest { @Test void testPartialBlock() throws IOException { - byte[] src = BCTestUtils.readResource("/bc4u-part.dds"); + ByteBuffer src = BCTestUtils.readResource("/bc4u-part.dds"); - byte[] actual = BlockDecoder.bc4(false) - .decode(src, BCTestUtils.DDS_HEADER_SIZE, 157, 119); - byte[] expected = BCTestUtils.readPng("/bc4u-part.png"); + ByteBuffer actual = BlockDecoder.bc4(false) + .decode(src.position(BCTestUtils.DDS_HEADER_SIZE), 157, 119); + ByteBuffer expected = BCTestUtils.readPng("/bc4u-part.png"); assertThat(actual).isEqualTo(expected); } @Test void testPartialBlockCrop() throws IOException { - byte[] src = BCTestUtils.readResource("/bc4u-part.dds"); - byte[] expected = BCTestUtils.readPng("/bc4u-part.png"); + ByteBuffer src = BCTestUtils.readResource("/bc4u-part.dds"); + ByteBuffer expected = BCTestUtils.readPng("/bc4u-part.png"); // Add a bit of chaos for everything int srcWidth = 157; int srcHeight = 119; int dstOffset = 31; - byte[] dst = new byte[8 * 8 + dstOffset]; + ByteBuffer dst = ByteBuffer.allocate(8 * 8 + dstOffset); var decoder = BlockDecoder.bc4(false); for (int h = 1; h <= 8; h++) { for (int w = 1; w <= 8; w++) { - decoder.decode(src, BCTestUtils.DDS_HEADER_SIZE, srcWidth, srcHeight, dst, dstOffset, w, h); + decoder.decode(src.position(BCTestUtils.DDS_HEADER_SIZE), srcWidth, srcHeight, dst.position(dstOffset), w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - assertThat(dst[y * w + x + dstOffset]).isEqualTo(expected[y * srcWidth + x]); + assertThat(dst.get(y * w + x + dstOffset)).isEqualTo(expected.get(y * srcWidth + x)); } } } @@ -46,14 +47,14 @@ void testPartialBlockCrop() throws IOException { @Test void testPartialBlockCropExtra() throws IOException { - byte[] src = BCTestUtils.readResource("/bc4u-part.dds"); - byte[] expected = BCTestUtils.readPng("/bc4u-part.png"); + ByteBuffer src = BCTestUtils.readResource("/bc4u-part.dds"); + ByteBuffer expected = BCTestUtils.readPng("/bc4u-part.png"); int srcWidth = 157; int srcHeight = 119; int dstOffset = 31; - byte[] dst = new byte[8 * 8 + dstOffset]; + ByteBuffer dst = ByteBuffer.allocate(8 * 8 + dstOffset); var decoder = BlockDecoder.bc4(false); // Test all offsets between 0 and 8 @@ -62,14 +63,14 @@ void testPartialBlockCropExtra() throws IOException { int w = 8; int h = 8; decoder.decode( - src, BCTestUtils.DDS_HEADER_SIZE, srcX, srcY, srcWidth, srcHeight, - dst, dstOffset, 0, 0, w, h, + src.position(BCTestUtils.DDS_HEADER_SIZE), srcX, srcY, srcWidth, srcHeight, + dst.position(dstOffset), 0, 0, w, h, w, h ); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - assertThat(dst[y * w + x + dstOffset]).isEqualTo(expected[(srcY + y) * srcWidth + (srcX + x)]); + assertThat(dst.get(y * w + x + dstOffset)).isEqualTo(expected.get((srcY + y) * srcWidth + (srcX + x))); } } } @@ -80,12 +81,12 @@ void testPartialBlockCropExtra() throws IOException { void testValidation() { assertThatIllegalArgumentException() .isThrownBy(() -> BlockDecoder.bc1(false) - .decode(null, 0, 0, 256)) + .decode(null, 0, 256)) .withMessage("src width (0) or height (256) is not positive"); assertThatIllegalArgumentException() .isThrownBy(() -> BlockDecoder.bc1(false) - .decode(null, 0, 256, 0)) + .decode(null, 256, 0)) .withMessage("src width (256) or height (0) is not positive"); } diff --git a/src/test/java/be/twofold/tinybcdec/PlatformTest.java b/src/test/java/be/twofold/tinybcdec/PlatformTest.java index fe55581..5ad25b5 100644 --- a/src/test/java/be/twofold/tinybcdec/PlatformTest.java +++ b/src/test/java/be/twofold/tinybcdec/PlatformTest.java @@ -16,8 +16,10 @@ void testFloat16ToFloat() { @Test void testFloat16ToFloat0() throws IOException { - byte[] bytes = BCTestUtils.readResource("/float16ToFloat.bin"); - var buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer(); + FloatBuffer buffer = BCTestUtils + .readResource("/float16ToFloat.bin") + .order(ByteOrder.LITTLE_ENDIAN) + .asFloatBuffer(); for (int i = Short.MIN_VALUE; i <= Short.MAX_VALUE; i++) { float actual = Platform.float16ToFloat0((short) i); From 53cbb73a2f6805b94cb263e8fded921f05b82fe1 Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 20:33:15 +0200 Subject: [PATCH 2/6] Rename ByteArrays, cleanup --- src/main/java/be/twofold/tinybcdec/BC1.java | 4 +- src/main/java/be/twofold/tinybcdec/BC2.java | 4 +- src/main/java/be/twofold/tinybcdec/BC4S.java | 4 +- src/main/java/be/twofold/tinybcdec/BC4U.java | 4 +- src/main/java/be/twofold/tinybcdec/BC6H.java | 8 +-- src/main/java/be/twofold/tinybcdec/BC7.java | 6 +- src/main/java/be/twofold/tinybcdec/BPTC.java | 4 +- .../be/twofold/tinybcdec/BlockDecoder.java | 2 +- .../java/be/twofold/tinybcdec/ByteArrays.java | 46 ------------- .../java/be/twofold/tinybcdec/ByteIO.java | 65 +++++++++++++++++++ 10 files changed, 83 insertions(+), 64 deletions(-) delete mode 100644 src/main/java/be/twofold/tinybcdec/ByteArrays.java create mode 100644 src/main/java/be/twofold/tinybcdec/ByteIO.java diff --git a/src/main/java/be/twofold/tinybcdec/BC1.java b/src/main/java/be/twofold/tinybcdec/BC1.java index 3a8fd38..d765db8 100644 --- a/src/main/java/be/twofold/tinybcdec/BC1.java +++ b/src/main/java/be/twofold/tinybcdec/BC1.java @@ -16,7 +16,7 @@ final class BC1 extends BlockDecoder { @Override public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { - long block = ByteArrays.getLong(src, srcPos); + long block = ByteIO.getLong(src, srcPos); int c0 = (int) (block/* */) & 0xFFFF; int c1 = (int) (block >>> 16) & 0xFFFF; @@ -56,7 +56,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { int color = colors[indices & 0x03]; - ByteArrays.setInt(dst, dstPos + x * BPP, color); + ByteIO.setInt(dst, dstPos + x * BPP, color); indices >>>= 2; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC2.java b/src/main/java/be/twofold/tinybcdec/BC2.java index 65fac38..eec2046 100644 --- a/src/main/java/be/twofold/tinybcdec/BC2.java +++ b/src/main/java/be/twofold/tinybcdec/BC2.java @@ -19,12 +19,12 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, } private void decodeAlpha(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { - long alphas = ByteArrays.getLong(src, srcPos); + long alphas = ByteIO.getLong(src, srcPos); for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = (byte) ((alphas & 15) * 17); - ByteArrays.setByte(dst, dstPos + x * BPP, alpha); + ByteIO.setByte(dst, dstPos + x * BPP, alpha); alphas >>>= 4; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC4S.java b/src/main/java/be/twofold/tinybcdec/BC4S.java index 8798e41..6a7f402 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4S.java +++ b/src/main/java/be/twofold/tinybcdec/BC4S.java @@ -9,7 +9,7 @@ final class BC4S extends BlockDecoder { @Override public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { - long block = ByteArrays.getLong(src, srcPos); + long block = ByteIO.getLong(src, srcPos); int a0 = Math.max(-127, (byte) (block/* */)); int a1 = Math.max(-127, (byte) (block >>> 8)); @@ -33,7 +33,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = alphas[(int) (indices & 0x07)]; - ByteArrays.setByte(dst, dstPos + x * bytesPerPixel, alpha); + ByteIO.setByte(dst, dstPos + x * bytesPerPixel, alpha); indices >>>= 3; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC4U.java b/src/main/java/be/twofold/tinybcdec/BC4U.java index e335744..9158e12 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4U.java +++ b/src/main/java/be/twofold/tinybcdec/BC4U.java @@ -9,7 +9,7 @@ final class BC4U extends BlockDecoder { @Override public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { - long block = ByteArrays.getLong(src, srcPos); + long block = ByteIO.getLong(src, srcPos); int a0 = (int) (block/* */) & 0xFF; int a1 = (int) (block >>> 8) & 0xFF; @@ -33,7 +33,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int x = 0; x < BLOCK_WIDTH; x++) { byte alpha = alphas[(int) (indices & 0x07)]; - ByteArrays.setByte(dst, dstPos + x * bytesPerPixel, alpha); + ByteIO.setByte(dst, dstPos + x * bytesPerPixel, alpha); indices >>>= 3; } dstPos += stride; diff --git a/src/main/java/be/twofold/tinybcdec/BC6H.java b/src/main/java/be/twofold/tinybcdec/BC6H.java index 7e2d2dc..2cf97fb 100644 --- a/src/main/java/be/twofold/tinybcdec/BC6H.java +++ b/src/main/java/be/twofold/tinybcdec/BC6H.java @@ -96,9 +96,9 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, partitions >>>= 2; int o = dstPos + x * BPP; - ByteArrays.setShort(dst, o/**/, r); - ByteArrays.setShort(dst, o + 2, g); - ByteArrays.setShort(dst, o + 4, b); + ByteIO.setShort(dst, o/**/, r); + ByteIO.setShort(dst, o + 2, g); + ByteIO.setShort(dst, o + 4, b); } dstPos += stride; } @@ -188,7 +188,7 @@ private int transformInverse(int value, int value0, int bits, boolean signed) { private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { - dst.put(dstPos + i, (byte) 0); + ByteIO.setByte(dst, dstPos + i, (byte) 0); } dstPos += stride; } diff --git a/src/main/java/be/twofold/tinybcdec/BC7.java b/src/main/java/be/twofold/tinybcdec/BC7.java index fb5a7d5..99e61f9 100644 --- a/src/main/java/be/twofold/tinybcdec/BC7.java +++ b/src/main/java/be/twofold/tinybcdec/BC7.java @@ -25,7 +25,7 @@ private BC7() { @Override public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { - int modeIndex = Integer.numberOfTrailingZeros(src.get(srcPos)); + int modeIndex = Integer.numberOfTrailingZeros(ByteIO.getByte(src, srcPos)); if (modeIndex >= MODES.size()) { fillInvalidBlock(dst, dstPos, stride); return; @@ -150,7 +150,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, } int rgba = r | g << 8 | b << 16 | a << 24; - ByteArrays.setInt(dst, dstPos + x * BPP, rgba); + ByteIO.setInt(dst, dstPos + x * BPP, rgba); } dstPos += stride; } @@ -159,7 +159,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { - dst.put(dstPos + i, (byte) 0); + ByteIO.setByte(dst, dstPos + i, (byte) 0); } dstPos += stride; } diff --git a/src/main/java/be/twofold/tinybcdec/BPTC.java b/src/main/java/be/twofold/tinybcdec/BPTC.java index 9050eab..362958e 100644 --- a/src/main/java/be/twofold/tinybcdec/BPTC.java +++ b/src/main/java/be/twofold/tinybcdec/BPTC.java @@ -118,8 +118,8 @@ private Bits(long lo, long hi) { } static Bits from(ByteBuffer buffer, int index) { - long lo = ByteArrays.getLong(buffer, index); - long hi = ByteArrays.getLong(buffer, index + 8); + long lo = ByteIO.getLong(buffer, index); + long hi = ByteIO.getLong(buffer, index + 8); return new Bits(lo, hi); } diff --git a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java index 18a06f4..ff59bb7 100644 --- a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java +++ b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java @@ -272,7 +272,7 @@ private void partialBlock( var srcOff = offset + (row * stride); var dstOff = dstPos + (row * lineStride); for (int i = 0; i < blockW * bytesPerPixel; i++) { - dst.put(dstOff + i, scratch.get(srcOff + i)); + ByteIO.setByte(dst, dstOff + i, ByteIO.getByte(scratch, srcOff + i)); } } } diff --git a/src/main/java/be/twofold/tinybcdec/ByteArrays.java b/src/main/java/be/twofold/tinybcdec/ByteArrays.java deleted file mode 100644 index 39ff0dd..0000000 --- a/src/main/java/be/twofold/tinybcdec/ByteArrays.java +++ /dev/null @@ -1,46 +0,0 @@ -package be.twofold.tinybcdec; - -import java.lang.invoke.*; -import java.nio.*; - -final class ByteArrays { - private static final VarHandle ShortVarHandle = - MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); - private static final VarHandle IntVarHandle = - MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); - private static final VarHandle LongVarHandle = - MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); - private static final VarHandle FloatVarHandle = - MethodHandles.byteArrayViewVarHandle(float[].class, ByteOrder.LITTLE_ENDIAN); - - private ByteArrays() { - } - - static void setByte(ByteBuffer buffer, int index, byte value) { - buffer.put(index, value); - } - - static short getShort(ByteBuffer buffer, int index) { - return buffer.getShort(index); - } - - static void setShort(ByteBuffer buffer, int index, short value) { - buffer.putShort(index, value); - } - - static int getInt(ByteBuffer buffer, int index) { - return buffer.getInt(index); - } - - static void setInt(ByteBuffer buffer, int index, int value) { - buffer.putInt(index, value); - } - - static long getLong(ByteBuffer buffer, int index) { - return buffer.getLong(index); - } - - static void setFloat(ByteBuffer buffer, int index, float value) { - buffer.putFloat(index, value); - } -} diff --git a/src/main/java/be/twofold/tinybcdec/ByteIO.java b/src/main/java/be/twofold/tinybcdec/ByteIO.java new file mode 100644 index 0000000..a838e44 --- /dev/null +++ b/src/main/java/be/twofold/tinybcdec/ByteIO.java @@ -0,0 +1,65 @@ +package be.twofold.tinybcdec; + +import java.lang.invoke.*; +import java.nio.*; + +/** + * ByteBuffer has many concrete subclasses (heap, direct, read-only, sliced, ...). Call sites that + * see 3+ receiver types go megamorphic and the JIT stops inlining, causing severe throughput drops. + * Routing through hasArray() keeps the hot path on a plain byte[] + VarHandle, which the JIT + * compiles to a single load/store instruction regardless of how many ByteBuffer types are in play. + *

+ * I can't believe I had to do this, but it drops the perf hit from 30% all the way down to 4% for BC1. + * Slower codecs lose less. + */ +final class ByteIO { + private static final VarHandle VH_SHORT = + MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_INT = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_LONG = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + + private ByteIO() { + } + + static byte getByte(ByteBuffer buffer, int index) { + if (buffer.hasArray()) { + return buffer.array()[buffer.arrayOffset() + index]; + } else { + return buffer.get(index); + } + } + + static void setByte(ByteBuffer buffer, int index, byte value) { + if (buffer.hasArray()) { + buffer.array()[buffer.arrayOffset() + index] = value; + } else { + buffer.put(index, value); + } + } + + static void setShort(ByteBuffer buffer, int index, short value) { + if (buffer.hasArray()) { + VH_SHORT.set(buffer.array(), buffer.arrayOffset() + index, value); + } else { + buffer.putShort(index, value); + } + } + + static void setInt(ByteBuffer buffer, int index, int value) { + if (buffer.hasArray()) { + VH_INT.set(buffer.array(), buffer.arrayOffset() + index, value); + } else { + buffer.putInt(index, value); + } + } + + static long getLong(ByteBuffer buffer, int index) { + if (buffer.hasArray()) { + return (long) VH_LONG.get(buffer.array(), buffer.arrayOffset() + index); + } else { + return buffer.getLong(index); + } + } +} From fa32c5fe4ca139983efe223dd58ef9b80452a7ff Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 20:53:05 +0200 Subject: [PATCH 3/6] Also add copy and fill --- src/main/java/be/twofold/tinybcdec/BC6H.java | 4 +- src/main/java/be/twofold/tinybcdec/BC7.java | 4 +- .../be/twofold/tinybcdec/BlockDecoder.java | 4 +- .../java/be/twofold/tinybcdec/ByteIO.java | 54 +++++++++++++------ 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/main/java/be/twofold/tinybcdec/BC6H.java b/src/main/java/be/twofold/tinybcdec/BC6H.java index 2cf97fb..f878c04 100644 --- a/src/main/java/be/twofold/tinybcdec/BC6H.java +++ b/src/main/java/be/twofold/tinybcdec/BC6H.java @@ -187,9 +187,7 @@ private int transformInverse(int value, int value0, int bits, boolean signed) { private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { - for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { - ByteIO.setByte(dst, dstPos + i, (byte) 0); - } + ByteIO.fill(dst, dstPos, BLOCK_WIDTH * BPP, (byte) 0); dstPos += stride; } } diff --git a/src/main/java/be/twofold/tinybcdec/BC7.java b/src/main/java/be/twofold/tinybcdec/BC7.java index 99e61f9..ba31ad5 100644 --- a/src/main/java/be/twofold/tinybcdec/BC7.java +++ b/src/main/java/be/twofold/tinybcdec/BC7.java @@ -158,9 +158,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { for (int y = 0; y < BLOCK_HEIGHT; y++) { - for (int i = 0; i < BLOCK_WIDTH * BPP; i++) { - ByteIO.setByte(dst, dstPos + i, (byte) 0); - } + ByteIO.fill(dst, dstPos, BLOCK_WIDTH * BPP, (byte) 0); dstPos += stride; } } diff --git a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java index ff59bb7..8bf26fd 100644 --- a/src/main/java/be/twofold/tinybcdec/BlockDecoder.java +++ b/src/main/java/be/twofold/tinybcdec/BlockDecoder.java @@ -271,9 +271,7 @@ private void partialBlock( for (int row = 0; row < blockH; row++) { var srcOff = offset + (row * stride); var dstOff = dstPos + (row * lineStride); - for (int i = 0; i < blockW * bytesPerPixel; i++) { - ByteIO.setByte(dst, dstOff + i, ByteIO.getByte(scratch, srcOff + i)); - } + ByteIO.copy(scratch, srcOff, dst, dstOff, blockW * bytesPerPixel); } } } diff --git a/src/main/java/be/twofold/tinybcdec/ByteIO.java b/src/main/java/be/twofold/tinybcdec/ByteIO.java index a838e44..b80e4bc 100644 --- a/src/main/java/be/twofold/tinybcdec/ByteIO.java +++ b/src/main/java/be/twofold/tinybcdec/ByteIO.java @@ -2,6 +2,7 @@ import java.lang.invoke.*; import java.nio.*; +import java.util.*; /** * ByteBuffer has many concrete subclasses (heap, direct, read-only, sliced, ...). Call sites that @@ -23,43 +24,66 @@ final class ByteIO { private ByteIO() { } - static byte getByte(ByteBuffer buffer, int index) { + static byte getByte(ByteBuffer buffer, int offset) { if (buffer.hasArray()) { - return buffer.array()[buffer.arrayOffset() + index]; + return buffer.array()[buffer.arrayOffset() + offset]; } else { - return buffer.get(index); + return buffer.get(offset); } } - static void setByte(ByteBuffer buffer, int index, byte value) { + static void setByte(ByteBuffer buffer, int offset, byte value) { if (buffer.hasArray()) { - buffer.array()[buffer.arrayOffset() + index] = value; + buffer.array()[buffer.arrayOffset() + offset] = value; } else { - buffer.put(index, value); + buffer.put(offset, value); } } - static void setShort(ByteBuffer buffer, int index, short value) { + static void setShort(ByteBuffer buffer, int offset, short value) { if (buffer.hasArray()) { - VH_SHORT.set(buffer.array(), buffer.arrayOffset() + index, value); + VH_SHORT.set(buffer.array(), buffer.arrayOffset() + offset, value); } else { - buffer.putShort(index, value); + buffer.putShort(offset, value); } } - static void setInt(ByteBuffer buffer, int index, int value) { + static void setInt(ByteBuffer buffer, int offset, int value) { if (buffer.hasArray()) { - VH_INT.set(buffer.array(), buffer.arrayOffset() + index, value); + VH_INT.set(buffer.array(), buffer.arrayOffset() + offset, value); } else { - buffer.putInt(index, value); + buffer.putInt(offset, value); } } - static long getLong(ByteBuffer buffer, int index) { + static long getLong(ByteBuffer buffer, int offset) { if (buffer.hasArray()) { - return (long) VH_LONG.get(buffer.array(), buffer.arrayOffset() + index); + return (long) VH_LONG.get(buffer.array(), buffer.arrayOffset() + offset); } else { - return buffer.getLong(index); + return buffer.getLong(offset); + } + } + + static void copy(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int length) { + if (src.hasArray() && dst.hasArray()) { + srcOff += src.arrayOffset(); + dstOff += dst.arrayOffset(); + System.arraycopy(src.array(), srcOff, dst.array(), dstOff, length); + } else { + for (int i = 0; i < length; i++) { + dst.put(dstOff + i, src.get(srcOff + i)); + } + } + } + + static void fill(ByteBuffer buffer, int offset, int length, byte value) { + if (buffer.hasArray()) { + offset += buffer.arrayOffset(); + Arrays.fill(buffer.array(), offset, offset + length, value); + } else { + for (int i = 0; i < length; i++) { + buffer.put(offset + i, value); + } } } } From e4bea50706555c0d5bd1cd51fae0c7ab44aa4876 Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 21:23:04 +0200 Subject: [PATCH 4/6] Update workflow --- .github/workflows/maven.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2a8472c..845197a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -11,17 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: '11' distribution: 'temurin' cache: maven - name: Build with Maven - run: mvn -B package --file pom.xml - -# - name: Submit Dependency Snapshot -# uses: advanced-security/maven-dependency-submission-action@v3 + run: mvn -B verify --file pom.xml From bab59149a2b74bf43fbe3a133ffaeac83ad4e30f Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 21:32:23 +0200 Subject: [PATCH 5/6] Share fillInvalidBlock --- src/main/java/be/twofold/tinybcdec/BC6H.java | 9 +-------- src/main/java/be/twofold/tinybcdec/BC7.java | 9 +-------- src/main/java/be/twofold/tinybcdec/BPTC.java | 11 +++++++++-- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/java/be/twofold/tinybcdec/BC6H.java b/src/main/java/be/twofold/tinybcdec/BC6H.java index f878c04..acbffb8 100644 --- a/src/main/java/be/twofold/tinybcdec/BC6H.java +++ b/src/main/java/be/twofold/tinybcdec/BC6H.java @@ -36,7 +36,7 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int modeIndex = mode(bits); if (modeIndex >= MODES.size()) { - fillInvalidBlock(dst, dstPos, stride); + fillInvalidBlock(dst, dstPos, stride, BPP); return; } Mode mode = MODES.get(modeIndex); @@ -185,13 +185,6 @@ private int transformInverse(int value, int value0, int bits, boolean signed) { return signed ? extendSign(value, bits) : value; } - private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { - for (int y = 0; y < BLOCK_HEIGHT; y++) { - ByteIO.fill(dst, dstPos, BLOCK_WIDTH * BPP, (byte) 0); - dstPos += stride; - } - } - private static final class Mode { private final boolean te; private final boolean pb; diff --git a/src/main/java/be/twofold/tinybcdec/BC7.java b/src/main/java/be/twofold/tinybcdec/BC7.java index ba31ad5..8162c4b 100644 --- a/src/main/java/be/twofold/tinybcdec/BC7.java +++ b/src/main/java/be/twofold/tinybcdec/BC7.java @@ -27,7 +27,7 @@ private BC7() { public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { int modeIndex = Integer.numberOfTrailingZeros(ByteIO.getByte(src, srcPos)); if (modeIndex >= MODES.size()) { - fillInvalidBlock(dst, dstPos, stride); + fillInvalidBlock(dst, dstPos, stride, BPP); return; } @@ -156,13 +156,6 @@ public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, } } - private static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride) { - for (int y = 0; y < BLOCK_HEIGHT; y++) { - ByteIO.fill(dst, dstPos, BLOCK_WIDTH * BPP, (byte) 0); - dstPos += stride; - } - } - private int unpack(int i, int n) { i = i << (8 - n); return i | i >>> n; diff --git a/src/main/java/be/twofold/tinybcdec/BPTC.java b/src/main/java/be/twofold/tinybcdec/BPTC.java index 362958e..62029cb 100644 --- a/src/main/java/be/twofold/tinybcdec/BPTC.java +++ b/src/main/java/be/twofold/tinybcdec/BPTC.java @@ -76,8 +76,8 @@ abstract class BPTC extends BlockDecoder { super(pixelStride, 16); } - static int partitions(int numPartitions, int partitionIndex) { - return PARTITIONS[numPartitions][partitionIndex]; + static int partitions(int numPartitions, int partition) { + return PARTITIONS[numPartitions][partition]; } static byte[] weights(int numIndexBits) { @@ -108,6 +108,13 @@ static int interpolate(int e0, int e1, int weight) { return (e0 * (64 - weight) + e1 * weight + 32) >>> 6; } + static void fillInvalidBlock(ByteBuffer dst, int dstPos, int stride, int bpp) { + for (int y = 0; y < BLOCK_HEIGHT; y++) { + ByteIO.fill(dst, dstPos, BLOCK_WIDTH * bpp, (byte) 0); + dstPos += stride; + } + } + static final class Bits { private long lo; private long hi; From fab655f1bf7f8386a1ac390f574c7a4d2b9bf82d Mon Sep 17 00:00:00 2001 From: Jan De Kock Date: Thu, 23 Apr 2026 21:35:33 +0200 Subject: [PATCH 6/6] Sign on deploy --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e28fb3f..0ded280 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,7 @@ sign-artifacts - verify + deploy sign