Fast render with shader color lookup (wrong colors for the moment)

This commit is contained in:
jglrxavpok 2020-08-11 23:23:14 +02:00
parent 23f64fec11
commit e04a4fad02
9 changed files with 356 additions and 34 deletions

View File

@ -125,6 +125,8 @@ dependencies {
lwjglApi "org.lwjgl:lwjgl-opengl"
lwjglApi "org.lwjgl:lwjgl-opengles"
lwjglApi "org.lwjgl:lwjgl-glfw"
lwjglApi "org.lwjgl:lwjgl-glfw"
lwjglApi 'org.joml:joml:1.9.25'
lwjglRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"

View File

@ -24,8 +24,6 @@ import java.awt.*;
import java.util.Arrays;
import java.util.function.Consumer;
import static org.lwjgl.opengl.GL11.*;
public class Demo {
public static void main(String[] args) {
@ -45,11 +43,17 @@ public class Demo {
LargeGraphics2DFramebuffer graphics2DFramebuffer = new LargeGraphics2DFramebuffer(512, 512);
LargeGLFWFramebuffer glfwFramebuffer = new LargeGLFWFramebuffer(512, 512);
renderingLoop(0, directFramebuffer, Demo::directRendering);
renderingLoop(101, graphics2DFramebuffer, Demo::graphics2DRendering);
glfwFramebuffer.useMapColors();
glfwFramebuffer.changeRenderingThreadToCurrent();
OpenGLRendering.init();
glfwFramebuffer.unbindContextFromThread();
// renderingLoop(0, directFramebuffer, Demo::directRendering);
// renderingLoop(101, graphics2DFramebuffer, Demo::graphics2DRendering);
renderingLoop(201, glfwFramebuffer, f -> {});
glfwFramebuffer.setupRenderLoop(30, TimeUnit.MILLISECOND, Demo::openGLRendering);
glfwFramebuffer.setupRenderLoop(15, TimeUnit.MILLISECOND, () -> OpenGLRendering.render(glfwFramebuffer));
for (int x = -2; x <= 2; x++) {
for (int z = -2; z <= 2; z++) {
@ -97,7 +101,7 @@ public class Demo {
f.preparePacket(packet);
sendPacket(packet);
}
}).repeat(30, TimeUnit.MILLISECOND).schedule();
}).repeat(15, TimeUnit.MILLISECOND).schedule();
}
private static void sendPacket(MapDataPacket packet) {
@ -117,21 +121,4 @@ public class Demo {
renderer.drawString("Here's a very very long string that needs multiple maps to fit", 0, 100);
}
private static void openGLRendering() {
glClearColor(0f, 0f, 0f, 1f);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(0, -0.75f);
glColor3f(1f, 0f, 0f);
glVertex2f(0.75f, 0.75f);
glColor3f(0f, 1f, 0f);
glVertex2f(-0.75f, 0.75f);
glColor3f(0f, 0f, 1f);
glEnd();
}
}

View File

@ -0,0 +1,218 @@
package net.minestom.demo.largeframebuffers;
import net.minestom.server.map.MapColors;
import net.minestom.server.map.framebuffers.LargeGLFWFramebuffer;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GLUtil;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.stream.Collectors;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
public final class OpenGLRendering {
private static int vbo;
private static int indexBuffer;
private static final int VERTEX_SIZE = 5*4; // position + tex
// array of vertices (order: X,Y,Z, Tex U, Tex V)
private static float[] vertices = {
// front face
-1f, -1f, -1f, 0, 0,
1f, -1f, -1f, 1, 0,
1f, 1f, -1f, 1, 1,
-1f, 1f, -1f, 0, 1,
// back face
-1f, -1f, 1f, 0, 1,
1f, -1f, 1f, 1, 1,
1f, 1f, 1f, 1, 0,
-1f, 1f, 1f, 0, 0,
};
private static int[] indices = {
// south face
0,1,2,
2,3,0,
// north face
4,5,6,
6,7,4,
// west face
0,4,7,
7,3,0,
// east face
1,5,6,
6,2,1,
// top face
3, 2, 6,
6, 7, 3
};
private static int renderShader;
private static Matrix4f projectionMatrix;
private static Matrix4f viewMatrix;
private static Matrix4f modelMatrix;
private static int projectionUniform;
private static int viewUniform;
private static int modelUniform;
private static int paletteTexture;
private static int boxTexture;
static void init() {
// GLUtil.setupDebugMessageCallback();
paletteTexture = loadTexture("palette");
boxTexture = loadTexture("box");
vbo = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
indexBuffer = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, VERTEX_SIZE, 0); // position
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, VERTEX_SIZE, 3*4); // color
glBindBuffer(GL_ARRAY_BUFFER, 0);
// prepare matrices and shader
renderShader = glCreateProgram();
projectionMatrix = new Matrix4f().setPerspective((float) (Math.PI/4f), 1f, 0.001f, 100f);
viewMatrix = new Matrix4f().setLookAt(5f, 5f, 5f, 0, 0, 0, 0, -1, 0);
modelMatrix = new Matrix4f().identity();
int vertexShader = createShader("/shaders/vertex.glsl", GL_VERTEX_SHADER);
int fragmentShader = createShader("/shaders/fragment.glsl", GL_FRAGMENT_SHADER);
glAttachShader(renderShader, vertexShader);
glAttachShader(renderShader, fragmentShader);
glLinkProgram(renderShader);
if(glGetProgrami(renderShader, GL_LINK_STATUS) == 0) {
System.err.println("Link error: "+glGetProgramInfoLog(renderShader));
}
projectionUniform = glGetUniformLocation(renderShader, "projection");
viewUniform = glGetUniformLocation(renderShader, "view");
modelUniform = glGetUniformLocation(renderShader, "model");
int paletteSizeUniform = glGetUniformLocation(renderShader, "paletteSize");
int paletteUniform = glGetUniformLocation(renderShader, "palette");
int boxUniform = glGetUniformLocation(renderShader, "box");
glUseProgram(renderShader); {
uploadMatrix(projectionUniform, projectionMatrix);
uploadMatrix(viewUniform, viewMatrix);
glUniform1i(boxUniform, 0); // texture unit 0
glUniform1i(paletteUniform, 1); // texture unit 1
glUniform1f(paletteSizeUniform, 236);
}
}
private static int loadTexture(String filename) {
int tex = glGenTextures();
glBindTexture(GL_TEXTURE_2D, tex);
BufferedImage image;
try {
image = ImageIO.read(OpenGLRendering.class.getResourceAsStream("/textures/"+filename+".png"));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Missing image "+filename, e);
}
ByteBuffer pixels = BufferUtils.createByteBuffer(image.getWidth()*image.getHeight()*4);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
int alpha = (rgb >> 24) & 0xFF;
int red = (rgb >> 16) & 0xFF;
int green = (rgb >> 8) & 0xFF;
int blue = rgb & 0xFF;
pixels.put((byte) red);
pixels.put((byte) green);
pixels.put((byte) blue);
pixels.put((byte) alpha);
}
}
pixels.flip();
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// closest neighbor required here, as pixels can have very different rgb values, and interpolation will break palette lookup
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return tex;
}
private static void uploadMatrix(int uniform, Matrix4f matrix) {
float[] values = new float[4*4];
matrix.get(values);
glUniformMatrix4fv(uniform, false, values);
}
private static int createShader(String filename, int type) {
int shader = glCreateShader(type);
try(BufferedReader reader = new BufferedReader(new InputStreamReader(OpenGLRendering.class.getResourceAsStream(filename)))) {
StringBuffer buffer = new StringBuffer();
String source = reader.lines().collect(Collectors.joining("\n"));
glShaderSource(shader, source);
glCompileShader(shader);
} catch (IOException e) {
e.printStackTrace();
}
return shader;
}
private static long lastTime = System.currentTimeMillis();
private static int frame = 0;
static void render(LargeGLFWFramebuffer framebuffer) {
if(frame % 100 == 0) {
long time = System.currentTimeMillis();
long dt = time-lastTime;
System.out.println(">> Render time for 100 frames: "+dt);
System.out.println(">> Average time per frame: "+(dt/100.0));
System.out.println(">> Average FPS: "+(1000.0/(dt/100.0)));
lastTime = time;
}
frame++;
glClearColor(MapColors.COLOR_BLACK.multiply53()/255.0f, 0f, 0f, 1f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
modelMatrix.rotateY((float) (Math.PI/60f));
// TODO: render texture
glUseProgram(renderShader); {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, paletteTexture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, boxTexture);
uploadMatrix(modelUniform, modelMatrix);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0);
}
glUseProgram(0);
}
}

View File

@ -0,0 +1,41 @@
package net.minestom.demo.largeframebuffers;
import net.minestom.server.map.MapColors;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class PaletteGenerator {
public static void main(String[] args) {
Map<Byte, Integer> colors = new HashMap<>();
int highestIndex = 0;
for(MapColors c : MapColors.values()) {
for(MapColors.Multiplier m : MapColors.Multiplier.values()) {
byte index = m.apply(c);
if(((int)index & 0xFF) > highestIndex) {
highestIndex = ((int)index) & 0xFF;
}
int rgb = MapColors.PreciseMapColor.toRGB(c, m);
colors.put(index, rgb);
}
}
BufferedImage paletteTexture = new BufferedImage(highestIndex+1, 1, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i <= highestIndex; i++) {
int rgb = colors.getOrDefault((byte)i, 0);
int argb = (0xFF << 24) | rgb;
paletteTexture.setRGB(i, 0, argb);
}
try {
ImageIO.write(paletteTexture, "png", new File("src/lwjgl/resources/textures/palette.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -24,6 +24,8 @@ abstract class GLFWCapableBuffer {
private final long glfwWindow;
private final int width;
private final int height;
private final ByteBuffer colorsBuffer;
private boolean onlyMapColors;
protected GLFWCapableBuffer(int width, int height) {
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
@ -36,6 +38,7 @@ abstract class GLFWCapableBuffer {
this.width = width;
this.height = height;
this.colors = new byte[width*height];
colorsBuffer = BufferUtils.createByteBuffer(width*height);
this.pixels = BufferUtils.createByteBuffer(width*height*4);
if(!glfwInit()) {
throw new RuntimeException("Failed to init GLFW");
@ -99,16 +102,22 @@ abstract class GLFWCapableBuffer {
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
*/
public void prepareMapColors() {
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int i = Framebuffer.index(x, y, width)*4;
int red = pixels.get(i) & 0xFF;
int green = pixels.get(i+1) & 0xFF;
int blue = pixels.get(i+2) & 0xFF;
int alpha = pixels.get(i+3) & 0xFF;
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
colors[Framebuffer.index(x, y, width)] = MapColors.closestColor(argb).getIndex();
if(onlyMapColors) {
colorsBuffer.rewind();
glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, colorsBuffer);
colorsBuffer.get(colors);
} else {
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int i = Framebuffer.index(x, y, width)*4;
int red = pixels.get(i) & 0xFF;
int green = pixels.get(i+1) & 0xFF;
int blue = pixels.get(i+2) & 0xFF;
int alpha = pixels.get(i+3) & 0xFF;
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
colors[Framebuffer.index(x, y, width)] = MapColors.closestColor(argb).getIndex();
}
}
}
}
@ -128,4 +137,20 @@ abstract class GLFWCapableBuffer {
public int height() {
return height;
}
/**
* Tells this buffer that the **RED** channel contains the index of the map color to use.
*
* This allows for optimizations and fast rendering (because there is no need for a conversion)
*/
public void useMapColors() {
onlyMapColors = true;
}
/**
* Opposite to {@link #useMapColors()}
*/
public void useRGB() {
onlyMapColors = false;
}
}

View File

@ -0,0 +1,33 @@
#version 330
in vec2 uv;
out vec4 fragColor;
uniform sampler2D box;
uniform sampler2D palette;
uniform float paletteSize;
void main() {
vec3 vertexColor = texture(box, uv).rgb;
// render in map colors
int closest = 0;
float distance = 1.0f/0.0f;
for(int i = 1; i < paletteSize; i++) {
vec3 mapColor = texture(palette, vec2(i/paletteSize, 0.0)).rgb;
float dr = mapColor.r - vertexColor.r;
float dg = mapColor.g - vertexColor.g;
float db = mapColor.b - vertexColor.b;
float d = dr*dr+dg*dg+db*db;
if(d < distance) {
distance = d;
closest = i;
}
}
fragColor = vec4(closest/255.0, closest/255.0, closest/255.0, 1.0);
//fragColor = vec4(vertexColor, 1.0);
}

View File

@ -0,0 +1,16 @@
#version 330
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 texCoords;
out vec2 uv;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main() {
mat4 mvp = projection * view * model;
uv = texCoords;
gl_Position = mvp * vec4(pos, 1.0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

View File

@ -307,7 +307,7 @@ public enum MapColors {
}
}
enum Multiplier {
public enum Multiplier {
x1_00(MapColors::baseColor, 1.00),
x0_53(MapColors::multiply53, 0.53),
x0_71(MapColors::multiply71, 0.71),