UtilsLWJGL.java Source code

Java tutorial

Introduction

Here is the source code for UtilsLWJGL.java

Source

/*
 * UtilsLWJGL.java from lwjgl3-demo-master
 * Copyright LWJGL. All rights reserved.
 * License terms: http://lwjgl.org/license.php
 *   Essentially a copy of DemoUtils.java with the added public static methods:
 *      long openWindow( int winW, int winH )
 *      long openWindow( int winW, int winH, openglMajor, int openglMinor )
 *      int makeShaderProgram( String vertexShader, String fragmentShader )
 *      int makeShaderProgram( String vShader, String fShader, String version )
 *      
 * Copyright LWJGL. All rights reserved.
 * License terms: http://lwjgl.org/license.php
 *
 * Edited by rdb Sept 2015:
 *  1. unpackaged it; simple demos; keep the structure simple. 
 *  2. minor formatting changes to conform more closely to my style.
 *  3. changed name from DemoUtils to UtilsLWJGL
 *  4. Added openWindow and makeShaderProgram
 *  5. Added checkBuffer method that reads a buffer back from GPU
 *      and compares to what was intended to be downloaded.
 * Nov 2016 rdb edits caused by changes in LWJGL3.1
 *  6. Changes in org.lwjgl.system.MemoryUtil
 *  7. Changes in glfw.GLFWvidmode
 *  8. glfwInit() returns boolean rather than GL11 constants
 *  9. GLContext seems to have disappeared.
 * 12/20/16 rdb
 *  Modified  openWindow( String title, int winW, int winH )
 *      so it has no default version, and specifies no version in hints;
 *      This makes lower end programs simpler: no need to specify version.
 *      Anything that needs more thna 4.1 should then query the version.
 *  Modified openWindow with version to try lower versions if specified
 *      version fails to get a window. Prints err at each failed attempt.
 * 12/28/16
 *  Modified BufferUtils to use MemoryUtil instead of BufferUtils (except when
 *      returning a Buffer object. No discernible peformance difference even  
 *      did 50000 objects with fpm in the 4 range.
 * 01/18/17 rdb Modified UtilsLWJGL.openWindow to work better in Eclipse
 *              where getenv is dangerous to use; instead use System.getProperty
 * 01/19/17 rdb Modified UtilsLWJGL.openWindow to better handle Linux.
 * 01/26/17 rdb Added prefix-based makeShaderProgram method
 */

//rdb package org.lwjgl.demo.opengl.util;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;

import static org.lwjgl.glfw.GLFW.*;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*; //Eclipse says not used, but it is!
import static org.lwjgl.opengl.GL20.*; //Eclipse says not used, but it is!
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.system.MemoryUtil.*;

import org.lwjgl.system.MemoryUtil;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL11;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UtilsLWJGL {
    // ------------------------ openWindow ----------------------------
    /**
     * Do whatever is necessary to open a GLFW window. This code is heavily
     * based on code from lwjgl3-demo-master.
     * 
     * @author rdb
     * @param winW
     *            int width of window to create
     * @param winH
     *            int height of window
     * @return int window Id for created window
     * 
     *         Returns the window id.
     */
    public static long openWindow(String title, int winW, int winH) {
        // if system environment specifies default Open GL version, use it
        // GLVERSION is an environment variable defined by rdb to help
        // provide better portability of code.
        // ***System.getenv doesn't work for user env variables in Eclipse
        //
        String oglVersion = System.getenv("GLVERSION");
        if (oglVersion != null)
            return openWindow(title, winW, winH, oglVersion);

        String osname = System.getProperty("os.name");

        // Now check if on linux and MESA_GL_VERSION is set; if so use it
        // MESA_GL_VERSION is defined by the Mesa GL environment
        // *** System.getenv doesn't work for user env variables in Eclipse
        oglVersion = System.getenv("MESA_GL_VERSION");

        if (osname.equalsIgnoreCase("linux") && oglVersion != null)
            return openWindow(title, winW, winH, oglVersion);

        // Because of idiosyncracies of Mac OS X OpenGL implementation, need
        // to make a hack here; must specify a desired version.
        if (osname.startsWith("Mac") || osname.equalsIgnoreCase("linux")) {
            return openWindow(title, winW, winH, 4, 1);
        }
        // This code is modeled after Anton's demos
        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if (!glfwInit())
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwDefaultWindowHints(); // optional, current window hints are default
        glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // window hidden after create
        glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // window is resizable
        glfwWindowHint(GLFW_DEPTH_BITS, 32); // is this needed for depth??
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        // Create the window
        long windowId = glfwCreateWindow(winW, winH, title, NULL, NULL);

        if (windowId == NULL)
            throw new RuntimeException("Failed to create the GLFW window");
        return windowId;
    }

    /**
     * Do whatever is necessary to open a GLFW window. This code is heavily
     * based on code from lwjgl3-demo-master.
     * 
     * @author rdb
     * @par:am winW int width of window to create
     * @param winH
     *            int height of window
     * @param String
     *            openglVersion, in form major.minor[FC]
     * @return int window Id for created window
     * 
     *         Returns the window id.
     */
    public static long openWindow(String title, int winW, int winH, String oglVersion) {
        // oglVersion must be of the form "4.1" = major.minor
        // Note: linux/MESA allows option "FC" to indicate forward comp.
        // This then becomes part of the regex.
        Pattern vPattern = Pattern.compile("(\\d+)\\.(\\d+)\\.*");
        Matcher m = vPattern.matcher(oglVersion);

        if (!m.find()) {
            System.err.println("UtilsLWJGL.openWindow parameter error: "
                    + "GL version string is not of the form major.minor: " + oglVersion + "\n     Trying with 4.3");
            return openWindow(title, winW, winH, 4, 3);
        }

        try {
            int major = new Integer(m.group(1));
            int minor = new Integer(m.group(2));
            return openWindow(title, winW, winH, major, minor);
        } catch (NumberFormatException nfe) {
            System.err.println(
                    "Unexpected error in openWindow processing!\n" + "with version parameter: " + oglVersion);
        }
        return NULL;
    }

    /**
     * Do whatever is necessary to open a GLFW window. This code is heavily
     * based on code from lwjgl3-demo-master.
     * 
     * @author rdb
     * @par:am winW int width of window to create
     * @param winH
     *            int height of window
     * @param int
     *            openglMajor major version # for opengl
     * @param int
     *            openglMinor minor version # for opengl
     * @return int window Id for created window
     * 
     *         Returns the window id.
     */
    public static long openWindow(String title, int winW, int winH, int openglMajor, int openglMinor) {
        long windowId = NULL; // NULL is defined in glfw.

        // Initialize GLFW. Most GLFW functions won't before doing this.
        if (!glfwInit()) // rdb 8
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwWindowHint(GLFW_VISIBLE, GL_TRUE); // window visible after create
        glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // window is resizable
        glfwWindowHint(GLFW_DEPTH_BITS, 32); // is this needed for depth
        // ---- next two are critical ---------
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

        // 09/10/15: Mac OS/X, Windows, and Linux all support 3.3!
        // 09/22/15: Well, kind of ???

        while (openglMajor > 1 && windowId == NULL) {
            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, openglMajor);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, openglMinor);

            // Create the window
            windowId = glfwCreateWindow(winW, winH, title, NULL, NULL);

            if (windowId == NULL) {
                System.err.println("GLFW window creation failed for Open GL " + openglMajor + "." + openglMinor);
                if (openglMinor > 0)
                    openglMinor--;
                else {
                    openglMinor = 5; // a hack; this means we'll miss 3.6+ etc
                    openglMajor--;
                }
            }
        }
        if (windowId == NULL) {
            throw new RuntimeException("Failed to create the GLFW window: ");
        }

        // Get the resolution of the primary monitor
        GLFWVidMode vmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        int centerX = (vmode.width() - winW) / 2;
        int centerY = (vmode.height() - winH) / 2;

        // Center our window
        glfwSetWindowPos(windowId, centerX, centerY);

        // Make the OpenGL context current
        glfwMakeContextCurrent(windowId);

        // rdb: true => forward compatible; do not use forward compatibility
        GL.createCapabilities();

        // Enable v-sync
        glfwSwapInterval(0);

        String glv = glGetString(GL_VERSION);
        System.err.println("*** Using GL version: " + glv);

        glfwShowWindow(windowId);
        return windowId;
    }

    // ------------------- glError( String ) ----------------------------------
    /**
     * Utility function to check for gl error condition and print a message to
     * std err.
     * 
     * @author rdb
     */
    public static int glError(String id) {
        int err = glGetError();
        if (err == GL_NO_ERROR)
            return GL_NO_ERROR;
        System.err.println("*** GL error " + id + " :" + glErrorMessage(err));
        return err;
    }

    // ------------------- glErrorMessage ----------------------------------
    /**
     * Map gl error code to a simple String message.
     * 
     * @author rdb
     * 
     */
    public static String glErrorMessage(int errCode) {
        String errName;
        switch (errCode) {
        case GL11.GL_INVALID_ENUM: // 0x0500;
            errName = "GL_INVALID_ENUM";
            break;
        case GL11.GL_INVALID_VALUE: // 0x0501;
            errName = "GL_INVALID_VALUE";
            break;
        case GL11.GL_INVALID_OPERATION: // 0x0502;
            errName = "GL_INVALID_OPERATION";
            break;
        case GL11.GL_STACK_OVERFLOW: // 0x0503;
            errName = "GL_STACK_OVERFLOW";
            break;
        case GL11.GL_STACK_UNDERFLOW: // 0x0504;
            errName = "GL_STACK_UNDERFLOW";
            break;
        case GL11.GL_OUT_OF_MEMORY: // 0x0505;
            errName = "GL_OUT_OF_MEMORY";
            break;
        case GL_INVALID_FRAMEBUFFER_OPERATION: // 0x0506;
            errName = "GL_INVALID_FRAMEBUFFER_OPERATION";
            break;
        case 0x0507:
            errName = "GL_CONTEXT_LOST";
            break;
        default:
            errName = "GL unknown error";
            break;
        }
        return String.format("GL Error: 0x0%3x (%5d) %s", errCode, errCode, errName);
    }

    // --------------- makeShaderProgram( String ) ---------------
    /**
     * Create the shader programs for this application. This is a convenience
     * interface for the common case, where the vertex shader and the fragment
     * shader have the same name -- except for the .vsh or .fsh extension. Pass
     * in only the prefix.
     * 
     * @author rdb
     * @param prefix
     *            String filename prefix for both shaders
     * @return long shader program id
     *
     * @throws IOException
     */
    public static int makeShaderProgram(String prefix) throws IOException {
        return makeShaderProgram(prefix + ".vsh", prefix + ".fsh");
    }

    // --------------- makeShaderProgram( String, String ) ---------------
    /**
     * Create the shader programs for this application. This code is based on
     * code from lwjgl3-demo-master.
     * 
     * @author rdb
     * @param vertexShader
     *            String filename for the vertex shader code
     * @param fragmentShader
     *            String filename for the fragment shader code
     * @return long shader program id
     *
     * @throws IOException
     */
    public static int makeShaderProgram(String vertexShader, String fragmentShader) throws IOException {
        // last parameter is desired version; default is system default.
        return makeShaderProgram(vertexShader, fragmentShader, null);
    }

    /**
     * Create shader programs for application for a specific opengl version.
     * This code is based on code from lwjgl3-demo-master.
     * 
     * @author rdb
     * @param vertexShader
     *            String filename for the vertex shader code
     * @param fragmentShader
     *            String filename for the fragment shader code
     * @param version
     *            String desired opengl version in form: 3.3
     * @return long shader program id
     *
     * @throws IOException
     */
    public static int makeShaderProgram(String vertexShader, String fragmentShader, String version)
            throws IOException {
        int vshader = createShader(vertexShader, GL_VERTEX_SHADER);
        int fshader = createShader(fragmentShader, GL_FRAGMENT_SHADER);

        int program = glCreateProgram();
        glAttachShader(program, vshader);
        glAttachShader(program, fshader);

        glLinkProgram(program);
        int linked = glGetProgrami(program, GL_LINK_STATUS);
        String programLog = glGetProgramInfoLog(program);
        if (programLog.trim().length() > 0) {
            System.err.println(programLog);
        }
        if (linked == 0) {
            throw new AssertionError("Could not link program");
        }
        return program;
    }

    // ------------- checkBuffer ---------------------------
    /**
     * Read data from currently bound buffer and compare it to the FloatBuffer
     * that is expected there. Return a count of mismatches. If there are
     * mismatches, the first "printErrors" of those will be printed.
     * 
     * If printErrors < 0, all entries will be printed with error count and the
     * absolute value of printErrors is the number of values per line. If a
     * filename is specified, it is used for output; otherwise System.err.
     */
    public static int checkBuffer(FloatBuffer sent, int nFloats, int printInfo) {
        return checkBuffer(sent, nFloats, printInfo, null);
    }

    public static int checkBuffer(FloatBuffer sent, int nFloats, int printInfo, String file) {
        PrintWriter out;
        if (file == null)
            out = new PrintWriter(System.err);
        try {
            out = new PrintWriter(file);
        } catch (IOException ioe) {
            System.err.println("Can't open " + file + " -- Using System.err");
            out = new PrintWriter(System.err);
        }

        FloatBuffer gpuBuf = MemoryUtil.memAllocFloat(nFloats);
        glGetBufferSubData(GL_ARRAY_BUFFER, 0, gpuBuf);

        boolean printData = (printInfo < 0);
        int numsPerLine = -printInfo;
        int numsOnLine = 0;

        int printErrMax = Math.max(0, printInfo);

        int errCount = 0;
        int printCount = 0;
        sent.rewind();
        gpuBuf.rewind();
        for (int j = 0; j < nFloats; j++) {
            float sValue = sent.get();
            float bValue = gpuBuf.get();
            if (printData) {
                out.print(bValue + " ");
                if (++numsOnLine >= numsPerLine) {
                    out.println();
                    numsOnLine = 0;
                }
            }

            if (sValue != bValue) {
                errCount++;
                if (printCount++ < numsPerLine)
                    out.printf("*** checkBuffer mismatch %d= %7.3f should be %7.3f\n", errCount, bValue, sValue);
            }
        }
        sent.flip();

        if (errCount > 0 && printErrMax > 0)
            out.printf("*** checkBuffer:  %d  mismatches in %d values.\n", errCount, nFloats);
        out.close();
        MemoryUtil.memFree(gpuBuf);
        return errCount;
    }

    // ------------- printBuffer ---------------------------
    /**
     * Read data from currently bound buffer and print the contents. The
     * printLimit parameter specifies the maximum number of values to print.
     */
    public static void printBuffer(String title, int nFloats, int printLimit) {
        printBuffer(title, nFloats, printLimit, null);
    }

    public static void printBuffer(String title, int nFloats, int printLimit, String file) {
        PrintWriter out;
        if (printLimit <= 0)
            printLimit = nFloats;
        if (file == null)
            out = new PrintWriter(System.out);
        else {
            try {
                out = new PrintWriter(file);
            } catch (IOException ioe) {
                System.err.println("UtilsLWJGL.printBuffer: Can't open " + file + " -- Using System.err");
                out = new PrintWriter(System.out);
            }
        }
        out.println(title);
        FloatBuffer gpuBuf = MemoryUtil.memAllocFloat(nFloats);
        glGetBufferSubData(GL_ARRAY_BUFFER, 0, gpuBuf);

        int numsPerLine = 9; // this is enough to hold 1 triangle
        int numsOnLine = 0;

        gpuBuf.rewind();
        for (int j = 0; j < printLimit; j++) {
            float val = gpuBuf.get();
            out.print(val + " ");
            if (++numsOnLine >= numsPerLine) {
                out.println();
                numsOnLine = 0;
            }

        }
        gpuBuf.flip();
        if (numsOnLine != 0)
            out.println();
        out.close();
        MemoryUtil.memFree(gpuBuf);
        return;
    }

    // +++++++++++++++ Kai Burjack methods from lwjgl3-demo-master
    // +++++++++++++++
    /**
     * Create a shader object from the given classpath resource.
     *
     * @param resource
     *            the class path
     * @param type
     *            the shader type
     *
     * @return the shader object id
     *
     * @throws IOException
     */
    public static int createShader(String resource, int type) throws IOException {
        return createShader(resource, type, null);
    }

    /**
     * Reads the specified resource and returns the raw data as a ByteBuffer.
     *
     * @param resource
     *            the resource to read
     * @param bufferSize
     *            the initial buffer size
     *
     * @return the resource data
     *
     * @throws IOException
     *             if an IO error occurs
     */
    public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException {
        ByteBuffer buffer;

        File file = new File(resource);
        if (file.isFile()) {
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            buffer = MemoryUtil.memAlloc((int) fc.size() + 1);

            while (fc.read(buffer) != -1)
                ;

            fc.close();
            fis.close();
        } else {
            buffer = MemoryUtil.memAlloc(bufferSize);

            InputStream source = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
            if (source == null)
                throw new FileNotFoundException(resource);

            try {
                ReadableByteChannel rbc = Channels.newChannel(source);
                try {
                    while (true) {
                        int bytes = rbc.read(buffer);
                        if (bytes == -1)
                            break;
                        if (buffer.remaining() == 0)
                            buffer = MemoryUtil.memRealloc(buffer, buffer.capacity() * 2);
                    }
                } finally {
                    rbc.close();
                }
            } finally {
                source.close();
            }
        }

        buffer.flip();
        return buffer;
    }

    /**
     * Create a shader object from the given classpath resource.
     *
     * @param resource
     *            the class path
     * @param type
     *            the shader type
     * @param version
     *            the GLSL version to prepend to the shader source, or null
     *
     * @return the shader object id
     *
     * @throws IOException
     */
    public static int createShader(String resource, int type, String version) throws IOException {
        int shader = glCreateShader(type);

        // rdb note: the "magic" number 8192 is only and "initial" buffer size.
        // The code increases the buffer if it is not large enough
        ByteBuffer source = ioResourceToByteBuffer(resource, 8192);

        if (version == null) {
            PointerBuffer strings = MemoryUtil.memAllocPointer(1);
            IntBuffer lengths = MemoryUtil.memAllocInt(1);

            strings.put(0, source);
            lengths.put(0, source.remaining());

            glShaderSource(shader, strings, lengths);
            MemoryUtil.memFree(lengths);
            MemoryUtil.memFree(strings);
        } else {
            PointerBuffer strings = MemoryUtil.memAllocPointer(1);
            IntBuffer lengths = MemoryUtil.memAllocInt(2);

            String preambleString = "#version " + version + "\n";
            byte[] barray = preambleString.getBytes();
            ByteBuffer preamble = MemoryUtil.memAlloc(barray.length);
            preamble.put(barray);

            strings.put(0, preamble);
            lengths.put(0, preamble.remaining());

            strings.put(1, source);
            lengths.put(1, source.remaining());

            glShaderSource(shader, strings, lengths);
            MemoryUtil.memFree(lengths);
            MemoryUtil.memFree(strings);
        }

        glCompileShader(shader);
        int compiled = glGetShaderi(shader, GL_COMPILE_STATUS);
        String shaderLog = glGetShaderInfoLog(shader);
        if (shaderLog.trim().length() > 0) {
            System.err.println(shaderLog);
        }
        if (compiled == 0) {
            // rdb added "resource" to message
            throw new AssertionError("Could not compile shader: " + resource);
        }
        return shader;
    }
}