Last active
August 3, 2022 10:39
-
-
Save acrylic-style/d64aaa08013be941c1cc8a5bb87228a0 to your computer and use it in GitHub Desktop.
Draw a text using LWJGL (OpenGL 3.2 Core Profile)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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