From e04a4fad0203cc44720a009a98943d8b9b52c211 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 11 Aug 2020 23:23:14 +0200 Subject: [PATCH] Fast render with shader color lookup (wrong colors for the moment) --- build.gradle | 2 + .../minestom/demo/largeframebuffers/Demo.java | 33 +-- .../largeframebuffers/OpenGLRendering.java | 218 ++++++++++++++++++ .../largeframebuffers/PaletteGenerator.java | 41 ++++ .../map/framebuffers/GLFWCapableBuffer.java | 45 +++- src/lwjgl/resources/shaders/fragment.glsl | 33 +++ src/lwjgl/resources/shaders/vertex.glsl | 16 ++ src/lwjgl/resources/textures/box.png | Bin 0 -> 274 bytes .../net/minestom/server/map/MapColors.java | 2 +- 9 files changed, 356 insertions(+), 34 deletions(-) create mode 100644 src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java create mode 100644 src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java create mode 100644 src/lwjgl/resources/shaders/fragment.glsl create mode 100644 src/lwjgl/resources/shaders/vertex.glsl create mode 100644 src/lwjgl/resources/textures/box.png diff --git a/build.gradle b/build.gradle index fcce68d31..a054c35f3 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java index 68a0c7aba..4607b36bb 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java @@ -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(); - } } diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java new file mode 100644 index 000000000..cd57de559 --- /dev/null +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java @@ -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); + } + +} diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java new file mode 100644 index 000000000..dad366baf --- /dev/null +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java @@ -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 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(); + } + } +} diff --git a/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java b/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java index b75d8e7ad..26f236ef7 100644 --- a/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java +++ b/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java @@ -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; + } } diff --git a/src/lwjgl/resources/shaders/fragment.glsl b/src/lwjgl/resources/shaders/fragment.glsl new file mode 100644 index 000000000..8ec323d44 --- /dev/null +++ b/src/lwjgl/resources/shaders/fragment.glsl @@ -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); +} \ No newline at end of file diff --git a/src/lwjgl/resources/shaders/vertex.glsl b/src/lwjgl/resources/shaders/vertex.glsl new file mode 100644 index 000000000..a2d795bcd --- /dev/null +++ b/src/lwjgl/resources/shaders/vertex.glsl @@ -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); +} \ No newline at end of file diff --git a/src/lwjgl/resources/textures/box.png b/src/lwjgl/resources/textures/box.png new file mode 100644 index 0000000000000000000000000000000000000000..1f467c6c22efb7ef6cc2be72c02a2c72cb66e750 GIT binary patch literal 274 zcmV+t0qy>YP)Y4>N;B~YK+wsPthEk`{XhofZ(f$F@G}_Mbl+EUsXEn~iL7o(hLh_xl;D;FiG|9i5 Yeh>?o6j;%#>;M1&07*qoM6N<$f~S#vbpQYW literal 0 HcmV?d00001 diff --git a/src/main/java/net/minestom/server/map/MapColors.java b/src/main/java/net/minestom/server/map/MapColors.java index f3450ec14..4c9083e37 100644 --- a/src/main/java/net/minestom/server/map/MapColors.java +++ b/src/main/java/net/minestom/server/map/MapColors.java @@ -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),