mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-28 03:57:50 +01:00
OpenGL rendering on maps
This commit is contained in:
parent
5971db5b92
commit
247dfeefc8
32
build.gradle
32
build.gradle
@ -1,9 +1,28 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'net.ltgt.apt' version '0.10'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
||||
}
|
||||
|
||||
project.ext.lwjglVersion = "3.2.3"
|
||||
|
||||
switch (OperatingSystem.current()) {
|
||||
case OperatingSystem.LINUX:
|
||||
def osArch = System.getProperty("os.arch")
|
||||
project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64")
|
||||
? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}"
|
||||
: "natives-linux"
|
||||
break
|
||||
case OperatingSystem.MAC_OS:
|
||||
project.ext.lwjglNatives = "natives-macos"
|
||||
break
|
||||
case OperatingSystem.WINDOWS:
|
||||
project.ext.lwjglNatives = System.getProperty("os.arch").contains("64") ? "natives-windows" : "natives-windows-x86"
|
||||
break
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -74,4 +93,17 @@ dependencies {
|
||||
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
api 'com.github.jglrxavpok:Hephaistos:v1.0.4'
|
||||
|
||||
// LWJGL, for map rendering
|
||||
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
||||
|
||||
api "org.lwjgl:lwjgl"
|
||||
api "org.lwjgl:lwjgl-egl"
|
||||
api "org.lwjgl:lwjgl-opengl"
|
||||
api "org.lwjgl:lwjgl-opengles"
|
||||
api "org.lwjgl:lwjgl-glfw"
|
||||
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
||||
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
||||
runtimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"
|
||||
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
||||
}
|
||||
|
@ -5,21 +5,23 @@ import net.minestom.server.event.player.PlayerSpawnEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.MapMeta;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.map.framebuffers.GLFWFramebuffer;
|
||||
import net.minestom.server.map.framebuffers.Graphics2DFramebuffer;
|
||||
import net.minestom.server.network.packet.server.play.MapDataPacket;
|
||||
import net.minestom.server.timer.SchedulerManager;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
public class MapAnimationDemo {
|
||||
|
||||
public static final int MAP_ID = 1;
|
||||
public static final int EGL_MAP_ID = 2;
|
||||
|
||||
private static final Graphics2DFramebuffer framebuffer = new Graphics2DFramebuffer();
|
||||
private static final GLFWFramebuffer glfwFramebuffer = new GLFWFramebuffer();
|
||||
|
||||
public static void init() {
|
||||
SchedulerManager scheduler = MinecraftServer.getSchedulerManager();
|
||||
@ -30,11 +32,31 @@ public class MapAnimationDemo {
|
||||
ItemStack map = new ItemStack(Material.FILLED_MAP, (byte) 1);
|
||||
map.setItemMeta(new MapMeta(MAP_ID));
|
||||
player.getInventory().addItemStack(map);
|
||||
|
||||
ItemStack map2 = new ItemStack(Material.FILLED_MAP, (byte) 1);
|
||||
map2.setItemMeta(new MapMeta(EGL_MAP_ID));
|
||||
player.getInventory().addItemStack(map2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static final Graphics2DFramebuffer framebuffer = new Graphics2DFramebuffer();
|
||||
glfwFramebuffer.setupRenderLoop(16, TimeUnit.MILLISECOND, () -> {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
private static float time = 0f;
|
||||
private static long lastTime = System.currentTimeMillis();
|
||||
@ -68,7 +90,13 @@ public class MapAnimationDemo {
|
||||
|
||||
MapDataPacket mapDataPacket = new MapDataPacket();
|
||||
mapDataPacket.mapId = MAP_ID;
|
||||
framebuffer.preparePacket(mapDataPacket, 32, 32, 64+32, 64+32);
|
||||
framebuffer.preparePacket(mapDataPacket);
|
||||
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(p -> {
|
||||
p.getPlayerConnection().sendPacket(mapDataPacket);
|
||||
});
|
||||
|
||||
mapDataPacket.mapId = EGL_MAP_ID;
|
||||
glfwFramebuffer.preparePacket(mapDataPacket);
|
||||
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(p -> {
|
||||
p.getPlayerConnection().sendPacket(mapDataPacket);
|
||||
});
|
||||
|
@ -0,0 +1,136 @@
|
||||
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 org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
/**
|
||||
* 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)}.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user