KhronosGroup / KTX-Software

KTX (Khronos Texture) Library and Tools
Other
854 stars 226 forks source link

The JNI binding for input swizzling is broken #875

Closed javagl closed 4 months ago

javagl commented 5 months ago

The setInputSwizzle method of the Java KtxBasisParams class receives an array of Java char values. (The same for KtxAstcParams - I'm focussing on one case here).

In the JNI layer, this swizzling information is supposed to be transferred into the char inputSwizzle[4]; array of the ktxBasisParams structure

This is done in this line, by casting the input value to a jbyteArray.

But ... the input it's not a jbyteArray, it's a jcharArray.

The point is: The size of char in Java is 16 bits. The size of char in C/C++ is 8 bits. (Usually. Who really knows?)

This means that the native inputSwizzle array receives "garbage" data. In the best case, the first two Java 16 bit char values might be interpreted as four C/C++ 8 bit char values. But in fact, when compiling in Debug mode, calling the setInputSwizzle function will trigger a debug assertion ("String subscript out of range", in xstring).

The following is a small test case: It generates an image that contains a red, green, blue, and white "stripe", compresses it, and writes it out.

package de.javagl.jktx;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.khronos.ktx.KtxBasisParams;
import org.khronos.ktx.KtxCreateStorage;
import org.khronos.ktx.KtxErrorCode;
import org.khronos.ktx.KtxTexture2;
import org.khronos.ktx.KtxTextureCreateInfo;
import org.khronos.ktx.VkFormat;

public class KtxSwizzleTest
{
    static
    {
        System.loadLibrary("./lib/ktx-jni");
    }

    private static int sizeX = 64;
    private static int sizeY = 64;

    public static void main(String[] args) throws IOException
    {
        Path output = Paths.get("./data/output_swizzle.ktx");

        KtxTextureCreateInfo info = new KtxTextureCreateInfo();
        info.setBaseWidth(sizeX);
        info.setBaseHeight(sizeY);
        info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
        KtxTexture2 t = KtxTexture2.create(info, KtxCreateStorage.ALLOC);

        byte rgba[] = createRgba();
        t.setImageFromMemory(0, 0, 0, rgba);

        KtxBasisParams p = new KtxBasisParams();
        p.setVerbose(true);
        p.setUastc(false);
        p.setInputSwizzle(new char[]{ 'r', 'g', 'b', 'a' });
        //p.setInputSwizzle(new char[]{ 'b', 'r', 'g', 'a' });
        int rc = t.compressBasisEx(p);
        if (rc != KtxErrorCode.SUCCESS)
        {
            throw new RuntimeException("basis error " + rc);
        }

        t.writeToNamedFile(output.toAbsolutePath().toString());

        System.out.println("Done, destroying");
        t.destroy();
    }

    // Create the RGBA pixels for an image that contains
    // 16 rows of red pixels
    // 16 rows of green pixels
    // 16 rows of blue pixels
    // 16 rows of white pixels
    private static byte[] createRgba()
    {
        byte[] rgba = new byte[sizeX * sizeY * 4];

        fillRows(rgba, 0, 16, 255, 0, 0, 255); // Red
        fillRows(rgba, 16, 32, 0, 255, 0, 255); // Green
        fillRows(rgba, 32, 48, 0, 0, 255, 255); // Blue
        fillRows(rgba, 48, 64, 255, 255, 255, 255); // White

        return rgba;
    }

    private static void fillRows(byte rgba[], int min, int max, int r, int g, int b, int a)
    {
        for (int y=min; y<max; y++)
        {
            for (int x=0; x<sizeX; x++)
            {
                int index = (y * sizeX) + x;
                rgba[index * 4 + 0] = (byte) r;
                rgba[index * 4 + 1] = (byte) g;
                rgba[index * 4 + 2] = (byte) b;
                rgba[index * 4 + 3] = (byte) a;
            }
        }

    }
}

The crucial line is the one where setInputSwizzle is called (the two options there can be commented out or used for the test)

Without calling setInputSwizzle, everything is fine (showing the PNG versions of the resulting KTX files here):

output_swizzle_unswizzled

When calling setInputSwizzle(new char[]{ 'r', 'g', 'b', 'a' }); (which should not change anything!), the output is this:

output_swizzle_rgba