Skip to content

Instantly share code, notes, and snippets.

@acrylic-style
Last active August 3, 2022 10:39
Show Gist options
  • Save acrylic-style/d64aaa08013be941c1cc8a5bb87228a0 to your computer and use it in GitHub Desktop.
Save acrylic-style/d64aaa08013be941c1cc8a5bb87228a0 to your computer and use it in GitHub Desktop.
Draw a text using LWJGL (OpenGL 3.2 Core Profile)
plugins {
id("java")
}
group = "org.example"
version = "1.0-SNAPSHOT"
val lwjglVersion = "3.3.1"
val lwjglNatives = "natives-windows"
repositories {
mavenCentral()
}
dependencies {
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
implementation("org.lwjgl", "lwjgl")
implementation("org.lwjgl", "lwjgl-assimp")
implementation("org.lwjgl", "lwjgl-glfw")
implementation("org.lwjgl", "lwjgl-openal")
implementation("org.lwjgl", "lwjgl-opengl")
implementation("org.lwjgl", "lwjgl-stb")
implementation("org.joml:joml:1.10.4")
runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
runtimeOnly("org.joml:joml:1.10.4")
}
package com.example.lwjgltest;
import org.joml.Matrix4d;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.stb.STBEasyFont;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL31C.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.*;
public class HelloWorld {
private static final int BACKGROUND_COLOR = 0xEF323D;
private static final int WIDTH = 1024;
private static final int HEIGHT = 768;
private long window;
private int programHUD;
private int uniformModelViewProjectionMatrixHUD;
private final Matrix4d modelViewProjectionMatrixHUD = new Matrix4d();
private int vao;
private int vboText;
public void run() {
init();
loop();
glDeleteVertexArrays(vao);
glDeleteBuffers(vboText);
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
glfwTerminate();
Objects.requireNonNull(glfwSetErrorCallback(null)).free();
}
public void init() {
GLFWErrorCallback.createPrint(System.err).set();
if (!glfwInit()) {
throw new IllegalStateException("Unable to initialize GLFW");
}
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
if (window == NULL) {
throw new RuntimeException("Failed to create the GLFW window");
}
glfwMakeContextCurrent(window);
//noinspection resource
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
glfwSetWindowShouldClose(window, true);
}
});
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
assert vidmode != null;
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
}
glfwMakeContextCurrent(window);
// vsync
glfwSwapInterval(1);
glfwShowWindow(window);
GL.createCapabilities();
modelViewProjectionMatrixHUD
.setOrtho(0.0, WIDTH, HEIGHT, 0.0, -1.0, 1.0)
.translate(4.0, 4.0, 0.0)
.scale(2.0, 2.0, 2.0);
programHUD = buildShaderProgram(
"""
#version 330
uniform mat4 mMVP;
layout(location = 0) in vec2 iPosition;
layout(location = 1) in vec4 iColor;
out vec4 vColor;
void main(void) {
gl_Position = mMVP * vec4(iPosition, 0.0, 1.0);
vColor = iColor;
}
""".stripIndent(),
"""
#version 330
in vec4 vColor;
layout(location = 0) out vec4 oColor;
void main(void) {
oColor = vColor;
}
""".stripIndent()
);
uniformModelViewProjectionMatrixHUD = glGetUniformLocation(programHUD, "mMVP");
glEnable(GL_CULL_FACE);
vao = glGenVertexArrays();
glBindVertexArray(vao);
vboText = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vboText);
glBufferData(GL_ARRAY_BUFFER, WIDTH * 1024, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private void loop() {
glClearColor(((BACKGROUND_COLOR >> 16) & 0xFF) / 255f, ((BACKGROUND_COLOR >> 8) & 0xFF) / 255f, (BACKGROUND_COLOR & 0xFF) / 255f, 0.0f);
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawText(0, 0, "Hello World!", BACKGROUND_COLOR, 0xFFFFFF00); // white
drawText(5, 10, "Line 2", BACKGROUND_COLOR, 0x00FF0000); // green
drawText(10, 20, "Blue", BACKGROUND_COLOR, 0x0000FF00); // blue but with 144/255 alpha
drawText(40, 20, "Blue but", BACKGROUND_COLOR, 0x0000FF90); // blue but with 144/255 alpha
drawText(90, 20, "fading", BACKGROUND_COLOR, 0x0000FFCF); // blue but with 207/255 alpha
drawText(0, 30, "i".repeat(200), BACKGROUND_COLOR, 0xFFFF0000);
drawText(10, 40, "invisible", BACKGROUND_COLOR, 0x00FF00FF);
glfwSwapBuffers(window);
glfwPollEvents();
}
}
private void drawText(int x, int y, String content, int backgroundRgb, int rgba) {
glUseProgram(programHUD);
try (MemoryStack frame = stackPush()) {
glUniformMatrix4fv(uniformModelViewProjectionMatrixHUD, false, modelViewProjectionMatrixHUD.get(frame.mallocFloat(4 * 4)));
}
try (MemoryStack stack = stackPush()) {
ByteBuffer text = stack.malloc(content.length() * 270);
int quads = STBEasyFont.stb_easy_font_print(x, y, content, rgbaToByteBuffer(stack, simulateAlpha(backgroundRgb, rgba)), text);
text.limit(quads * 4 * 16);
ByteBuffer triangles = memAlloc(quads * 6 * 16);
try {
copyQuadsToTriangles(text, triangles);
glBindBuffer(GL_ARRAY_BUFFER, vboText);
glBufferSubData(GL_ARRAY_BUFFER, 0, triangles);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * 4, 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true, 4 * 4, 3 * 4);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * 4, 0);
glVertexAttrib4f(1, 1, 1, 1, 1);
glDrawArrays(GL_TRIANGLES, 0, quads * 6);
} finally {
memFree(triangles);
}
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram(0);
}
public static void main(String[] args) {
new HelloWorld().run();
}
private static int buildShaderProgram(String vsh, String fsh) {
int vshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vshader, vsh);
glCompileShader(vshader);
if (glGetShaderi(vshader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException(glGetShaderInfoLog(vshader));
}
int fshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fshader, fsh);
glCompileShader(fshader);
if (glGetShaderi(fshader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException(glGetShaderInfoLog(fshader));
}
int program = glCreateProgram();
glAttachShader(program, vshader);
glAttachShader(program, fshader);
glLinkProgram(program);
if (glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
throw new IllegalStateException(glGetProgramInfoLog(program));
}
glDeleteShader(fshader);
glDeleteShader(vshader);
return program;
}
private static ByteBuffer copyQuadsToTriangles(ByteBuffer quads, ByteBuffer triangles) {
int vertexCount = quads.remaining() >> 4;
int quadCount = vertexCount >> 2;
if (triangles == null) {
triangles = memAlloc(quadCount * 6 * 16);
}
long s = memAddress(quads);
long t = memAddress(triangles);
for (int i = 0; i < quadCount; i++) {
long quad = s + i * (4 * 16);
long triangle = t + i * (6 * 16);
memCopy(quad + 0 * 16, triangle + 0 * 16, 16);
memCopy(quad + 2 * 16, triangle + 1 * 16, 16);
memCopy(quad + 1 * 16, triangle + 2 * 16, 16);
memCopy(quad + 0 * 16, triangle + 3 * 16, 16);
memCopy(quad + 3 * 16, triangle + 4 * 16, 16);
memCopy(quad + 2 * 16, triangle + 5 * 16, 16);
}
return triangles;
}
private static ByteBuffer rgbaToByteBuffer(MemoryStack stack, int rgba) {
ByteBuffer buffer = stack.malloc(4);
buffer.put((byte) ((rgba >> 24) & 0xFF));
buffer.put((byte) ((rgba >> 16) & 0xFF));
buffer.put((byte) ((rgba >> 8) & 0xFF));
buffer.put((byte) (rgba & 0xFF));
buffer.flip();
return buffer;
}
private static int simulateAlpha(int backgroundColor, int rgba) {
int r = (rgba >> 24) & 0xFF;
int g = (rgba >> 16) & 0xFF;
int b = (rgba >> 8) & 0xFF;
int a = rgba & 0xFF;
int br = (backgroundColor >> 16) & 0xFF;
int bg = (backgroundColor >> 8) & 0xFF;
int bb = backgroundColor & 0xFF;
double nonAlphaPercentage = (0xFF - a) / 255.0;
int newR = (int) (br + (r - br) * nonAlphaPercentage);
int newG = (int) (bg + (g - bg) * nonAlphaPercentage);
int newB = (int) (bb + (b - bb) * nonAlphaPercentage);
return (newR << 24) | (newG << 16) | (newB << 8) | a;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment