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>
<artifactId>lwjgl-glfw</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-nanovg</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-meshoptimizer</artifactId>
@ -195,6 +199,11 @@
<artifactId>lwjgl-glfw</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-nanovg</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<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.model.ClientPlayer;
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.model.Model;
import nl.andrewl.aos_core.model.Team;
@ -39,7 +39,7 @@ public class GameRenderer {
private final ClientConfig.DisplayConfig config;
private ChunkRenderer chunkRenderer;
private GUIRenderer guiRenderer;
private GuiRenderer guiRenderer;
private ModelRenderer modelRenderer;
private final Camera camera;
private final Client client;
@ -129,7 +129,11 @@ public class GameRenderer {
this.chunkRenderer = new ChunkRenderer();
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");
clipTexture = new GUITexture("gui/clip.png");
bulletTexture = new GUITexture("gui/bullet.png");
@ -192,7 +196,7 @@ public class GameRenderer {
}
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());
ClientPlayer myPlayer = client.getMyPlayer();
@ -324,6 +328,7 @@ public class GameRenderer {
-0.90f,
-0.90f
);
guiRenderer.drawNvg(screenWidth, screenHeight);
guiRenderer.end();
glfwSwapBuffers(windowHandle);

View File

@ -1,19 +1,40 @@
package nl.andrewl.aos2_client.render.gui;
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.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.util.HashMap;
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.system.MemoryUtil.memFree;
/**
* 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 vboId;
private final int vertexCount;
@ -25,7 +46,16 @@ public class GUIRenderer {
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();
vboId = glGenBuffers();
FloatBuffer buffer = BufferUtils.createFloatBuffer(8);
@ -87,6 +117,17 @@ public class GUIRenderer {
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() {
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
@ -96,9 +137,13 @@ public class GUIRenderer {
}
public void free() {
memFree(jetbrainsMonoFontData);
nvgDelete(vgId);
for (var tex : textures.values()) tex.free();
glDeleteBuffers(vboId);
glDeleteVertexArrays(vaoId);
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.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public final class 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 {
try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) {
if (in == null) throw new IOException("Could not load classpath resource: " + resource);
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 java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.*;
import java.util.LinkedList;
/**
@ -118,6 +115,8 @@ public class ClientCommunicationHandler {
.withShutdownHook(() -> server.getPlayerManager().deregister(this.player));
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) {
log.warn("An IOException occurred while attempting to establish a connection to a client.", e);
}