New optional slider style

New slider rendering works by rendering the slider to an
offscreen buffer

Add CurveRenderState.java and FrameBufferCache.java that were forgotten in the last commit
This commit is contained in:
Peter Tissen
2015-03-30 14:19:39 +02:00
parent 90c8c9e705
commit 41c7825728
11 changed files with 790 additions and 28 deletions

View File

@@ -0,0 +1,428 @@
/*
* 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);
}
}
}