/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.rendering.shader; import com.google.common.io.CharStreams; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL20; import org.newdawn.slick.util.ResourceLoader; import org.terasology.logic.manager.Config; import org.terasology.logic.manager.ShaderManager; import org.terasology.rendering.assets.Shader; import org.terasology.teraspout.TeraBlock; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.logging.Level; import java.util.logging.Logger; import static org.lwjgl.opengl.GL11.glBindTexture; /** * Wraps a OpenGL shader program. Provides convenience methods for setting * uniform variables of various types. * * @author Benjamin Glatzel <benjamin.glatzel@me.com> */ public class ShaderProgram { private int _shaderProgram, _fragmentProgram, _vertexProgram; private String _title; private IShaderParameters _parameters; private Logger logger = Logger.getLogger(getClass().getName()); public ShaderProgram(String title) { this(title, null); } public ShaderProgram(String title, IShaderParameters params) { _title = title; _parameters = params; compileShaderProgram(); } private void compileShaderProgram() { compileShader(GL20.GL_FRAGMENT_SHADER); compileShader(GL20.GL_VERTEX_SHADER); _shaderProgram = GL20.glCreateProgram(); GL20.glAttachShader(_shaderProgram, _fragmentProgram); GL20.glAttachShader(_shaderProgram, _vertexProgram); GL20.glLinkProgram(_shaderProgram); GL20.glValidateProgram(_shaderProgram); } public void recompile() { logger.log(Level.INFO, "Recompiling shader {0}.", new String[]{_title}); dispose(); compileShaderProgram(); } public void dispose() { logger.log(Level.INFO, "Disposing shader {0}.", new String[]{_title}); GL20.glDeleteShader(_shaderProgram); _shaderProgram = 0; GL20.glDeleteProgram(_fragmentProgram); _fragmentProgram = 0; GL20.glDeleteProgram(_vertexProgram); _vertexProgram = 0; } private void compileShader(int type) { int shaderId = GL20.glCreateShader(type); StringBuilder shader = Shader.createShaderBuilder(); if (type == GL20.GL_FRAGMENT_SHADER) shader.append(Shader.getIncludedFunctionsFragment()).append("\n"); else shader.append(Shader.getIncludedFunctionsVertex()).append("\n"); String filename = _title; if (type == GL20.GL_FRAGMENT_SHADER) { filename += "_frag.glsl"; } else if (type == GL20.GL_VERTEX_SHADER) { filename += "_vert.glsl"; } logger.log(Level.INFO, "Loading shader {0} ({1}, type = {2})", new String[]{_title, filename, String.valueOf(type)}); // Read in the shader code shader.append(readShader(filename)); if (type == GL20.GL_FRAGMENT_SHADER) { _fragmentProgram = shaderId; } else if (type == GL20.GL_VERTEX_SHADER) { _vertexProgram = shaderId; } GL20.glShaderSource(shaderId, shader.toString()); GL20.glCompileShader(shaderId); printLogInfo(shaderId); } private String readShader(String filename) { String line, code = ""; try { BufferedReader reader = new BufferedReader(new InputStreamReader(ResourceLoader.getResource("org/terasology/data/shaders/" + filename).openStream())); while ((line = reader.readLine()) != null) { code += line + "\n"; } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to read shader."); } return code; } private void printLogInfo(int shaderId) { IntBuffer intBuffer = BufferUtils.createIntBuffer(1); GL20.glGetShader(shaderId, GL20.GL_INFO_LOG_LENGTH, intBuffer); int length = intBuffer.get(); if (length <= 1) { return; } ByteBuffer infoBuffer = BufferUtils.createByteBuffer(length); intBuffer.flip(); GL20.glGetShaderInfoLog(shaderId, intBuffer, infoBuffer); int actualLength = intBuffer.get(); byte[] infoBytes = new byte[actualLength]; infoBuffer.get(infoBytes); logger.log(Level.INFO, "{0}", new String(infoBytes)); } public void enable() { ShaderProgram activeProgram = ShaderManager.getInstance().getActiveShaderProgram(); if (activeProgram != this) { GL13.glActiveTexture(GL13.GL_TEXTURE0); glBindTexture(GL11.GL_TEXTURE_2D, 0); GL20.glUseProgram(_shaderProgram); // Make sure the shader manager knows that this program is currently active ShaderManager.getInstance().setActiveShaderProgram(this); // Set the shader parameters if available if (_parameters != null) { _parameters.applyParameters(this); } } } public void setFloat(String desc, float f) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform1f(id, f); } public void setFloat3(String desc, float f1, float f2, float f3) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform3f(id, f1, f2, f3); } public void setFloat4(String desc, float f1, float f2, float f3, float f4) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform4f(id, f1, f2, f3, f4); } public void setInt(String desc, int i) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform1i(id, i); } public void setFloat2(String desc, FloatBuffer buffer) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform2(id, buffer); } public void setFloat1(String desc, FloatBuffer buffer) { enable(); int id = GL20.glGetUniformLocation(_shaderProgram, desc); GL20.glUniform1(id, buffer); } public IShaderParameters getShaderParameters() { return _parameters; } public int getShaderId() { return _shaderProgram; } }