com.flowpowered.caustic.lwjgl.gl20.GL20Program.java Source code

Java tutorial

Introduction

Here is the source code for com.flowpowered.caustic.lwjgl.gl20.GL20Program.java

Source

/*
 * This file is part of Caustic LWJGL, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2013 Flow Powered <https://flowpowered.com/>
 *
 * 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.flowpowered.caustic.lwjgl.gl20;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import gnu.trove.impl.Constants;
import gnu.trove.iterator.TObjectIntIterator;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

import com.flowpowered.math.matrix.Matrix2f;
import com.flowpowered.math.matrix.Matrix3f;
import com.flowpowered.math.matrix.Matrix4f;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector4f;

import com.flowpowered.caustic.api.gl.Program;
import com.flowpowered.caustic.api.gl.Shader;
import com.flowpowered.caustic.api.util.CausticUtil;
import com.flowpowered.caustic.lwjgl.LWJGLUtil;

/**
 * An OpenGL 2.0 implementation of {@link Program}.
 *
 * @see Program
 */
public class GL20Program extends Program {
    // Represents an unset value for a uniform
    private static final Object UNSET = new Object() {
        @Override
        public String toString() {
            return "UNSET";
        }
    };
    // Set of all shaders in this program
    private final Set<Shader> shaders = new HashSet<>();
    // Map of the attribute names to their vao index (optional for GL30 as they can be defined in the shader instead)
    private final TObjectIntMap<String> attributeLayouts = new TObjectIntHashMap<>();
    // Map of the texture units to their names
    private final TIntObjectMap<String> textureLayouts = new TIntObjectHashMap<>();
    // Map of the uniform names to their locations
    private final TObjectIntMap<String> uniforms = new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY,
            Constants.DEFAULT_LOAD_FACTOR, -1);
    // Map of the uniform names to their values
    private final Map<String, Object> uniformValues = new HashMap<>();

    @Override
    public void create() {
        checkNotCreated();
        // Create program
        id = GL20.glCreateProgram();
        // Update the state
        super.create();
    }

    @Override
    public void destroy() {
        checkCreated();
        // Delete the program
        GL20.glDeleteProgram(id);
        // Check for errors
        LWJGLUtil.checkForGLError();
        // Clear the data
        shaders.clear();
        attributeLayouts.clear();
        textureLayouts.clear();
        uniforms.clear();
        uniformValues.clear();
        // Update the state
        super.destroy();
    }

    @Override
    public void attachShader(Shader shader) {
        checkCreated();
        // Attach the shader
        GL20.glAttachShader(id, shader.getID());
        // Check for errors
        LWJGLUtil.checkForGLError();
        // Add the shader to the set
        shaders.add(shader);
        // Add all attribute and texture layouts
        attributeLayouts.putAll(shader.getAttributeLayouts());
        textureLayouts.putAll(shader.getTextureLayouts());
    }

    @Override
    public void detachShader(Shader shader) {
        checkCreated();
        // Attach the shader
        GL20.glDetachShader(id, shader.getID());
        // Check for errors
        LWJGLUtil.checkForGLError();
        // Remove the shader from the set
        shaders.remove(shader);
        // Remove all attribute and texture layouts
        for (String attribute : shader.getAttributeLayouts().keySet()) {
            attributeLayouts.remove(attribute);
        }
        for (int unit : shader.getTextureLayouts().keys()) {
            textureLayouts.remove(unit);
        }
    }

    @Override
    public void link() {
        checkCreated();
        // Add the attribute layouts to the program state
        final TObjectIntIterator<String> iterator = attributeLayouts.iterator();
        while (iterator.hasNext()) {
            iterator.advance();
            // Bind the index to the name
            GL20.glBindAttribLocation(id, iterator.value(), iterator.key());
        }
        // Link program
        GL20.glLinkProgram(id);
        // Check program link status
        if (GL20.glGetProgrami(id, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
            throw new IllegalStateException("Program could not be linked\n" + GL20.glGetProgramInfoLog(id, 1000));
        }
        if (CausticUtil.isDebugEnabled()) {
            // Validate program
            GL20.glValidateProgram(id);
            // Check program validation status
            if (GL20.glGetProgrami(id, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) {
                final Logger logger = CausticUtil.getCausticLogger();
                logger.log(Level.WARNING,
                        "Program validation failed. This doesn''t mean it won''t work, so you maybe able to ignore it\n{0}",
                        GL20.glGetProgramInfoLog(id, 1000));
            }
        }
        // Load uniforms
        uniforms.clear();
        final int uniformCount = GL20.glGetProgrami(id, GL20.GL_ACTIVE_UNIFORMS);
        final int maxLength = GL20.glGetProgrami(id, GL20.GL_ACTIVE_UNIFORM_MAX_LENGTH);
        final IntBuffer lengthBuffer = CausticUtil.createIntBuffer(1);
        final IntBuffer ignored1 = CausticUtil.createIntBuffer(1);
        final IntBuffer ignored2 = CausticUtil.createIntBuffer(1);
        final ByteBuffer nameBuffer = CausticUtil.createByteBuffer(maxLength);
        final byte[] nameBytes = new byte[maxLength];
        for (int i = 0; i < uniformCount; i++) {
            lengthBuffer.clear();
            ignored1.clear();
            ignored2.clear();
            nameBuffer.clear();
            GL20.glGetActiveUniform(id, i, lengthBuffer, ignored1, ignored2, nameBuffer);
            final int length = lengthBuffer.get();
            nameBuffer.get(nameBytes, 0, length);
            // Simplify array names
            final String name = new String(nameBytes, 0, length).replaceFirst("\\[\\d+\\]", "");
            uniforms.put(name, GL20.glGetUniformLocation(id, name));
            uniformValues.put(name, UNSET);
        }
        // Check for errors
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void use() {
        checkCreated();
        // Bind the program
        GL20.glUseProgram(id);
        // Check for errors
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void bindSampler(int unit) {
        if (!textureLayouts.containsKey(unit)) {
            throw new IllegalArgumentException("No texture layout has been set for the unit: " + unit);
        }
        setUniform(textureLayouts.get(unit), unit);
    }

    // TODO: Support int and boolean vectors
    @Override
    public void setUniform(String name, boolean b) {
        checkCreated();
        if (!isDirty(name, b)) {
            return;
        }
        GL20.glUniform1i(uniforms.get(name), b ? 1 : 0);
        uniformValues.put(name, b);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, int i) {
        checkCreated();
        if (!isDirty(name, i)) {
            return;
        }
        GL20.glUniform1i(uniforms.get(name), i);
        uniformValues.put(name, i);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, float f) {
        checkCreated();
        if (!isDirty(name, f)) {
            return;
        }
        GL20.glUniform1f(uniforms.get(name), f);
        uniformValues.put(name, f);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, float[] fs) {
        checkCreated();
        if (!isDirty(name, fs)) {
            return;
        }
        final FloatBuffer floatBuffer = CausticUtil.createFloatBuffer(fs.length);
        for (float f : fs) {
            floatBuffer.put(f);
        }
        floatBuffer.flip();
        GL20.glUniform1(uniforms.get(name), floatBuffer);
        uniformValues.put(name, fs);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Vector2f v) {
        checkCreated();
        if (!isDirty(name, v)) {
            return;
        }
        GL20.glUniform2f(uniforms.get(name), v.getX(), v.getY());
        uniformValues.put(name, v);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Vector2f[] vs) {
        checkCreated();
        if (!isDirty(name, vs)) {
            return;
        }
        final FloatBuffer vectorBuffer = CausticUtil.createFloatBuffer(vs.length * 2);
        for (Vector2f v : vs) {
            vectorBuffer.put(v.getX());
            vectorBuffer.put(v.getY());
        }
        vectorBuffer.flip();
        GL20.glUniform2(uniforms.get(name), vectorBuffer);
        uniformValues.put(name, vs);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Vector3f v) {
        checkCreated();
        if (!isDirty(name, v)) {
            return;
        }
        GL20.glUniform3f(uniforms.get(name), v.getX(), v.getY(), v.getZ());
        uniformValues.put(name, v);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Vector3f[] vs) {
        checkCreated();
        if (!isDirty(name, vs)) {
            return;
        }
        final FloatBuffer vectorBuffer = CausticUtil.createFloatBuffer(vs.length * 3);
        for (Vector3f v : vs) {
            vectorBuffer.put(v.getX());
            vectorBuffer.put(v.getY());
            vectorBuffer.put(v.getZ());
        }
        vectorBuffer.flip();
        GL20.glUniform3(uniforms.get(name), vectorBuffer);
        uniformValues.put(name, vs);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Vector4f v) {
        checkCreated();
        if (!isDirty(name, v)) {
            return;
        }
        GL20.glUniform4f(uniforms.get(name), v.getX(), v.getY(), v.getZ(), v.getW());
        uniformValues.put(name, v);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Matrix2f m) {
        checkCreated();
        if (!isDirty(name, m)) {
            return;
        }
        final FloatBuffer buffer = CausticUtil.createFloatBuffer(4);
        buffer.put(m.toArray(true));
        buffer.flip();
        GL20.glUniformMatrix2(uniforms.get(name), false, buffer);
        uniformValues.put(name, m);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Matrix3f m) {
        checkCreated();
        if (!isDirty(name, m)) {
            return;
        }
        final FloatBuffer buffer = CausticUtil.createFloatBuffer(9);
        buffer.put(m.toArray(true));
        buffer.flip();
        GL20.glUniformMatrix3(uniforms.get(name), false, buffer);
        uniformValues.put(name, m);
        LWJGLUtil.checkForGLError();
    }

    @Override
    public void setUniform(String name, Matrix4f m) {
        checkCreated();
        if (!isDirty(name, m)) {
            return;
        }
        final FloatBuffer buffer = CausticUtil.createFloatBuffer(16);
        buffer.put(m.toArray(true));
        buffer.flip();
        GL20.glUniformMatrix4(uniforms.get(name), false, buffer);
        uniformValues.put(name, m);
        LWJGLUtil.checkForGLError();
    }

    private boolean isDirty(String name, Object newValue) {
        final Object oldValue = uniformValues.get(name);
        return oldValue != null && (oldValue == UNSET || !oldValue.equals(newValue));
    }

    @Override
    public Set<Shader> getShaders() {
        return Collections.unmodifiableSet(shaders);
    }

    @Override
    public Set<String> getUniformNames() {
        return Collections.unmodifiableSet(uniforms.keySet());
    }

    @Override
    public GLVersion getGLVersion() {
        return GLVersion.GL20;
    }
}