Added preliminary font rendering implementation.

This commit is contained in:
Andrew Lalis 2022-07-27 11:28:46 +02:00
parent cf67e4588e
commit 0b5bfca706
8 changed files with 170 additions and 10 deletions

View File

@ -164,6 +164,10 @@
<groupId>org.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId> <artifactId>lwjgl-glfw</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-nanovg</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl-meshoptimizer</artifactId> <artifactId>lwjgl-meshoptimizer</artifactId>
@ -195,6 +199,11 @@
<artifactId>lwjgl-glfw</artifactId> <artifactId>lwjgl-glfw</artifactId>
<classifier>${lwjgl.natives}</classifier> <classifier>${lwjgl.natives}</classifier>
</dependency> </dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-nanovg</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency> <dependency>
<groupId>org.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl-meshoptimizer</artifactId> <artifactId>lwjgl-meshoptimizer</artifactId>

View File

@ -5,7 +5,7 @@ import nl.andrewl.aos2_client.Client;
import nl.andrewl.aos2_client.config.ClientConfig; import nl.andrewl.aos2_client.config.ClientConfig;
import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.model.ClientPlayer;
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer; import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
import nl.andrewl.aos2_client.render.gui.GUIRenderer; import nl.andrewl.aos2_client.render.gui.GuiRenderer;
import nl.andrewl.aos2_client.render.gui.GUITexture; import nl.andrewl.aos2_client.render.gui.GUITexture;
import nl.andrewl.aos2_client.render.model.Model; import nl.andrewl.aos2_client.render.model.Model;
import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.model.Team;
@ -39,7 +39,7 @@ public class GameRenderer {
private final ClientConfig.DisplayConfig config; private final ClientConfig.DisplayConfig config;
private ChunkRenderer chunkRenderer; private ChunkRenderer chunkRenderer;
private GUIRenderer guiRenderer; private GuiRenderer guiRenderer;
private ModelRenderer modelRenderer; private ModelRenderer modelRenderer;
private final Camera camera; private final Camera camera;
private final Client client; private final Client client;
@ -129,7 +129,11 @@ public class GameRenderer {
this.chunkRenderer = new ChunkRenderer(); this.chunkRenderer = new ChunkRenderer();
log.debug("Initialized chunk renderer."); log.debug("Initialized chunk renderer.");
this.guiRenderer = new GUIRenderer(); try {
this.guiRenderer = new GuiRenderer();
} catch (IOException e) {
throw new RuntimeException(e);
}
crosshairTexture = new GUITexture("gui/crosshair.png"); crosshairTexture = new GUITexture("gui/crosshair.png");
clipTexture = new GUITexture("gui/clip.png"); clipTexture = new GUITexture("gui/clip.png");
bulletTexture = new GUITexture("gui/bullet.png"); bulletTexture = new GUITexture("gui/bullet.png");
@ -192,7 +196,7 @@ public class GameRenderer {
} }
public void draw() { public void draw() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw()); chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
ClientPlayer myPlayer = client.getMyPlayer(); ClientPlayer myPlayer = client.getMyPlayer();
@ -324,6 +328,7 @@ public class GameRenderer {
-0.90f, -0.90f,
-0.90f -0.90f
); );
guiRenderer.drawNvg(screenWidth, screenHeight);
guiRenderer.end(); guiRenderer.end();
glfwSwapBuffers(windowHandle); glfwSwapBuffers(windowHandle);

View File

@ -1,19 +1,40 @@
package nl.andrewl.aos2_client.render.gui; package nl.andrewl.aos2_client.render.gui;
import nl.andrewl.aos2_client.render.ShaderProgram; import nl.andrewl.aos2_client.render.ShaderProgram;
import nl.andrewl.aos2_client.util.ResourceUtils;
import nl.andrewl.aos_core.FileUtils;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.nanovg.NVGColor;
import org.lwjgl.nanovg.NVGPaint;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static org.lwjgl.nanovg.NanoVG.*;
import static org.lwjgl.nanovg.NanoVGGL3.*;
import static org.lwjgl.opengl.GL46.*; import static org.lwjgl.opengl.GL46.*;
import static org.lwjgl.system.MemoryUtil.memFree;
/** /**
* Manages rendering of 2D GUI components like cross-hairs, inventory stuff, etc. * Manages rendering of 2D GUI components like cross-hairs, inventory stuff, etc.
*/ */
public class GUIRenderer { public class GuiRenderer {
private final long vgId;
private final int jetbrainsMonoFont;
private final ByteBuffer jetbrainsMonoFontData;
private static final NVGColor colorA = NVGColor.create();
private static final NVGColor colorB = NVGColor.create();
private static final NVGColor colorC = NVGColor.create();
private static final NVGPaint paintA = NVGPaint.create();
private static final NVGPaint paintB = NVGPaint.create();
private static final NVGPaint paintC = NVGPaint.create();
private final int vaoId; private final int vaoId;
private final int vboId; private final int vboId;
private final int vertexCount; private final int vertexCount;
@ -25,7 +46,16 @@ public class GUIRenderer {
private final Map<String, GUITexture> textures = new HashMap<>(); private final Map<String, GUITexture> textures = new HashMap<>();
public GUIRenderer() { public GuiRenderer() throws IOException {
vgId = nvgCreate(NVG_ANTIALIAS);
jetbrainsMonoFontData = FileUtils.readClasspathResourceAsDirectByteBuffer("text/JetBrainsMono-Regular.ttf");
jetbrainsMonoFont = nvgCreateFontMem(
vgId,
"jetbrains-mono",
jetbrainsMonoFontData,
0
);
vaoId = glGenVertexArrays(); vaoId = glGenVertexArrays();
vboId = glGenBuffers(); vboId = glGenBuffers();
FloatBuffer buffer = BufferUtils.createFloatBuffer(8); FloatBuffer buffer = BufferUtils.createFloatBuffer(8);
@ -87,6 +117,17 @@ public class GUIRenderer {
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount); glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
} }
public void drawNvg(float width, float height) {
nvgBeginFrame(vgId, width, height, width / height);
nvgSave(vgId);
nvgFontSize(vgId, 60f);
nvgFontFaceId(vgId, jetbrainsMonoFont);
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
nvgText(vgId, 50, 50, "Hello world!");
nvgRestore(vgId);
nvgEndFrame(vgId);
}
public void end() { public void end() {
glDisable(GL_BLEND); glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
@ -96,9 +137,13 @@ public class GUIRenderer {
} }
public void free() { public void free() {
memFree(jetbrainsMonoFontData);
nvgDelete(vgId);
for (var tex : textures.values()) tex.free(); for (var tex : textures.values()) tex.free();
glDeleteBuffers(vboId); glDeleteBuffers(vboId);
glDeleteVertexArrays(vaoId); glDeleteVertexArrays(vaoId);
shaderProgram.free(); shaderProgram.free();
} }
} }

View File

@ -0,0 +1,17 @@
package nl.andrewl.aos2_client.render.gui;
import org.lwjgl.nanovg.NVGColor;
public class GuiUtils {
public static NVGColor rgba(float r, float g, float b, float a, NVGColor color) {
color.r(r);
color.g(g);
color.b(b);
color.a(a);
return color;
}
public static boolean isBlack(NVGColor color) {
return color.r() == 0 && color.g() == 0 && color.b() == 0 && color.a() == 0;
}
}

View File

@ -0,0 +1,59 @@
package nl.andrewl.aos2_client.util;
import org.lwjgl.BufferUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.lwjgl.BufferUtils.createByteBuffer;
import static org.lwjgl.system.MemoryUtil.memSlice;
public class ResourceUtils {
public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException {
ByteBuffer buffer;
Path path = Paths.get(resource);
if (Files.isReadable(path)) {
try (SeekableByteChannel fc = Files.newByteChannel(path)) {
buffer = createByteBuffer((int)fc.size() + 1);
while (fc.read(buffer) != -1) {
;
}
}
} else {
try (
InputStream source = ResourceUtils.class.getClassLoader().getResourceAsStream(resource);
ReadableByteChannel rbc = Channels.newChannel(source)
) {
buffer = createByteBuffer(bufferSize);
while (true) {
int bytes = rbc.read(buffer);
if (bytes == -1) {
break;
}
if (buffer.remaining() == 0) {
buffer = resizeBuffer(buffer, buffer.capacity() * 3 / 2); // 50%
}
}
}
}
buffer.flip();
return memSlice(buffer);
}
private static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity) {
ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity);
buffer.flip();
newBuffer.put(buffer);
return newBuffer;
}
}

View File

@ -2,14 +2,40 @@ package nl.andrewl.aos_core;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public final class FileUtils { public final class FileUtils {
private FileUtils() {} private FileUtils() {}
/**
* Reads a classpath resource as a string.
* @param resource The resource to read.
* @return The string contents of the resource.
* @throws IOException If the resource can't be found or read.
*/
public static String readClasspathFile(String resource) throws IOException { public static String readClasspathFile(String resource) throws IOException {
try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) { try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) {
if (in == null) throw new IOException("Could not load classpath resource: " + resource); if (in == null) throw new IOException("Could not load classpath resource: " + resource);
return new String(in.readAllBytes()); return new String(in.readAllBytes());
} }
} }
/**
* Reads a classpath resource into a directly-allocated byte buffer that
* must be deallocated manually.
* @param resource The resource to read.
* @return The byte buffer containing the resource.
* @throws IOException If the resource can't be found or read.
*/
public static ByteBuffer readClasspathResourceAsDirectByteBuffer(String resource) throws IOException {
try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) {
if (in == null) throw new IOException("Could not load classpath resource: " + resource);
byte[] bytes = in.readAllBytes();
ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length).order(ByteOrder.nativeOrder());
buffer.put(bytes);
buffer.flip();
return buffer;
}
}
} }

View File

@ -20,10 +20,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.*;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.util.LinkedList; import java.util.LinkedList;
/** /**
@ -118,6 +115,8 @@ public class ClientCommunicationHandler {
.withShutdownHook(() -> server.getPlayerManager().deregister(this.player)); .withShutdownHook(() -> server.getPlayerManager().deregister(this.player));
new Thread(tcpReceiver).start(); new Thread(tcpReceiver).start();
} }
} catch (SocketTimeoutException e) {
// Ignore this one, since this will happen if the client doesn't send data properly.
} catch (IOException e) { } catch (IOException e) {
log.warn("An IOException occurred while attempting to establish a connection to a client.", e); log.warn("An IOException occurred while attempting to establish a connection to a client.", e);
} }