Cannot get compressed texture to work

Started by N-o-N-o, September 29, 2017, 08:51:22

Previous topic - Next topic

N-o-N-o

Greetings,

I've been trying to implement this openGL tutorial with LWJGL
http://www.opengl-tutorial.org/beginners-tutorials/tutorial-5-a-textured-cube/
HoweverI can't seem to obtain anything but a black cube...

Note that I've been trying to get back into java too (which I haven't used since 2011), so the code is probably, uh. Less than optimal.

Here is my code, with long lists of points coordinate, colors, and function removed for clarity. Lemme know if I need to need to add anything.

    private void initOpenGl() {
        glfwMakeContextCurrent(window);
        // Create OpenGl capabilities
        GL.createCapabilities();
        // Set the clear color
        glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
        // Enable depth test
        glEnable(GL_DEPTH_TEST);
        // Accept fragment if it closer to the camera than the former one
        glDepthFunc(GL_LESS);
        glfwSwapBuffers(window);

    }

    private synchronized void graphicLoop() {
       (...)

        try (MemoryStack stack = stackPush()) {

            /**** Vertex buffer : our triangle ****/
            // Triangle coordinates for a cube
            //FloatBuffer triangleBuffer = stack.callocFloat(triangle.length);
            FloatBuffer triangleBuffer = stack.callocFloat(cubeTriangles.length);
            triangleBuffer.put(cubeTriangles);
            triangleBuffer.flip();

            /**** Color data ****/
            FloatBuffer colorBuffer = stack.callocFloat(colorData.length);
            colorBuffer.put(colorData);
            colorBuffer.flip();

            /**** Buffer objects. One for vertexes, one for colors ****/
            int vertexVbo, colorVbo, UV_vbo;
            IntBuffer vertexBuffer = stack.callocInt(1);
            glGenBuffers(vertexBuffer);
            vertexVbo = vertexBuffer.get(0);
            glBindBuffer(GL_ARRAY_BUFFER, vertexVbo);
            glBufferData(GL_ARRAY_BUFFER, triangleBuffer, GL_STATIC_DRAW);

            IntBuffer vertex2Buffer = stack.callocInt(1);
            glGenBuffers(vertex2Buffer);
            colorVbo = vertex2Buffer.get(0);
            glBindBuffer(GL_ARRAY_BUFFER, colorVbo);
            glBufferData(GL_ARRAY_BUFFER, colorBuffer, GL_STATIC_DRAW);

            IntBuffer UV_Buffer = stack.callocInt(1);
            glGenBuffers(UV_Buffer);
            UV_vbo = UV_Buffer.get(0);
            glBindBuffer(GL_ARRAY_BUFFER, UV_vbo);
            glBufferData(GL_ARRAY_BUFFER, colorBuffer, GL_STATIC_DRAW);

            
            int vbaName;
            IntBuffer vbaBuffer = stack.callocInt(1);
            vbaBuffer.put(glGenVertexArrays());
            vbaName = vbaBuffer.get(0);
            glBindVertexArray(vbaName);
            
            /**** Program ****/
            IntBuffer shaderBuffer = stack.callocInt(4);
            /*int vertexShader;
            shaderBuffer.put(0, glCreateShader(GL_VERTEX_SHADER));
            vertexShader = shaderBuffer.get(0);
            glShaderSource(vertexShader,readGLSLfile("vertex.glsl"));
            glCompileShader(vertexShader);*/


            int viewShader;
            shaderBuffer.put(2, glCreateShader(GL_VERTEX_SHADER));
            viewShader = shaderBuffer.get(2);
            glShaderSource(viewShader, readGLSLfile("mvpVertex.glsl"));

            int colorShader;
            shaderBuffer.put(1,glCreateShader(GL_FRAGMENT_SHADER));
            colorShader = shaderBuffer.get(1);
            glShaderSource(colorShader, readGLSLfile("color.glsl"));

            //Build glsl program
            int pID;
            shaderBuffer.put(3, glCreateProgram());
            pID = shaderBuffer.get(3);
            //glAttachShader(pID, vertexShader);
            glAttachShader(pID, viewShader);
            glAttachShader(pID, colorShader);
            glLinkProgram(pID);

            // Check for errors
            int status = glGetProgrami(pID, GL_LINK_STATUS);
            if (status == 0) {
                int infoLogLength = glGetProgrami(pID, GL_INFO_LOG_LENGTH);

                String strInfoLog = null;
                strInfoLog = glGetProgramInfoLog(pID, infoLogLength);

                System.err.printf("Linker failure: %s\n", strInfoLog);
            }

            Matrix4f mvp = new Matrix4f()
                    .perspective((float) Math.toRadians(90.0), 1.0f, 0.1f, 100.0f)
                    .lookAt(4.0f, 3.0f, 3.0f,
                            0.0f, 0.0f, 0.0f,
                            0.0f, 1.0f, 0.0f);
            FloatBuffer mvp_buffer = stack.callocFloat(16);
            mvp.get(mvp_buffer);

            IntBuffer intBuffer = stack.callocInt(1);
            intBuffer.put(glGetUniformLocation(pID, "MVP"));
            int mvpLocation = intBuffer.get(0);

            /**** Compressed texture ****/
            CompressedTexture texture = new CompressedTexture("uvtemplate.DDS");
            int textureGLSL_ID  = glGetUniformLocation(pID, "myTextureSampler");

            while (!myControlHandler.getWindowShouldClose()) {
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				
                glUseProgram(pID);
				
                glUniformMatrix4fv(mvpLocation, false, mvp_buffer);
				
                // Bind our texture in Texture Unit 0
                glActiveTexture(GL_TEXTURE0);
                glBindTexture(GL_TEXTURE_2D, texture.getTextureID());
                // Set our "myTextureSampler" sampler to use Texture Unit 0
                glUniform1i(textureGLSL_ID, 0);
				
                glEnableVertexAttribArray(0);
                glBindBuffer(GL_ARRAY_BUFFER, vertexVbo);
                glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
                glEnableVertexAttribArray(1);
                glBindBuffer(GL_ARRAY_BUFFER, colorVbo);
                glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0);
				
                glEnableVertexAttribArray(2);
                glBindBuffer(GL_ARRAY_BUFFER, UV_vbo);
				
                glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, 0);
                glDrawArrays(GL_TRIANGLES, 0, 3*12);
                glDisableVertexAttribArray(0);
                glDisableVertexAttribArray(1);
                glDisableVertexAttribArray(2);
                glfwSwapBuffers(window);
            }
        }
    }

public class CompressedTexture {

    private int textureID;
    private int height;
    private int width;
    private int linearSize;
    private int mipMapCount;
    private int fourCC;
    private int components;
    private int format;
    private int dataLength;
    private ByteBuffer textureData;

    public CompressedTexture(String fileName) {
        parseFile(fileName);
        textureID = glGenTextures();
        // "Bind" the newly created texture : all future texture functions will modify this texture
        glBindTexture(GL_TEXTURE_2D, textureID);
        glPixelStorei(GL_UNPACK_ALIGNMENT,1);
        loadMipmaps();
    }

    public void parseFile(String fileName) {
        fileName = "res/" + fileName;
        try (FileInputStream reader = new FileInputStream (fileName)) {
            byte[] header = new byte[4];
            reader.read(header);
            if (new String (header).compareTo(new String("DDS ")) != 0)
                throw new IOException("Not a DDS file");
            readHeader(reader);
            readData(reader);
        } catch (IOException x) {
            System.err.println(x);
        }
        components  = (fourCC == 0x31545844) ? 3 : 4;
        switch (fourCC) {
            case 0x30315844://DX10
                format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
                break;
            case 0x31545844://DXT1
                format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
                break;
            case 0x33545844://DXT3
                format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
                break;
            case 0x35545844://DXT5
                format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
                break;
            default:
                format = -1;
                try {
                    throw  new Exception("Unknown compressed texture type\n     ");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }

    private void readHeader(FileInputStream reader) throws IOException {
        /******* Reading header *******/
        byte[] header = new byte[4];
        reader.skip(8);
        reader.read(header);
        height = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt();
        reader.read(header);
        width = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt();
        reader.read(header);
        linearSize = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt();
        reader.skip(4);
        reader.read(header);
        mipMapCount = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt();
        reader.skip(52);
        reader.read(header);
        fourCC = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt();
        System.out.println("Height : " + height);
        System.out.println("width : " + width);
        System.out.println("linearSize : " + linearSize);
        System.out.println("mipMapCount : " + mipMapCount);
        System.out.printf("fourCC : %x\n", fourCC);
    }

    private void readData(FileInputStream reader) throws IOException {
        /******* Reading data *******/
        int buffSize = mipMapCount > 1 ? linearSize * 2 : linearSize;
        //MemoryUtil.MemoryAllocator memAlloc = MemoryUtil.getAllocator();
        //this.textureData = MemoryUtil.memCalloc(buffSize);
        this.textureData = ByteBuffer.allocate(buffSize);
        this.dataLength = reader.read(textureData.array(), 0, buffSize);
        textureData.flip();

    }

    private void loadMipmaps() {
        int blockSize = (format==GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
        int loopWidth = this.width;
        int loopHeight = this.height;
        ByteBuffer data = BufferUtils.createByteBuffer(dataLength);
        data.put(textureData);
        data.rewind();
        for(int level = 0; level < mipMapCount; ++level) {
            int size = ((loopWidth+3)/4)*((loopHeight+3)/4)*blockSize;
            ByteBuffer mipMapData = BufferUtils.createByteBuffer(size);
            byte[] mipMapByte = new byte[size];
            data.get(mipMapByte);
            mipMapData.put(mipMapByte);
            mipMapData.rewind();
            glCompressedTexImage2D(GL_TEXTURE_2D,
                    level,
                    format,
                    loopWidth,
                    loopHeight,
                    0,
                    mipMapData);
            loopWidth  /= 2;
            loopHeight /= 2;
        }
    }


I hope I'm not breaking any rules with first post.

Any help would be greatly appreciated.

EDIT :

Ah. I suppose I should add GLSL files !

#version 330 core

// Interpolated values from the vertex shaders
in vec3 fragmentColor;
// Interpolated values from the vertex shaders
in vec2 UV;

// Ouput data
out vec3 color;

// Values that stay constant for the whole mesh.
uniform sampler2D myTextureSampler;

void main(){

	// Output color = color specified in the vertex shader,
	// interpolated between all 3 surrounding vertices
//	color = fragmentColor;
    color = texture( myTextureSampler, UV ).rgb;
//    color = vec3(1, UV );//colors the cube : UV values are set
}

#version 330 core

//in vec3 vertexPosition_modelspace;
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec3 vertexColor;
layout(location = 2) in vec2 vertexUV;

// Output data ; will be interpolated for each fragment.
out vec2 UV;
out vec3 fragmentColor;

// Values that stay constant for the whole mesh.
uniform mat4 MVP;

void main(){
    // Output position of the vertex, in clip space : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

    // Color / UV
    fragmentColor = vertexColor;
    UV = vertexUV;
}


I've been editing the code for some time now, and i'm honestly at a loss...

spasi

Try calling GLUtil.setupDebugMessageCallback(); after GL.createCapabilities(); to make sure the code isn't raising any OpenGL errors.

I would also try a simpler texture (uncompressed, no mipmaps) to make sure the shader doesn't have any issues.

N-o-N-o

Thanks for your answer. I've made a new Bitmap texture class, and added the debug callback as you advised.

Here are the callback messages :
Quote[LWJGL] OpenGL debug message
   ID: 0x20071
   Source: API
   Type: OTHER
   Severity: NOTIFICATION
   Message: Buffer detailed info: Buffer object 1 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.
[LWJGL] OpenGL debug message
   ID: 0x20071
   Source: API
   Type: OTHER
   Severity: NOTIFICATION
   Message: Buffer detailed info: Buffer object 2 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.
[LWJGL] OpenGL debug message
   ID: 0x20071
   Source: API
   Type: OTHER
   Severity: NOTIFICATION
   Message: Buffer detailed info: Buffer object 3 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.

Weird thing is, the program doesn't close on clicking the close button (crashes instead). This behaviour stop if I remove the display loop.

Here's the new BitmapTexture class :

public class BitmapTexture {

    private int textureID;
    private int dataPos;
    private int imageSize;
    private int height;
    private int width;
    private int dataLength;
    private ByteBuffer textureData;

    public BitmapTexture(String fileName) {
        parseFile(fileName);
        textureID = glGenTextures();
        // "Bind" the newly created texture : all future texture functions will modify this texture
        glBindTexture(GL_TEXTURE_2D, textureID);
        glPixelStorei(GL_UNPACK_ALIGNMENT,1);
        glTexImage2D(GL_TEXTURE_2D,
                0,
                GL_RGB,
                width,
                height,
                0,
                GL_BGR,
                GL_UNSIGNED_BYTE,
                textureData);
    }

    public void parseFile(String fileName) {
        fileName = "res/" + fileName;
        try (FileInputStream reader = new FileInputStream (fileName)) {
            readHeader(reader);
            readData(reader);
        } catch (IOException x) {
            System.err.println(x);
        }
    }

    private void readHeader(FileInputStream reader) throws IOException {
        /******* Reading header *******/
        byte[] header = new byte[54];
        reader.read(header);
        if (header[0] !='B' || header[1] != 'M')
            throw new IOException("Not a BMP file");

        /****** reading into a ByteBuffer ******/
        ByteBuffer intBuffer = ByteBuffer.allocate(24);
        intBuffer.put(header, 0x1E, 4);
        intBuffer.put(header, 0x1C, 4);
        intBuffer.put(header, 0x0A, 4);
        intBuffer.put(header, 0x12, 4);
        intBuffer.put(header, 0x16, 4);
        intBuffer.put(header, 0x22, 4);
        intBuffer.flip();

        if (intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt() != 0 ||
                intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt() != 24)
            throw new IOException("Not a valid BMP file");
        dataPos = intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
        height = intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
        width = intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
        imageSize = intBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt();

        System.out.println("dataPos : " + dataPos);
        System.out.println("Height : " + height);
        System.out.println("width : " + width);
        System.out.println("imageSize : " + imageSize);
    }

    private void readData(FileInputStream reader) throws IOException {
        /******* Reading data *******/
        textureData = ByteBuffer.allocate(imageSize);
        dataLength = reader.read(textureData.array(), 0, imageSize);
        textureData.flip();
    }

    public int getTextureID() {
        return textureID;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public ByteBuffer getTextureData() {
        return textureData;
    }

    public void flipTextureData() {
        textureData.flip();
    }

}


spasi

Hard to be helpful without seeing more of the code. Could you please prepare an MVCE?

Btw, why not use stb_image instead of parsing the .bmp manually?

N-o-N-o

I'll try to use stb_image to see if it fixes things first. I'll keep you updated with a workable exemple after that.

N-o-N-o

Here is a simpler sample, with the same issue, single java class, image and two glsl file. You will need to edit where (I used absolute pathes) the three files are in code.

https://files.fm/u/tepuuruu

spasi

The application appears hang, because your rendering loop is missing glfwPollEvents().

The texture sampling doesn't work, because your texture is incomplete (missing mipmap levels). Use glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) to fix.

N-o-N-o

Quote from: spasi on October 02, 2017, 11:57:30
The application appears hang, because your rendering loop is missing glfwPollEvents().

The texture sampling doesn't work, because your texture is incomplete (missing mipmap levels). Use glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) to fix.
Hmm, this is not displaying what i want to (probably bad set of UV coordinates), but this is a step forward.

Thanks for your help !

spasi

Quote from: N-o-N-o on October 02, 2017, 12:13:39Hmm, this is not displaying what i want to (probably bad set of UV coordinates), but this is a step forward.

Right, that was another problem. Put magicUVdata instead of cubeTriangles in the uvBuffer.

N-o-N-o

Ah, just figured it out. Didn't expect help for that one. Thanks

I thought mipmap level were only used for compressed texture, so I'm surprised the issue is here with a bitmap texture...

spasi

Quote from: N-o-N-o on October 02, 2017, 12:27:06I thought mipmap level were only used for compressed texture, so I'm surprised the issue is here with a bitmap texture...

The minification filter works regardless of texture type/format. Compressed textures are exactly like normal textures, except they burn less bandwidth at the expense of quality.

There are a few other improvements I would recommend.

1. Instead of:

float[] cubeTriangles = {
    // lots of float literals
};

FloatBuffer triangleBuffer = stack.callocFloat(cubeTriangles.length);
triangleBuffer.put(cubeTriangles);
triangleBuffer.flip();


you can do:

FloatBuffer triangleBuffer = stack.floats(
    // lots of float literals
);


2. Instead of:

IntBuffer vboBuffer = stack.callocInt(1);
glGenBuffers(vboBuffer);
int vertexVbo = vboBuffer.get(0);


you can do:

int vertexVbo = glGenBuffers();


3. The buffer here is redundant:

int vbaName;
IntBuffer vbaBuffer = stack.callocInt(1);
vbaBuffer.put(glGenVertexArrays());
vbaName = vbaBuffer.get(0);


just do:

int vbaName = glGenVertexArrays();

N-o-N-o

Thanks.

Uhhh, well.

Some of the IntBuffer stuff happened when you try to turn to magic to solve issues with code.

Which next bring the question, when should I bring the memory stack ? I seem to need it for stbi_load to work, for example.

spasi

Quote from: N-o-N-o on October 03, 2017, 06:01:26when should I bring the memory stack ?

Whenever you need a small, short-lived buffer. One good example from your code is the mvp_buffer for updating the MVP uniform.

N-o-N-o

Hello again,

I tried to switch back to compressed textures, and I still get a black cube.

I compared the C++ code from the tutorial with the one for BMP files, and I see no glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) or the likes...

Which brings several questions :

Should I use some other parameters ?
Does defining multiple mipmap levels make the setting irrelevant ?
Is there any built-in compressed texture loader like stb ?

Any help would be appreciated.

spasi

Quote from: N-o-N-o on October 11, 2017, 12:26:03Should I use some other parameters ?
Does defining multiple mipmap levels make the setting irrelevant ?

The default value for GL_TEXTURE_MIN_FILTER is GL_NEAREST_MIPMAP_LINEAR. If *_MIPMAP_* is used, then all mipmap levels must be present. If not, you may define mipmaps, but they won't be used.

Quote from: N-o-N-o on October 11, 2017, 12:26:03Is there any built-in compressed texture loader like stb ?

No, only stb_image and Tiny OpenEXR. Which .dds loader do you use?