com.samrj.devil.gl.ShaderProgram.java Source code

Java tutorial

Introduction

Here is the source code for com.samrj.devil.gl.ShaderProgram.java

Source

/*
 * Copyright (c) 2015 Sam Johnson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.samrj.devil.gl;

import com.samrj.devil.io.MemStack;
import com.samrj.devil.math.Mat2;
import com.samrj.devil.math.Mat3;
import com.samrj.devil.math.Mat4;
import com.samrj.devil.math.Vec2;
import com.samrj.devil.math.Vec3;
import com.samrj.devil.math.Vec4;
import com.samrj.devil.util.IdentitySet;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;

/**
 * OpenGL shader program wrapper.
 * 
 * @author Samuel Johnson (SmashMaster)
 */
public final class ShaderProgram extends DGLObj {
    public static enum State {
        NEW, LINKED, COMPLETE, DELETED;
    }

    /**
     * The OpenGL id of this shader program.
     */
    final int id;

    private final Set<Shader> shaders;
    private List<Attribute> attributes;
    private Map<String, Attribute> attMap;
    private State state;

    ShaderProgram() {
        DGL.checkState();
        if (!DGL.getCapabilities().OpenGL20)
            throw new UnsupportedOperationException("Shader programs unsupported in OpenGL < 2.0");
        id = GL20.glCreateProgram();
        shaders = new IdentitySet<>();
        state = State.NEW;
    }

    /**
     * Attaches the given shader to this program.
     * 
     * @param shader The shader to attach to this program.
     * @return This shader program.
     */
    public ShaderProgram attach(Shader shader) {
        if (state != State.NEW)
            throw new IllegalStateException("Shader program must be new to attach shaders.");
        if (shader.state() != Shader.State.COMPILED)
            throw new IllegalStateException("Cannot attach shader that is not compiled.");

        GL20.glAttachShader(id, shader.id);
        shaders.add(shader);
        return this;
    }

    /**
     * Detaches the given shader from this program. Can be safely done at any
     * point after linking.
     * 
     * @param shader The shader to detach.
     * @return This shader program.
     */
    public ShaderProgram detach(Shader shader) {
        GL20.glDetachShader(id, shader.id);
        shaders.remove(shader);
        return this;
    }

    /**
     * Attaches each of the given shaders to this program.
     * 
     * @param shaders An array of shaders to attach to this program.
     * @return This shader program.
     */
    public ShaderProgram attach(Shader... shaders) {
        for (Shader shader : shaders)
            attach(shader);
        return this;
    }

    /**
     * Detaches all shaders from this program. Should be called after linking.
     * 
     * @return This shader program.
     */
    public ShaderProgram detachAll() {
        for (Shader shader : shaders)
            GL20.glDetachShader(id, shader.id);
        shaders.clear();
        return this;
    }

    private void checkStatus(int type) {
        if (GL20.glGetProgrami(id, type) != GL11.GL_TRUE) {
            int logLength = GL20.glGetProgrami(id, GL20.GL_INFO_LOG_LENGTH);
            String log = GL20.glGetProgramInfoLog(id, logLength);
            throw new ShaderException(log);
        }
    }

    /**
     * Links this shader program, creating executables that may run on the GPU
     * and compiling a list of input attributes.
     * 
     * @return This shader program.
     */
    public ShaderProgram link() {
        if (state != State.NEW)
            throw new IllegalStateException("Shader program must be new to link.");

        GL20.glLinkProgram(id);
        checkStatus(GL20.GL_LINK_STATUS);

        int numAttributes = GL20.glGetProgrami(id, GL20.GL_ACTIVE_ATTRIBUTES);
        ArrayList<Attribute> attList = new ArrayList<>(numAttributes);
        attMap = new HashMap<>(numAttributes);

        int attBytes = 4 + 4 + 4 + 32;
        long address = MemStack.push(attBytes);
        ByteBuffer buffer = MemoryUtil.memByteBuffer(address, attBytes);
        for (int index = 0; index < numAttributes; index++) {
            long nameAddress = address + 12;
            GL20.nglGetActiveAttrib(id, index, 31, address, address + 4, address + 8, nameAddress);

            buffer.rewind();
            buffer.getInt();
            int size = buffer.getInt();
            int type = buffer.getInt();
            String name = MemoryUtil.memASCII(nameAddress);
            int location = GL20.nglGetAttribLocation(id, nameAddress);

            Attribute att = new Attribute(name, type, size, location);
            attList.add(att);
            attMap.put(name, att);
        }
        MemStack.pop();
        attributes = Collections.unmodifiableList(attList);

        state = State.LINKED;
        return this;
    }

    /**
     * Validates this program, checking to see whether the executables contained
     * in this program can be executed in the current OpenGL state.
     * 
     * @return This shader program.
     */
    public ShaderProgram validate() {
        if (state != State.LINKED)
            throw new IllegalStateException("Shader program must be linked to validate.");

        GL20.glValidateProgram(id);
        checkStatus(GL20.GL_VALIDATE_STATUS);

        state = State.COMPLETE;
        return this;
    }

    /**
     * Use this shader for any subsequent draw calls.
     */
    void use() {
        if (state == State.DELETED)
            throw new IllegalStateException("Shader must not be deleted to use.");
        GL20.glUseProgram(id);
    }

    /**
     * Returns the location of the attribute with the given name, or -1 if none
     * with the given name exists.
     * 
     * @param name The name of the attribute to find.
     * @return The location of the attribute.
     */
    public int getAttributeLocation(String name) {
        return GL20.glGetAttribLocation(id, name);
    }

    // <editor-fold defaultstate="collapsed" desc="Uniform methods">
    /**
     * Returns the location of the uniform with the given name, or -1 if none
     * with the given name exists.
     * 
     * @param name The name of the uniform to find.
     * @return The location of a uniform.
     */
    public int getUniformLocation(String name) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        return GL20.glGetUniformLocation(id, name);
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @param x The value to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform1i(String name, int x) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;
        GL20.glUniform1i(loc, x);
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform1iv(String name, int... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapi(array);
        GL20.nglUniform1iv(loc, array.length, address);
        MemStack.pop();
        return true;
    }

    public boolean uniform1b(String name, boolean b) {
        return uniform1i(name, b ? 1 : 0);
    }

    public boolean uniform1bv(String name, boolean... array) {
        int[] ints = new int[array.length];
        for (int i = 0; i < array.length; i++)
            ints[i] = array[i] ? 1 : 0;
        return uniform1iv(name, ints);
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @param x The value to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform1f(String name, float x) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;
        GL20.glUniform1f(loc, x);
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform1fv(String name, float... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapf(array);
        GL20.nglUniform1fv(loc, array.length, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform2f(String name, float x, float y) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;
        GL20.glUniform2f(loc, x, y);
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform2fv(String name, float... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapf(array);
        GL20.nglUniform2fv(loc, array.length / 2, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec2(String name, Vec2 v) {
        return uniform2f(name, v.x, v.y);
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec2v(String name, Vec2... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniform2fv(loc, array.length, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform3f(String name, float x, float y, float z) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;
        GL20.glUniform3f(loc, x, y, z);
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform3fv(String name, float... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapf(array);
        GL20.nglUniform3fv(loc, array.length / 3, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec3(String name, Vec3 v) {
        return uniform3f(name, v.x, v.y, v.z);
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec3v(String name, Vec3... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniform3fv(loc, array.length, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform4f(String name, float x, float y, float z, float w) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;
        GL20.glUniform4f(loc, x, y, z, w);
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniform4fv(String name, float... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapf(array);
        GL20.nglUniform4fv(loc, array.length / 4, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec4(String name, Vec4 v) {
        return uniform4f(name, v.x, v.y, v.z, v.w);
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformVec4v(String name, Vec4... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniform4fv(loc, array.length, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat2(String name, Mat2 matrix) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrap(matrix);
        GL20.nglUniformMatrix2fv(loc, 1, false, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat2v(String name, Mat2... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniformMatrix2fv(loc, array.length, false, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat3(String name, Mat3 matrix) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrap(matrix);
        GL20.nglUniformMatrix3fv(loc, 1, false, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat3v(String name, Mat3... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniformMatrix3fv(loc, array.length, false, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the value of a uniform variable for this program. Program must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform to specify.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat4(String name, Mat4 matrix) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrap(matrix);
        GL20.nglUniformMatrix4fv(loc, 1, false, address);
        MemStack.pop();
        return true;
    }

    /**
     * Specifies the values of a uniform variable array for this program. Must
     * be in use. Returns true if and only if the uniform exists and is active.
     * 
     * @param name The name of the uniform array to specify.
     * @param array An array of values to set the uniform to.
     * @return Whether or not the uniform exists and is active.
     */
    public boolean uniformMat4v(String name, Mat4... array) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        int loc = GL20.glGetUniformLocation(id, name);
        if (loc < 0)
            return false;

        long address = MemStack.wrapv(array);
        GL20.nglUniformMatrix4fv(loc, array.length, false, address);
        MemStack.pop();
        return true;
    }
    // </editor-fold>

    /**
     * Binds the given color output name to the given color attachment.
     * 
     * @param name The output variable name to bind.
     * @param colorNumber The color attachment layer to bind to.
     */
    public void bindFragDataLocation(String name, int colorNumber) {
        if (DGL.currentProgram() != this)
            throw new IllegalStateException("Program must be in use.");
        GL30.glBindFragDataLocation(id, colorNumber, name);
    }

    /**
     * 
     * @return A set of each shader attached to this program.
     */
    public Set<Shader> getShaders() {
        return Collections.unmodifiableSet(shaders);
    }

    /**
     * @return A list of every vertex attribute associated with this program.
     */
    public List<Attribute> getAttributes() {
        if (state != State.LINKED && state != State.COMPLETE)
            throw new IllegalStateException("Shader must be linked or complete to have attributes.");
        return attributes;
    }

    /**
     * Returns the attribute with the given name, or null if no such attribute
     * is active.
     * 
     * @param name The name of the attribute to find.
     * @return The attribute with the given name.
     */
    public Attribute getAttribute(String name) {
        return attMap.get(name);
    }

    /**
     * @return The state of this shader program.
     */
    public State state() {
        return state;
    }

    @Override
    void delete() {
        if (state == State.DELETED)
            return;

        if (DGL.currentProgram() == this)
            DGL.useProgram(null);
        attributes = null;
        attMap = null;
        GL20.glDeleteProgram(id);

        state = State.DELETED;
    }

    /**
     * Vertex attribute class for this specific shader program.
     */
    public class Attribute {
        /**
         * The name of this attribute, as it appears in the vertex shader.
         */
        public final String name;

        /**
         * The type of this attribute.
         */
        public final AttributeType type;

        /**
         * The number of elements in this attribute array, if this attribute is
         * an array type.
         */
        public final int size;

        /**
         * The location of this attribute.
         */
        public final int location;

        private Attribute(String name, int type, int size, int location) {
            this.name = name;
            this.type = AttributeType.get(type);
            if (this.type == null)
                throw new IllegalArgumentException();
            this.size = size;
            this.location = location;
        }

        @Override
        public String toString() {
            return "Att \"" + name + "\", type: " + type + ", size: " + size + ", loc: " + location;
        }
    }
}