opsu-dance/src/itdelatrisu/opsu/render/CurveRenderState.java

429 lines
14 KiB
Java
Raw Normal View History

/*
* 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.Options;
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.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
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.
*/
public class CurveRenderState {
/** cached drawn slider, only used if new style sliders are activated */
public Rendertarget fbo;
/** thickness of the curve */
float scale;
/** The HitObject associated with the curve to be drawn */
HitObject hitObject;
/** Static state that's needed to draw the new style curves */
private static final NewCurveStyleState staticState = new NewCurveStyleState();
public CurveRenderState(float scale, HitObject hitObject) {
fbo = null;
this.scale = scale;
this.hitObject = hitObject;
}
public void draw(Color color, Vec2f[] curve) {
float alpha = color.a;
//if this curve hasn't been drawn, draw it and cache the result
if (fbo == null) {
FrameBufferCache cache = FrameBufferCache.getInstance();
Rendertarget mapping = cache.get(hitObject);
if (mapping == null) {
mapping = cache.insert(hitObject);
}
fbo = mapping;
int old_fb = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
int old_tex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo.getID());
GL11.glViewport(0, 0, fbo.width, fbo.height);
GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
Utils.COLOR_WHITE_FADE.a = 1.0f;
this.draw_curve(color, curve);
color.a = 1f;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, old_tex);
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, old_fb);
Utils.COLOR_WHITE_FADE.a = alpha;
}
//draw a fullscreen quad with the texture that contains the curve
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_TEXTURE_1D);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID());
GL11.glBegin(GL11.GL_QUADS);
GL11.glColor4f(1.0f, 1.0f, 1.0f, alpha);
GL11.glTexCoord2f(1.0f, 1.0f);
GL11.glVertex2i(fbo.width, 0);
GL11.glTexCoord2f(0.0f, 1.0f);
GL11.glVertex2i(0, 0);
GL11.glTexCoord2f(0.0f, 0.0f);
GL11.glVertex2i(0, fbo.height);
GL11.glTexCoord2f(1.0f, 0.0f);
GL11.glVertex2i(fbo.width, fbo.height);
GL11.glEnd();
}
public void discardCache() {
fbo = null;
FrameBufferCache.getInstance().freeMappingFor(hitObject);
}
private class RenderState {
boolean smoothedPoly;
boolean blendEnabled;
boolean depthEnabled;
boolean depthWriteEnabled;
boolean texEnabled;
int texUnit;
int oldProgram;
int oldArrayBuffer;
}
private RenderState startRender() {
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.texUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
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, GL11.GL_CLAMP);
GL20.glUseProgram(0);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glPushMatrix();
GL11.glLoadIdentity();
return state;
}
private void endRender(RenderState state) {
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glPopMatrix();
GL11.glEnable(GL11.GL_BLEND);
GL20.glUseProgram(state.oldProgram);
GL13.glActiveTexture(state.texUnit);
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);
}
}
/**
* Do the actual drawing of the curve into the currently bound framebuffer.
* @param color the color of the curve
* @param curve the points along the curve
*/
private void draw_curve(Color color, Vec2f[] curve) {
RenderState state = startRender();
int vtx_buf;
//floatsize * (position + texture coordinates) * (number of cones) * (vertices in a cone)
FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * (2 * curve.length - 1) * (NewCurveStyleState.DIVIDES + 2)).asFloatBuffer();
staticState.initShaderProgram();
staticState.initGradient();
vtx_buf = GL15.glGenBuffers();
for (int i = 0; i < curve.length; ++i) {
float x = curve[i].x;
float y = curve[i].y;
//if(i == 0 || i==curve.length-1){
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
if (i != 0) {
float last_x = curve[i - 1].x;
float last_y = curve[i - 1].y;
double diff_x = x - last_x;
double diff_y = y - last_y;
x = (float) (x - diff_x / 2);
y = (float) (y - diff_y / 2);
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
}
}
buff.flip();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtx_buf);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW);
staticState.render(color, curve.length * 2 - 1, vtx_buf);
GL15.glDeleteBuffers(vtx_buf);
endRender(state);
}
/**
* Fill {@code buff} with the texture coordinates and positions for a cone
* with {@code DIVIDES} ground corners that has its center at the coordinates
* {@code (x1,y1)}
* @param buff the buffer to be filled
* @param x1 x-coordinate of the cone
* @param y1 y-coordinate of the cone
* @param DIVIDES the base of the cone is a regular polygon with this many sides
*/
protected void fillCone(FloatBuffer buff, float x1, float y1, final int DIVIDES) {
float divx = Options.getLatestResolutionWidth() / 2.0f;
float divy = Options.getLatestResolutionHeight() / 2.0f;
float offx = -1.0f;
float offy = 1.0f;
float x, y;
float radius = scale / 2;
buff.put(1.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(1.0, 0.5);
x = offx + x1 / divx;
y = offy - y1 / divy;
buff.put(x);
buff.put(y);
buff.put(0f);
buff.put(1f);
//GL11.glVertex4f(x, y, 0.0f, 1.0f);
for (int j = 0; j < DIVIDES; ++j) {
double phase = j * (float) Math.PI * 2 / DIVIDES;
buff.put(0.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(0.0, 0.5);
x = (x1 + radius * (float) Math.sin(phase)) / divx;
y = (y1 + radius * (float) Math.cos(phase)) / divy;
buff.put((offx + x));
buff.put((offy - y));
buff.put(1f);
buff.put(1f);
//GL11.glVertex4f(x + 90 * (float) Math.sin(phase), y + 90 * (float) Math.cos(phase), 1.0f, 1.0f);
}
buff.put(0.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(0.0, 0.5);
x = (x1 + radius * (float) Math.sin(0.0)) / divx;
y = (y1 + radius * (float) Math.cos(0.0)) / divy;
buff.put((offx + x));
buff.put((offy - y));
buff.put(1f);
buff.put(1f);
//GL11.glVertex4f(x + 90 * (float) Math.sin(0.0), y + 90 * (float) Math.cos(0.0), 1.0f, 1.0f);
}
/**
* 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@gmail.com>}
*/
private static class NewCurveStyleState {
/**
* used for new style Slider rendering, defines how many vertices the
* base of the cone has that is used to draw the curve
*/
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 color attribute
*/
protected int colLoc = 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();
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);
GL30.glGenerateMipmap(GL11.GL_TEXTURE_1D);
}
}
/**
* 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 330\n"
+ "\n"
+ "layout(location = 0) in vec4 in_position;\n"
+ "layout(location = 1) in vec2 in_tex_coord;\n"
+ "\n"
+ "out vec2 tex_coord;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = in_position;\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 330\n"
+ "\n"
+ "uniform sampler1D tex;\n"
+ "uniform vec2 tex_size;\n"
+ "uniform vec3 col_tint;\n"
+ "\n"
+ "in vec2 tex_coord;\n"
+ "layout(location = 0) out vec4 out_colour;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " vec4 in_color = texture(tex, tex_coord.x);\n"
+ " float blend_factor = in_color.r-in_color.b;\n"
+ " vec4 new_color = vec4(mix(in_color.xyz,col_tint,blend_factor),in_color.w);\n"
+ " out_colour = 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));
}
attribLoc = GL20.glGetAttribLocation(program, "in_position");
texCoordLoc = GL20.glGetAttribLocation(program, "in_tex_coord");
int texLoc = GL20.glGetUniformLocation(program, "tex");
colLoc = GL20.glGetUniformLocation(program, "col_tint");
GL20.glUniform1i(texLoc, 0);
}
}
/**
* Make the drawcalls for the curve. This requires the {@code vertexBufferObject} to be filled with
* {@code numCones * (NewCurveStyleState.DIVIDES+2)} sets of (u,v,x,y,z,w) floats
* @param color the color of the curve to be drawn
* @param numCones the number of points in the {code vertexBufferObject}
* @param vertexBufferObject the OpenGL vertex buffer object ID that
* contains the positions and texture coordinates of the cones
*/
public void render(Color color, int numCones, int vertexBufferObject) {
GL20.glUseProgram(program);
GL20.glEnableVertexAttribArray(attribLoc);
GL20.glEnableVertexAttribArray(texCoordLoc);
//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(attribLoc, 4, GL11.GL_FLOAT, false, 6 * 4, 2 * 4);
GL20.glVertexAttribPointer(texCoordLoc, 2, GL11.GL_FLOAT, false, 6 * 4, 0);
GL20.glUniform3f(colLoc, color.r, color.g, color.b);
for (int i = 0; i < numCones; ++i) {
GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2);
}
GL20.glDisableVertexAttribArray(texCoordLoc);
GL20.glDisableVertexAttribArray(attribLoc);
}
}
}