mirror of https://github.com/Minestom/Minestom.git
138 lines
4.8 KiB
Java
138 lines
4.8 KiB
Java
|
package net.minestom.server.map.framebuffers;
|
||
|
|
||
|
import net.minestom.server.MinecraftServer;
|
||
|
import net.minestom.server.map.Framebuffer;
|
||
|
import net.minestom.server.map.MapColors;
|
||
|
import net.minestom.server.timer.Task;
|
||
|
import net.minestom.server.utils.time.TimeUnit;
|
||
|
import org.lwjgl.BufferUtils;
|
||
|
import org.lwjgl.PointerBuffer;
|
||
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
||
|
import org.lwjgl.opengl.GL;
|
||
|
import org.lwjgl.system.MemoryStack;
|
||
|
|
||
|
import java.nio.ByteBuffer;
|
||
|
|
||
|
import static org.lwjgl.glfw.GLFW.*;
|
||
|
import static org.lwjgl.opengl.GL11.*;
|
||
|
|
||
|
/**
|
||
|
* GLFW-based framebuffer.
|
||
|
*
|
||
|
* Due to its interfacing with OpenGL(-ES), extra care needs to be applied when using this framebuffer.
|
||
|
* Rendering to this framebuffer should only be done via the thread on which the context is present.
|
||
|
* To perform map conversion at the end of a frame, it is advised to use {@link #render(Runnable)} to render to the map.
|
||
|
*
|
||
|
* Use {@link #changeRenderingThreadToCurrent} in a thread to switch the thread on which to render.
|
||
|
*
|
||
|
* Use {@link #setupRenderLoop} with a callback to setup a task in the {@link net.minestom.server.timer.SchedulerManager}
|
||
|
* to automatically render to the offscreen buffer on a specialized thread.
|
||
|
*
|
||
|
* GLFWFramebuffer does not provide guarantee that the result of {@link #toMapColors()} is synchronized with rendering, but
|
||
|
* it will be updated after each frame rendered through {@link #render(Runnable)} or {@link #setupRenderLoop(long, TimeUnit, Runnable)}.
|
||
|
*
|
||
|
* This framebuffer is meant to render to a single map (ie it is only compatible with 128x128 rendering)
|
||
|
*/
|
||
|
public class GLFWFramebuffer implements Framebuffer {
|
||
|
|
||
|
private final byte[] colors = new byte[WIDTH*HEIGHT];
|
||
|
private final ByteBuffer pixels = BufferUtils.createByteBuffer(WIDTH*HEIGHT*4);
|
||
|
private final long glfwWindow;
|
||
|
|
||
|
public GLFWFramebuffer() {
|
||
|
this(GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates the framebuffer and initializes a new EGL context
|
||
|
*/
|
||
|
public GLFWFramebuffer(int apiContext, int clientAPI) {
|
||
|
if(!glfwInit()) {
|
||
|
throw new RuntimeException("Failed to init GLFW");
|
||
|
}
|
||
|
|
||
|
GLFWErrorCallback.createPrint().set();
|
||
|
glfwDefaultWindowHints();
|
||
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||
|
|
||
|
glfwWindowHint(GLFW_CONTEXT_CREATION_API, apiContext);
|
||
|
glfwWindowHint(GLFW_CLIENT_API, clientAPI);
|
||
|
|
||
|
this.glfwWindow = glfwCreateWindow(WIDTH, HEIGHT, "", 0L, 0L);
|
||
|
if(glfwWindow == 0L) {
|
||
|
try(var stack = MemoryStack.stackPush()) {
|
||
|
PointerBuffer desc = stack.mallocPointer(1);
|
||
|
int errcode = glfwGetError(desc);
|
||
|
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public GLFWFramebuffer unbindContextFromThread() {
|
||
|
glfwMakeContextCurrent(0L);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public void changeRenderingThreadToCurrent() {
|
||
|
glfwMakeContextCurrent(glfwWindow);
|
||
|
GL.createCapabilities();
|
||
|
}
|
||
|
|
||
|
public Task setupRenderLoop(long period, TimeUnit unit, Runnable rendering) {
|
||
|
return MinecraftServer.getSchedulerManager()
|
||
|
.buildTask(new Runnable() {
|
||
|
private boolean first = true;
|
||
|
|
||
|
@Override
|
||
|
public void run() {
|
||
|
if(first) {
|
||
|
changeRenderingThreadToCurrent();
|
||
|
first = false;
|
||
|
}
|
||
|
render(rendering);
|
||
|
}
|
||
|
})
|
||
|
.repeat(period, unit)
|
||
|
.schedule();
|
||
|
}
|
||
|
|
||
|
public void render(Runnable rendering) {
|
||
|
rendering.run();
|
||
|
glfwSwapBuffers(glfwWindow);
|
||
|
prepareMapColors();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||
|
* 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)*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)] = MapColors.closestColor(argb).getIndex();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void cleanup() {
|
||
|
glfwTerminate();
|
||
|
}
|
||
|
|
||
|
public long getGLFWWindow() {
|
||
|
return glfwWindow;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public byte[] toMapColors() {
|
||
|
return colors;
|
||
|
}
|
||
|
}
|