/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.render;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.beatmap.HitObject;
import itdelatrisu.opsu.objects.curves.Vec2f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLContext;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.util.Log;
/**
* Hold the temporary render state that needs to be restored again after the new
* style curves are drawn.
*
* @author Bigpet {@literal <dravorek (at) gmail.com>}
*/
public class CurveRenderState {
/** The width and height of the display container this curve gets drawn into. */
protected static int containerWidth, containerHeight;
/** Thickness of the curve. */
protected static int scale;
/** Static state that's needed to draw the new style curves. */
private static final NewCurveStyleState staticState = new NewCurveStyleState();
/** The vertex buffer used for the curve's vertices. */
private int vboID;
/** The HitObject associated with the curve to be drawn. */
protected HitObject hitObject;
/** The points along the curve to be drawn. */
protected Vec2f[] curve;
/** The indices of the points. */
protected int[] pointIndices;
/**
* Set the width and height of the container that Curves get drawn into.
* Should be called before any curves are drawn.
* @param width the container width
* @param height the container height
* @param circleDiameter the circle diameter
*/
public static void init(int width, int height, float circleDiameter) {
containerWidth = width;
containerHeight = height;
// equivalent to what happens in Slider.init()
scale = (int) (circleDiameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
//scale = scale * 118 / 128; //for curves exactly as big as the sliderball
}
/**
* Undo the static state. Static state setup caused by calls to
* {@link #draw(org.newdawn.slick.Color, org.newdawn.slick.Color, float)}
* are undone.
*/
public static void shutdown() {
staticState.shutdown();
}
/**
* Creates an object to hold the render state that's necessary to draw a curve.
* @param hitObject the HitObject that represents this curve, just used as a unique ID
* @param curve the points along the curve to be drawn
*/
public CurveRenderState(HitObject hitObject, Vec2f[] curve) {
this.hitObject = hitObject;
this.curve = curve;
this.pointIndices = new int[curve.length];
this.vboID = -1;
}
/**
* Draw a curve to the screen that's tinted with `color`. The first time
* this is called this caches the image result of the curve and on subsequent
* runs it just draws the cached copy to the screen.
* @param color tint of the curve
* @param borderColor the curve border color
* @param t the point up to which the curve should be drawn (in the interval [0, 1])
*/
public void draw(Color color, Color borderColor, float t) {
t = Utils.clamp(t, 0.0f, 1.0f);
// create curve geometry and store it on the GPU
if (vboID == -1) {
vboID = GL15.glGenBuffers();
createVertexBuffer(vboID);
}
int drawUpTo = (int) (t * (curve.length - 1));
this.renderCurve(color, borderColor, drawUpTo);
}
/**
* Discard the geometry for this curve object.
*/
public void discardGeometry() {
GL15.glDeleteBuffers(vboID);
vboID = -1;
}
/**
* A structure to hold all the important OpenGL state that needs to be
* changed to draw the curve. This is used to backup and restore the state
* so that the code outside of this (mainly Slick2D) doesn't break.
*/
private class RenderState {
boolean smoothedPoly;
boolean blendEnabled;
boolean depthEnabled;
boolean depthWriteEnabled;
boolean texEnabled;
int oldProgram;
int oldArrayBuffer;
}
/**
* Backup the current state of the relevant OpenGL state and change it to
* what's needed to draw the curve.
*/
private RenderState saveRenderState() {
RenderState state = new RenderState();
state.smoothedPoly = GL11.glGetBoolean(GL11.GL_POLYGON_SMOOTH);
state.blendEnabled = GL11.glGetBoolean(GL11.GL_BLEND);
state.depthEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_TEST);
state.depthWriteEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK);
state.texEnabled = GL11.glGetBoolean(GL11.GL_TEXTURE_2D);
state.oldProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
state.oldArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
GL11.glDisable(GL11.GL_POLYGON_SMOOTH);
GL11.glEnable(GL11.GL_BLEND);
GL14.glBlendEquation(GL14.GL_FUNC_ADD);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(true);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_TEXTURE_1D);
GL11.glBindTexture(GL11.GL_TEXTURE_1D, staticState.gradientTexture);
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL20.glUseProgram(0);
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
return state;
}
/**
* Restore the old OpenGL state that's backed up in {@code state}.
* @param state the old state to restore
*/
private void restoreRenderState(RenderState state) {
GL11.glEnable(GL11.GL_BLEND);
GL20.glUseProgram(state.oldProgram);
GL11.glDisable(GL11.GL_TEXTURE_1D);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, state.oldArrayBuffer);
if (!state.depthWriteEnabled)
GL11.glDepthMask(false);
if (!state.depthEnabled)
GL11.glDisable(GL11.GL_DEPTH_TEST);
if (state.texEnabled)
GL11.glEnable(GL11.GL_TEXTURE_2D);
if (state.smoothedPoly)
GL11.glEnable(GL11.GL_POLYGON_SMOOTH);
if (!state.blendEnabled)
GL11.glDisable(GL11.GL_BLEND);
}
/**
* Write the vertices and (with position and texture coordinates) for the full
* curve into the OpenGL buffer with the ID specified by {@code bufferID}
* @param bufferID the buffer ID for the OpenGL buffer the vertices should be written into
*/
private void createVertexBuffer(int bufferID) {
float radius = scale / 2;
int triangle_count = NewCurveStyleState.DIVIDES; // for curve caps
float last_dx=0, last_dy=0;
float last_alpha = 0;
for (int i = 0; i < curve.length; ++i) { // compute number of triangles
float x = curve[i].x;
float y = curve[i].y;
if (i > 0) {
float last_x = curve[i - 1].x;
float last_y = curve[i - 1].y;
float diff_x = x - last_x;
float diff_y = y - last_y;
float alpha = (float)Math.atan2(diff_y, diff_x);
if (i > 1) {
float theta = alpha - last_alpha;
if (theta > Math.PI) theta -= 2*Math.PI;
if (theta < -Math.PI) theta += 2*Math.PI;
if (Math.abs(theta) < 2*Math.PI / NewCurveStyleState.DIVIDES) {
triangle_count++;
} else {
int divs = (int)(Math.ceil(NewCurveStyleState.DIVIDES * Math.abs(theta) / (2*Math.PI)));
triangle_count += divs;
}
}
triangle_count += 4;
last_dx = diff_x;
last_dy = diff_y;
last_alpha = alpha;
}
}
int arrayBufferBinding = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * 3 * (triangle_count)).asFloatBuffer();
last_dx=0; last_dy=0;
float last_ox=0, last_oy=0;
for (int i = 0; i < curve.length; ++i) {
float x = curve[i].x;
float y = curve[i].y;
if (i > 0) {
/*
Render this shape:
___ ___
|A /|C /|
| /B| /D|
|/__|/__|
*/
float last_x = curve[i - 1].x;
float last_y = curve[i - 1].y;
float diff_x = x - last_x;
float diff_y = y - last_y;
float length = (float)Math.hypot(diff_x, diff_y);
float offs_x = radius * diff_y / length;
float offs_y = radius * -diff_x / length;
float alpha = (float)Math.atan2(diff_y, diff_x);
if (i > 1) {
float cross = last_dx * diff_y - last_dy * diff_x;
float theta = alpha - last_alpha;
if (theta > Math.PI) theta -= 2*Math.PI;
if (theta < -Math.PI) theta += 2*Math.PI;
if (Math.abs(theta) < 2*Math.PI / NewCurveStyleState.DIVIDES) { // small angle, just render single triangle
if (cross > 0) { // going counterclockwise
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + last_ox);buff.put(last_y + last_oy);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + offs_x); buff.put(last_y + offs_y);
buff.put(1.0f); buff.put(1.0f);
} else if (cross < 0) {
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x - offs_x); buff.put(last_y - offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x - last_ox);buff.put(last_y - last_oy);
buff.put(1.0f); buff.put(1.0f);
} else {
// straight line, very unlikely
}
} else {
int divs = (int)(Math.ceil(NewCurveStyleState.DIVIDES * Math.abs(theta) / (2*Math.PI)));
float phi = Math.abs(theta) / divs;
float sinphi = (float)Math.sin(phi);
float cosphi = (float)Math.cos(phi);
float prev_ox = last_ox;
float prev_oy = last_oy;
if (cross < 0) {
prev_ox = -offs_x;
prev_oy = -offs_y;
}
for (int j = 0; j < divs; j++) {
/*
* Ratation matrix:
* [ cos -sin ]
* [ sin cos ]
*/
float ox = cosphi*prev_ox - sinphi*prev_oy;
float oy = sinphi*prev_ox + cosphi*prev_oy;
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + prev_ox);buff.put(last_y + prev_oy);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + ox); buff.put(last_y + oy);
buff.put(1.0f); buff.put(1.0f);
prev_ox = ox; prev_oy = oy;
}
}
} else {
int divs = NewCurveStyleState.DIVIDES / 2;
float phi = (float)(Math.PI / divs);
float sinphi = (float)Math.sin(phi);
float cosphi = (float)Math.cos(phi);
float prev_ox = 0;
float prev_oy = -radius;
for (int j = 0; j < divs; j++) {
float ox = cosphi*prev_ox - sinphi*prev_oy;
float oy = sinphi*prev_ox + cosphi*prev_oy;
buff.put(1.0f); buff.put(0.5f);
buff.put(0); buff.put(0);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(0 + prev_ox); buff.put(0 + prev_oy);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(0 + ox); buff.put(0 + oy);
buff.put(1.0f); buff.put(1.0f);
prev_ox = ox; prev_oy = oy;
}
prev_ox = -offs_x;
prev_oy = -offs_y;
for (int j = 0; j < divs; j++) {
float ox = cosphi*prev_ox - sinphi*prev_oy;
float oy = sinphi*prev_ox + cosphi*prev_oy;
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + prev_ox);buff.put(last_y + prev_oy);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + ox); buff.put(last_y + oy);
buff.put(1.0f); buff.put(1.0f);
prev_ox = ox; prev_oy = oy;
}
}
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x - offs_x); buff.put(last_y - offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(x); buff.put(y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(x - offs_x); buff.put(y - offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x - offs_x); buff.put(last_y - offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(x); buff.put(y);
buff.put(0.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(x + offs_x); buff.put(y + offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(x); buff.put(y);
buff.put(0.0f); buff.put(1.0f);
buff.put(1.0f); buff.put(0.5f);
buff.put(last_x); buff.put(last_y);
buff.put(0.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(last_x + offs_x); buff.put(last_y + offs_y);
buff.put(1.0f); buff.put(1.0f);
buff.put(0.0f); buff.put(0.5f);
buff.put(x + offs_x); buff.put(y + offs_y);
buff.put(1.0f); buff.put(1.0f);
last_dx = diff_x;
last_dy = diff_y;
last_ox = offs_x;
last_oy = offs_y;
last_alpha = alpha;
}
pointIndices[i] = buff.position() / 6; // 6 elements per vertex
}
buff.flip();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, arrayBufferBinding);
}
/**
* Do the actual drawing of the curve into the currently bound framebuffer.
* @param color the color of the curve
* @param borderColor the curve border color
*/
private void renderCurve(Color color, Color borderColor, int to) {
staticState.initGradient();
RenderState state = saveRenderState();
staticState.initShaderProgram();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
GL20.glUseProgram(staticState.program);
GL20.glEnableVertexAttribArray(staticState.attribLoc);
GL20.glEnableVertexAttribArray(staticState.texCoordLoc);
GL20.glUniform1i(staticState.texLoc, 0);
GL20.glUniform4f(staticState.colLoc, color.r, color.g, color.b, color.a);
GL20.glUniform4f(staticState.colBorderLoc, borderColor.r, borderColor.g, borderColor.b, borderColor.a);
float lastSegmentX = to == 0 ? curve[1].x - curve[0].x : curve[to].x - curve[to-1].x;
float lastSegmentY = to == 0 ? curve[1].y - curve[0].y : curve[to].y - curve[to-1].y;
float lastSegmentInvLen = 1.f/(float)Math.hypot(lastSegmentX, lastSegmentY);
GL20.glUniform4f(staticState.endPointLoc, curve[to].x, curve[to].y, lastSegmentX * lastSegmentInvLen, lastSegmentY * lastSegmentInvLen);
//stride is 6*4 for the floats (4 bytes) (u,v)(x,y,z,w)
//2*4 is for skipping the first 2 floats (u,v)
GL20.glVertexAttribPointer(staticState.attribLoc, 4, GL11.GL_FLOAT, false, 6 * 4, 2 * 4);
GL20.glVertexAttribPointer(staticState.texCoordLoc, 2, GL11.GL_FLOAT, false, 6 * 4, 0);
GL11.glColorMask(false,false,false,false);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, pointIndices[to]);
GL11.glColorMask(true,true,true,true);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDepthFunc(GL11.GL_EQUAL);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, pointIndices[to]);
GL11.glDepthFunc(GL11.GL_LESS);
GL11.glFlush();
GL20.glDisableVertexAttribArray(staticState.texCoordLoc);
GL20.glDisableVertexAttribArray(staticState.attribLoc);
restoreRenderState(state);
}
/**
* Contains all the necessary state that needs to be tracked to draw curves
* in the new style and not re-create the shader each time.
*
* @author Bigpet {@literal <dravorek (at) gmail.com>}
*/
private static class NewCurveStyleState {
/**
* Used for new style Slider rendering, defines how many vertices there
* are in a circle. Must be even.
*/
protected static final int DIVIDES = 30;
/** OpenGL shader program ID used to draw and recolor the curve. */
protected int program = 0;
/** OpenGL shader attribute location of the vertex position attribute. */
protected int attribLoc = 0;
/** OpenGL shader attribute location of the texture coordinate attribute. */
protected int texCoordLoc = 0;
/** OpenGL shader uniform location of the end point attribute. */
protected int endPointLoc = 0;
/** OpenGL shader uniform location of the color attribute. */
protected int colLoc = 0;
/** OpenGL shader uniform location of the border color attribute. */
protected int colBorderLoc = 0;
/** OpenGL shader uniform location of the texture sampler attribute. */
protected int texLoc = 0;
/** OpenGL texture id for the gradient texture for the curve. */
protected int gradientTexture = 0;
/**
* Reads the first row of the slider gradient texture and upload it as
* a 1D texture to OpenGL if it hasn't already been done.
*/
public void initGradient() {
if (gradientTexture == 0) {
Image slider = GameImage.SLIDER_GRADIENT.getImage().getScaledCopy(1.0f / GameImage.getUIscale());
staticState.gradientTexture = GL11.glGenTextures();
ByteBuffer buff = BufferUtils.createByteBuffer(slider.getWidth() * 4);
for (int i = 0; i < slider.getWidth(); ++i) {
Color col = slider.getColor(i, 0);
buff.put((byte) (255 * col.r));
buff.put((byte) (255 * col.g));
buff.put((byte) (255 * col.b));
buff.put((byte) (255 * col.a));
}
buff.flip();
GL11.glBindTexture(GL11.GL_TEXTURE_1D, gradientTexture);
GL11.glTexImage1D(GL11.GL_TEXTURE_1D, 0, GL11.GL_RGBA, slider.getWidth(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buff);
ContextCapabilities capabilities = GLContext.getCapabilities();
if (capabilities.OpenGL30) {
GL30.glGenerateMipmap(GL11.GL_TEXTURE_1D);
} else if (capabilities.GL_EXT_framebuffer_object) {
EXTFramebufferObject.glGenerateMipmapEXT(GL11.GL_TEXTURE_1D);
} else {
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL14.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
}
}
}
/**
* Compiles and links the shader program for the new style curve objects
* if it hasn't already been compiled and linked.
*/
public void initShaderProgram() {
if (program == 0) {
program = GL20.glCreateProgram();
int vtxShdr = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
int frgShdr = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
GL20.glShaderSource(vtxShdr, "#version 130\n"
+ "\n"
+ "uniform vec4 endPoint;\n"
+ "\n"
+ "attribute vec4 in_position;\n"
+ "attribute vec2 in_tex_coord;\n"
+ "\n"
+ "varying vec2 tex_coord;\n"
+ "void main()\n"
+ "{\n"
+ " vec4 pos = in_position;\n"
+ " if (gl_VertexID < " + 3 * DIVIDES / 2 + ") {\n"
+ " mat2 rot = mat2(endPoint.zw, vec2(-1.0,1.0)*endPoint.wz);\n"
+ " pos.xy = endPoint.xy + rot * in_position.xy;\n"
+ " }\n"
+ " gl_Position = gl_ModelViewProjectionMatrix * pos;\n"
+ " tex_coord = in_tex_coord;\n"
+ "}");
GL20.glCompileShader(vtxShdr);
int res = GL20.glGetShaderi(vtxShdr, GL20.GL_COMPILE_STATUS);
if (res != GL11.GL_TRUE) {
String error = GL20.glGetShaderInfoLog(vtxShdr, 1024);
Log.error("Vertex Shader compilation failed.", new Exception(error));
}
GL20.glShaderSource(frgShdr, "#version 110\n"
+ "\n"
+ "uniform sampler1D tex;\n"
+ "uniform vec2 tex_size;\n"
+ "uniform vec4 col_tint;\n"
+ "uniform vec4 col_border;\n"
+ "\n"
+ "varying vec2 tex_coord;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " vec4 in_color = texture1D(tex, tex_coord.x);\n"
+ " float blend_factor = in_color.r-in_color.b;\n"
+ " vec4 new_color = vec4(mix(in_color.xyz*col_border.xyz,col_tint.xyz,blend_factor),in_color.w*col_tint.w);\n"
+ " gl_FragColor = new_color;\n"
+ "}");
GL20.glCompileShader(frgShdr);
res = GL20.glGetShaderi(frgShdr, GL20.GL_COMPILE_STATUS);
if (res != GL11.GL_TRUE) {
String error = GL20.glGetShaderInfoLog(frgShdr, 1024);
Log.error("Fragment Shader compilation failed.", new Exception(error));
}
GL20.glAttachShader(program, vtxShdr);
GL20.glAttachShader(program, frgShdr);
GL20.glLinkProgram(program);
res = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
if (res != GL11.GL_TRUE) {
String error = GL20.glGetProgramInfoLog(program, 1024);
Log.error("Program linking failed.", new Exception(error));
}
GL20.glDeleteShader(vtxShdr);
GL20.glDeleteShader(frgShdr);
attribLoc = GL20.glGetAttribLocation(program, "in_position");
texCoordLoc = GL20.glGetAttribLocation(program, "in_tex_coord");
texLoc = GL20.glGetUniformLocation(program, "tex");
colLoc = GL20.glGetUniformLocation(program, "col_tint");
endPointLoc = GL20.glGetUniformLocation(program, "endPoint");
colBorderLoc = GL20.glGetUniformLocation(program, "col_border");
}
}
/**
* Cleanup any OpenGL objects that may have been initialized.
*/
private void shutdown() {
if (gradientTexture != 0) {
GL11.glDeleteTextures(gradientTexture);
gradientTexture = 0;
}
if (program != 0) {
GL20.glDeleteProgram(program);
program = 0;
attribLoc = 0;
texCoordLoc = 0;
colLoc = 0;
colBorderLoc = 0;
texLoc = 0;
}
}
}
}