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 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 diff --git a/src/main/java/be/twofold/tinybcdec/BC1.java b/src/main/java/be/twofold/tinybcdec/BC1.java index 80ebba1..d765db8 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,8 +15,8 @@ final class BC1 extends BlockDecoder { } @Override - public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stride) { - long block = ByteArrays.getLong(src, srcPos); + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { + long block = ByteIO.getLong(src, srcPos); int c0 = (int) (block/* */) & 0xFFFF; int c1 = (int) (block >>> 16) & 0xFFFF; @@ -54,7 +56,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++) { 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 cf96fb6..eec2046 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) { - long alphas = ByteArrays.getLong(src, srcPos); + private void decodeAlpha(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { + 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); - 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/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..6a7f402 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4S.java +++ b/src/main/java/be/twofold/tinybcdec/BC4S.java @@ -1,13 +1,15 @@ 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) { - long block = ByteArrays.getLong(src, srcPos); + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { + long block = ByteIO.getLong(src, srcPos); int a0 = Math.max(-127, (byte) (block/* */)); int a1 = Math.max(-127, (byte) (block >>> 8)); @@ -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; + 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 b5fba2b..9158e12 100644 --- a/src/main/java/be/twofold/tinybcdec/BC4U.java +++ b/src/main/java/be/twofold/tinybcdec/BC4U.java @@ -1,13 +1,15 @@ 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) { - long block = ByteArrays.getLong(src, srcPos); + public void decodeBlock(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int stride) { + long block = ByteIO.getLong(src, srcPos); int a0 = (int) (block/* */) & 0xFF; int a1 = (int) (block >>> 8) & 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; + ByteIO.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..acbffb8 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,12 +31,12 @@ 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); if (modeIndex >= MODES.size()) { - fillInvalidBlock(dst, dstPos, stride); + fillInvalidBlock(dst, dstPos, stride, BPP); return; } Mode mode = MODES.get(modeIndex); @@ -95,9 +96,9 @@ public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stri 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; } @@ -184,13 +185,6 @@ 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) { - for (int y = 0; y < BLOCK_HEIGHT; y++) { - Arrays.fill(dst, dstPos, 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 18886fa..8162c4b 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,10 +24,10 @@ 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(ByteIO.getByte(src, srcPos)); if (modeIndex >= MODES.size()) { - fillInvalidBlock(dst, dstPos, stride); + fillInvalidBlock(dst, dstPos, stride, BPP); return; } @@ -149,19 +150,12 @@ public void decodeBlock(byte[] src, int srcPos, byte[] dst, int dstPos, int stri } 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; } } - private static void fillInvalidBlock(byte[] dst, int dstPos, int stride) { - for (int y = 0; y < BLOCK_HEIGHT; y++) { - Arrays.fill(dst, dstPos, 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 7f09caa..62029cb 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 = { {}, @@ -74,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) { @@ -106,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; @@ -115,9 +124,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 = 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 f18ab93..8bf26fd 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,23 @@ 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); + ByteIO.copy(scratch, srcOff, dst, dstOff, blockW * bytesPerPixel); } } } 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 341f72a..0000000 --- a/src/main/java/be/twofold/tinybcdec/ByteArrays.java +++ /dev/null @@ -1,42 +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 short getShort(byte[] array, int index) { - return (short) ShortVarHandle.get(array, index); - } - - static void setShort(byte[] array, int index, short value) { - ShortVarHandle.set(array, index, value); - } - - static int getInt(byte[] array, int index) { - return (int) IntVarHandle.get(array, index); - } - - static void setInt(byte[] array, int index, int value) { - IntVarHandle.set(array, index, value); - } - - static long getLong(byte[] array, int index) { - return (long) LongVarHandle.get(array, index); - } - - static void setFloat(byte[] array, int index, float value) { - FloatVarHandle.set(array, 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..b80e4bc --- /dev/null +++ b/src/main/java/be/twofold/tinybcdec/ByteIO.java @@ -0,0 +1,89 @@ +package be.twofold.tinybcdec; + +import java.lang.invoke.*; +import java.nio.*; +import java.util.*; + +/** + * 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 offset) { + if (buffer.hasArray()) { + return buffer.array()[buffer.arrayOffset() + offset]; + } else { + return buffer.get(offset); + } + } + + static void setByte(ByteBuffer buffer, int offset, byte value) { + if (buffer.hasArray()) { + buffer.array()[buffer.arrayOffset() + offset] = value; + } else { + buffer.put(offset, value); + } + } + + static void setShort(ByteBuffer buffer, int offset, short value) { + if (buffer.hasArray()) { + VH_SHORT.set(buffer.array(), buffer.arrayOffset() + offset, value); + } else { + buffer.putShort(offset, value); + } + } + + static void setInt(ByteBuffer buffer, int offset, int value) { + if (buffer.hasArray()) { + VH_INT.set(buffer.array(), buffer.arrayOffset() + offset, value); + } else { + buffer.putInt(offset, value); + } + } + + static long getLong(ByteBuffer buffer, int offset) { + if (buffer.hasArray()) { + return (long) VH_LONG.get(buffer.array(), buffer.arrayOffset() + offset); + } else { + 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); + } + } + } +} 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);