[CLOSED] [3.0.0a] Rendering a basic texture with modern GL

Started by whizvox, March 15, 2016, 19:57:15

Previous topic - Next topic

whizvox

Hey guys. I've been trying to work with this for a while. I know how to render stuff with VBOs and glBegin(), so I'm still new to the concept of shaders. My tutorial searching and experimenting has resulted in this code, which only produces a black screen:

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.stb.STBImage.*;
import static org.lwjgl.system.MemoryUtil.NULL;

public class ModernRendering {

    private static final String SHADER_VERTEX =
            "#version 150 core\n" +
            "in vec2 vertexPosition;\n" +
            "in vec2 textureCoord;\n" +
            "in vec3 fragColor;\n" +
            "out vec2 f_texCoord;\n" +
            "out vec3 f_fragColor;\n" +
            "uniform mat4 model;\n" +
            "uniform mat4 view;\n" +
            "uniform mat4 projection;\n" +
            "void main() {\n" +
            "    f_texCoord = textureCoord;\n" +
            "    f_fragColor = fragColor;\n" +
            "    mat4 mvp = model * view * projection;\n" +
            "    gl_Position = mvp * vec4(vertexPosition, 0.0, 1.0);\n" +
            "}";
    private static final String SHADER_FRAGMENT =
            "#version 150 core\n" +
            "in vec2 f_texCoord;\n" +
            "in vec3 f_fragColor;\n" +
            "out vec4 finalFragColor;\n" +
            "uniform sampler2D tex;\n" +
            "void main() {\n" +
            "    vec4 textureColor = texture(tex, f_texCoord);\n" +
            "    finalFragColor = textureColor * vec4(f_fragColor, 1.0);\n" +
            "}";

    private static final String TEXTURE_FILE_PATH = "Z:\\My Pictures\\truss1.png";

    private static final int WIDTH = 800, HEIGHT = 600, VERTEX_SIZE = 7, VERTEX_COUNT = 6;

    static long window;
    static GLFWErrorCallback errorCallback;
    static GLFWKeyCallback keyCallback;

    static int vao, vbo, shader_v, shader_f, prog, tex;
    static FloatBuffer vertices;

    static void initGLFW() {
        glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));
        if (glfwInit() != GLFW_TRUE) {
            throw new RuntimeException("Could not initialize GLFW context");
        }

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello world!", NULL, NULL);
        if (window == NULL) {
            glfwTerminate();
            throw new RuntimeException("Could not initialize GLFW window");
        }

        GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        glfwSetWindowPos(window, (vidMode.width() - WIDTH) / 2, (vidMode.height() - HEIGHT) / 2);

        glfwMakeContextCurrent(window);
        GL.createCapabilities();
        glfwSwapInterval(1);

        glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(long window, int key, int scancode, int action, int mods) {
                if (key == GLFW_KEY_ESCAPE) {
                    glfwSetWindowShouldClose(window, GLFW_TRUE);
                }
            }
        });
    }

    static void initRenderObjects() {
        vertices = BufferUtils.createFloatBuffer(VERTEX_SIZE * VERTEX_COUNT);
        float x1 = -0.5f, y1 = 0.5f, x2 = 1.5f, y2 = 1.5f, r = 1f, g = 1f, b = 1f, s1 = 0f, t1 = 0f, s2 = 1f, t2 = 0f;
        vertices.put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x1).put(y2).put(s1).put(t2).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x2).put(y1).put(s2).put(t1).put(r).put(g).put(b);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_LIGHTING);

        vao = glGenVertexArrays();
        glBindVertexArray(vao);
        vbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);

        shader_v = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(shader_v, SHADER_VERTEX);
        glCompileShader(shader_v);
        if (glGetShaderi(shader_v, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create vertex shader");
        }

        shader_f = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(shader_f, SHADER_FRAGMENT);
        glCompileShader(shader_f);
        if (glGetShaderi(shader_f, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create fragment shader");
        }

        prog = glCreateProgram();
        glAttachShader(prog, shader_v);
        glAttachShader(prog, shader_f);
        glBindFragDataLocation(prog, 0, "finalFragColor");
        glLinkProgram(prog);
        if (glGetProgrami(prog, GL_LINK_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not link shaders in program");
        }
        glUseProgram(prog);

        int attrib_position = glGetAttribLocation(prog, "position");
        glEnableVertexAttribArray(attrib_position);
        glVertexAttribPointer(attrib_position, Float.SIZE * 2, GL_FLOAT, false, Float.SIZE * VERTEX_SIZE, 0);
        int attrib_texCoords = glGetAttribLocation(prog, "texCoords");
        glEnableVertexAttribArray(attrib_texCoords);
        glVertexAttribPointer(attrib_texCoords, Float.SIZE * 2, GL_FLOAT, false, Float.SIZE * VERTEX_SIZE, Float.SIZE * 2);
        int attrib_color = glGetAttribLocation(prog, "color");
        glEnableVertexAttribArray(attrib_color);
        glVertexAttribPointer(attrib_color, Float.SIZE * 3, GL_FLOAT, false, Float.SIZE * VERTEX_SIZE, Float.SIZE * 4);

        int uni_tex = glGetUniformLocation(prog, "tex");
        glUniform1i(uni_tex, 0);
        int uni_model = glGetUniformLocation(prog, "model");
        Matrix4f mat_model = new Matrix4f();
        glUniform4fv(uni_model, mat_model.getBuffer());
        int uni_view = glGetUniformLocation(prog, "view");
        Matrix4f mat_view = new Matrix4f();
        glUniform4fv(uni_view, mat_view.getBuffer());
        int uni_projection = glGetUniformLocation(prog, "projection");
        Matrix4f mat_projection = Matrix4f.orthographic(-1f, 1f, -1f, 1f, -1f, 1f);
        glUniform4fv(uni_projection, mat_projection.getBuffer());

        stbi_set_flip_vertically_on_load(GL_TRUE);
        IntBuffer
                tex_width = BufferUtils.createIntBuffer(1),
                tex_height = BufferUtils.createIntBuffer(1),
                tex_comp = BufferUtils.createIntBuffer(1);
        ByteBuffer tex_data = stbi_load(TEXTURE_FILE_PATH, tex_width, tex_height, tex_comp, 4);
        if (tex_data == null) {
            throw new RuntimeException("Could not load texture: " + TEXTURE_FILE_PATH);
        }

        tex = glGenTextures();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width.get(), tex_height.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
    }

    static void update() {
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    static void draw() {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glBindVertexArray(vao);
        glUseProgram(prog);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);

        glDrawArrays(GL_TRIANGLES, 0, VERTEX_COUNT);

        glUseProgram(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glfwSwapBuffers(window);
    }

    static void dispose() {
        glfwTerminate();
    }

    static boolean shouldClose() {
        return glfwWindowShouldClose(window) == GLFW_TRUE;
    }

    public static void main(String[] args) {
        try {
            initGLFW();
            initRenderObjects();
            while (!shouldClose()) {
                update();
                draw();
            }
        } finally {
            dispose();
        }
    }

}


And here's the Matrix4f class, which was taken from here: https://github.com/SilverTiger/lwjgl3-tutorial/blob/master/src/silvertiger/tutorial/lwjgl/math/Matrix4f.java

Like I said, I'm new to the idea of shaders and attributes and that stuff, so I have no idea what I'm doing wrong.

Kai

First thing I noticed, is that your vertex attributes are named inconsistently between how they are named in the vertex shader and how you are looking them up via glGetAttribLocation.
Second: when working with NIO buffers, you need to .flip() them after you filled them, so that their position is back to zero before handing them off to LWJGL/OpenGL to read from.
Third: the <size> parameter of glVertexAttribPointer is not in byte
Fourth: Float.SIZE is not the number of basic machine units (i.e. bytes) of a --->32bit<--- IEEE754 float
Fifth: glEnable(GL_TEXTURE_2D) is illegal in the core profile
Sixth: glUniform4fv is not the right way to set a matrix uniform in a shader
Seventh: The multiplication order of your model * view * projection matrices in the vertex shader is wrong (this does not change anything at the moment, because those matrices are all identity matrices anyways, but it will be noticeable when you use non-identity matrices)

whizvox

Thanks for the pointers! I've updated the code now:

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.stb.STBImage.*;
import static org.lwjgl.system.MemoryUtil.NULL;

public class ModernRendering {

    // attribute names now correspond
    private static final String SHADER_VERTEX =
            "#version 150 core\n" +
            "in vec2 position;\n" +
            "in vec2 texCoord;\n" +
            "in vec3 color;\n" +
            "out vec2 f_texCoord;\n" +
            "out vec3 f_color;\n" +
            "uniform mat4 model;\n" +
            "uniform mat4 view;\n" +
            "uniform mat4 projection;\n" +
            "void main() {\n" +
            "    f_texCoord = texCoord;\n" +
            "    f_color = color;\n" +
            "    mat4 mvp = projection * view * model;\n" +
            "    gl_Position = mvp * vec4(position, 0.0, 1.0);\n" +
            "}";
    private static final String SHADER_FRAGMENT =
            "#version 150 core\n" +
            "in vec2 f_texCoord;\n" +
            "in vec3 f_color;\n" +
            "out vec4 fragColor;\n" +
            "uniform sampler2D tex;\n" +
            "void main() {\n" +
            "    vec4 textureColor = texture(tex, f_texCoord);\n" +
            "    fragColor = textureColor * vec4(f_color, 1.0);\n" +
            "}";

    private static final String TEXTURE_FILE_PATH = "Z:\\My Pictures\\truss1.png";

    private static final int WIDTH = 800, HEIGHT = 600, VERTEX_SIZE = 7, VERTEX_COUNT = 6;

    static long window;
    static GLFWErrorCallback errorCallback;
    static GLFWKeyCallback keyCallback;

    static int vao, vbo, shader_v, shader_f, prog, tex;
    static FloatBuffer vertices;

    static void initGLFW() {
        glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));
        if (glfwInit() != GLFW_TRUE) {
            throw new RuntimeException("Could not initialize GLFW context");
        }

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello world!", NULL, NULL);
        if (window == NULL) {
            glfwTerminate();
            throw new RuntimeException("Could not initialize GLFW window");
        }

        GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        glfwSetWindowPos(window, (vidMode.width() - WIDTH) / 2, (vidMode.height() - HEIGHT) / 2);

        glfwMakeContextCurrent(window);
        GL.createCapabilities();
        glfwSwapInterval(1);

        glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(long window, int key, int scancode, int action, int mods) {
                if (key == GLFW_KEY_ESCAPE) {
                    glfwSetWindowShouldClose(window, GLFW_TRUE);
                }
            }
        });
    }

    static void initRenderObjects() {
        vertices = BufferUtils.createFloatBuffer(VERTEX_SIZE * VERTEX_COUNT);
        float x = 0f, y = 0f, width = 1f, height = 1f;
        float x1 = x, y1 = y, x2 = x + width, y2 = y + height, s1 = 0f, t1 = 0f, s2 = 1f, t2 = 0f, r = 1f, g = 1f, b = 1f;
        vertices.put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x1).put(y2).put(s1).put(t2).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x2).put(y1).put(s2).put(t1).put(r).put(g).put(b);
        // buffer is now flipped
        vertices.flip();

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        vao = glGenVertexArrays();
        glBindVertexArray(vao);
        vbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);

        shader_v = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(shader_v, SHADER_VERTEX);
        glCompileShader(shader_v);
        if (glGetShaderi(shader_v, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create vertex shader");
        }

        shader_f = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(shader_f, SHADER_FRAGMENT);
        glCompileShader(shader_f);
        if (glGetShaderi(shader_f, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create fragment shader");
        }

        prog = glCreateProgram();
        glAttachShader(prog, shader_v);
        glAttachShader(prog, shader_f);
        glBindFragDataLocation(prog, 0, "fragColor");
        glLinkProgram(prog);
        if (glGetProgrami(prog, GL_LINK_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not link shaders in program");
        }
        glUseProgram(prog);

        int attrib_position = glGetAttribLocation(prog, "position");
        glEnableVertexAttribArray(attrib_position);
        // attributes are now properly initialized
        glVertexAttribPointer(attrib_position, 2, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, 0);
        int attrib_texCoords = glGetAttribLocation(prog, "texCoord");
        glEnableVertexAttribArray(attrib_texCoords);
        glVertexAttribPointer(attrib_texCoords, 2, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, Float.BYTES * 2);
        int attrib_color = glGetAttribLocation(prog, "color");
        glEnableVertexAttribArray(attrib_color);
        glVertexAttribPointer(attrib_color, 3, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, Float.BYTES * 4);

        int uni_tex = glGetUniformLocation(prog, "tex");
        glUniform1i(uni_tex, 0);
        int uni_model = glGetUniformLocation(prog, "model");
        Matrix4f mat_model = new Matrix4f();
        // now using glUniformMatrix4fv instead of glUniform4fv
        glUniformMatrix4fv(uni_model, false, mat_model.getBuffer());
        int uni_view = glGetUniformLocation(prog, "view");
        Matrix4f mat_view = new Matrix4f();
        glUniformMatrix4fv(uni_view, false, mat_view.getBuffer());
        int uni_projection = glGetUniformLocation(prog, "projection");
        // updated the projection matrix
        Matrix4f mat_projection = Matrix4f.orthographic(0f, 1f, 0f, 1f, 0f, 1f);
        glUniformMatrix4fv(uni_projection, false, mat_projection.getBuffer());

        stbi_set_flip_vertically_on_load(GL_TRUE);
        IntBuffer
                tex_width = BufferUtils.createIntBuffer(1),
                tex_height = BufferUtils.createIntBuffer(1),
                tex_comp = BufferUtils.createIntBuffer(1);
        ByteBuffer tex_data = stbi_load(TEXTURE_FILE_PATH, tex_width, tex_height, tex_comp, 4);
        if (tex_data == null) {
            throw new RuntimeException("Could not load texture: " + TEXTURE_FILE_PATH);
        }

        tex = glGenTextures();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width.get(), tex_height.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
    }

    static void update() {
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    static void draw() {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glBindVertexArray(vao);
        glUseProgram(prog);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);

        glDrawArrays(GL_TRIANGLES, 0, VERTEX_COUNT);

        glUseProgram(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glfwSwapBuffers(window);
    }

    static void dispose() {
        glfwTerminate();
    }

    static boolean shouldClose() {
        return glfwWindowShouldClose(window) == GLFW_TRUE;
    }

    public static void main(String[] args) {
        try {
            initGLFW();
            initRenderObjects();
            while (!shouldClose()) {
                update();
                draw();
            }
        } finally {
            dispose();
        }
    }

}


Everything ALMOST works. This happens:



and it's supposed to look like this:



(don't ask why it's named truss1.png)

It seems as if only the bottom of the texture is rendered and stretched to the top of the window. Any advice?

whizvox

NEVER MIND IT WORKS!

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.stb.STBImage.*;
import static org.lwjgl.system.MemoryUtil.NULL;

public class ModernRendering {

    private static final String SHADER_VERTEX =
            "#version 150 core\n" +
            "in vec2 position;\n" +
            "in vec2 texCoord;\n" +
            "in vec3 color;\n" +
            "out vec2 f_texCoord;\n" +
            "out vec3 f_color;\n" +
            "uniform mat4 model;\n" +
            "uniform mat4 view;\n" +
            "uniform mat4 projection;\n" +
            "void main() {\n" +
            "    f_texCoord = texCoord;\n" +
            "    f_color = color;\n" +
            "    mat4 mvp = projection * view * model;\n" +
            "    gl_Position = mvp * vec4(position, 0.0, 1.0);\n" +
            "}";
    private static final String SHADER_FRAGMENT =
            "#version 150 core\n" +
            "in vec2 f_texCoord;\n" +
            "in vec3 f_color;\n" +
            "out vec4 fragColor;\n" +
            "uniform sampler2D tex;\n" +
            "void main() {\n" +
            "    vec4 textureColor = texture(tex, f_texCoord);\n" +
            "    fragColor = textureColor * vec4(f_color, 1.0);\n" +
            "}";

    private static final String TEXTURE_FILE_PATH = "Z:\\My Pictures\\truss1.png";

    private static final int WIDTH = 800, HEIGHT = 600, VERTEX_SIZE = 7, VERTEX_COUNT = 6;

    static long window;
    static GLFWErrorCallback errorCallback;
    static GLFWKeyCallback keyCallback;

    static int vao, vbo, shader_v, shader_f, prog, tex;
    static FloatBuffer vertices;

    static void initGLFW() {
        glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));
        if (glfwInit() != GLFW_TRUE) {
            throw new RuntimeException("Could not initialize GLFW context");
        }

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello world!", NULL, NULL);
        if (window == NULL) {
            glfwTerminate();
            throw new RuntimeException("Could not initialize GLFW window");
        }

        GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        glfwSetWindowPos(window, (vidMode.width() - WIDTH) / 2, (vidMode.height() - HEIGHT) / 2);

        glfwMakeContextCurrent(window);
        GL.createCapabilities();
        glfwSwapInterval(1);

        glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(long window, int key, int scancode, int action, int mods) {
                if (key == GLFW_KEY_ESCAPE) {
                    glfwSetWindowShouldClose(window, GLFW_TRUE);
                }
            }
        });
    }

    static void initRenderObjects() {
        vertices = BufferUtils.createFloatBuffer(VERTEX_SIZE * VERTEX_COUNT);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        vao = glGenVertexArrays();
        glBindVertexArray(vao);
        vbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertices, GL_DYNAMIC_DRAW);

        shader_v = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(shader_v, SHADER_VERTEX);
        glCompileShader(shader_v);
        if (glGetShaderi(shader_v, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create vertex shader");
        }

        shader_f = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(shader_f, SHADER_FRAGMENT);
        glCompileShader(shader_f);
        if (glGetShaderi(shader_f, GL_COMPILE_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not create fragment shader");
        }

        prog = glCreateProgram();
        glAttachShader(prog, shader_v);
        glAttachShader(prog, shader_f);
        glBindFragDataLocation(prog, 0, "fragColor");
        glLinkProgram(prog);
        if (glGetProgrami(prog, GL_LINK_STATUS) != GL_TRUE) {
            throw new RuntimeException("Could not link shaders in program");
        }
        glUseProgram(prog);

        int attrib_position = glGetAttribLocation(prog, "position");
        glEnableVertexAttribArray(attrib_position);
        glVertexAttribPointer(attrib_position, 2, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, 0);
        int attrib_texCoords = glGetAttribLocation(prog, "texCoord");
        glEnableVertexAttribArray(attrib_texCoords);
        glVertexAttribPointer(attrib_texCoords, 2, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, Float.BYTES * 2);
        int attrib_color = glGetAttribLocation(prog, "color");
        glEnableVertexAttribArray(attrib_color);
        glVertexAttribPointer(attrib_color, 3, GL_FLOAT, false, Float.BYTES * VERTEX_SIZE, Float.BYTES * 4);

        int uni_tex = glGetUniformLocation(prog, "tex");
        glUniform1i(uni_tex, 0);
        int uni_model = glGetUniformLocation(prog, "model");
        Matrix4f mat_model = new Matrix4f();
        glUniformMatrix4fv(uni_model, false, mat_model.getBuffer());
        int uni_view = glGetUniformLocation(prog, "view");
        Matrix4f mat_view = new Matrix4f();
        glUniformMatrix4fv(uni_view, false, mat_view.getBuffer());
        int uni_projection = glGetUniformLocation(prog, "projection");
        Matrix4f mat_projection = Matrix4f.orthographic(0f, 1f, 0f, 1f, 0f, 1f);
        glUniformMatrix4fv(uni_projection, false, mat_projection.getBuffer());

        stbi_set_flip_vertically_on_load(GL_TRUE);
        IntBuffer
                tex_width = BufferUtils.createIntBuffer(1),
                tex_height = BufferUtils.createIntBuffer(1),
                tex_comp = BufferUtils.createIntBuffer(1);
        ByteBuffer tex_data = stbi_load(TEXTURE_FILE_PATH, tex_width, tex_height, tex_comp, 4);
        if (tex_data == null) {
            throw new RuntimeException("Could not load texture: " + TEXTURE_FILE_PATH);
        }

        tex = glGenTextures();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width.get(), tex_height.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
    }

    static void update() {
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    static void draw() {
        vertices.flip();
        glBindVertexArray(vao);
        glUseProgram(prog);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferSubData(GL_ARRAY_BUFFER, 0, vertices);

        glDrawArrays(GL_TRIANGLES, 0, VERTEX_COUNT);

        glUseProgram(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glfwSwapBuffers(window);
    }

    static void dispose() {
        glfwTerminate();
    }

    static boolean shouldClose() {
        return glfwWindowShouldClose(window) == GLFW_TRUE;
    }

    static void renderTexture(int texId, int activeTexture, float x1, float y1, float x2, float y2, float s1, float t1, float s2, float t2, float r, float g, float b) {
        glActiveTexture(GL_TEXTURE0 | activeTexture);
        glBindTexture(GL_TEXTURE_2D, texId);
        vertices.put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x1).put(y2).put(s1).put(t2).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x1).put(y1).put(s1).put(t1).put(r).put(g).put(b)
                .put(x2).put(y2).put(s2).put(t2).put(r).put(g).put(b)
                .put(x2).put(y1).put(s2).put(t1).put(r).put(g).put(b);
    }

    public static void main(String[] args) {
        float x = 0f, y = 0f, width = 1f, height = 1f;
        try {
            initGLFW();
            initRenderObjects();
            while (!shouldClose()) {
                update();
                renderTexture(tex, 0, x, y, x + width, y + height, 0f, 0f, 1f, 1f, 1f, 1f, 1f);
                draw();
            }
        } finally {
            dispose();
        }
    }

}