├── .gitignore ├── README.md ├── assets ├── fontShader.glsl └── sdfShader.glsl ├── build.gradle ├── gradlew ├── gradlew.bat ├── libs ├── freetype-jni.jar └── natives │ └── windows │ └── freetype-jni-64.dll ├── settings.gradle └── src └── main └── java ├── Batch.java ├── Fonts ├── CFont.java └── CharInfo.java ├── Main.java ├── Sdf.java ├── Shader.java └── Window.java /.gitignore: -------------------------------------------------------------------------------- 1 | build/** 2 | .gradle/** 3 | .idea/** 4 | gradle/** 5 | tmp.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Font Rendering Series 2 | 3 | This repository contains all the code that was created for the series at https://www.youtube.com/playlist?list=PLtrSb4XxIVbrxVWnF3KnVACeJU1mo1B7L -------------------------------------------------------------------------------- /assets/fontShader.glsl: -------------------------------------------------------------------------------- 1 | #type vertex 2 | #version 330 core 3 | layout(location=0) in vec2 aPos; 4 | layout(location=1) in vec3 aColor; 5 | layout(location=2) in vec2 aTexCoords; 6 | 7 | out vec2 fTexCoords; 8 | out vec3 fColor; 9 | 10 | uniform mat4 uProjection; 11 | 12 | void main() 13 | { 14 | fTexCoords = aTexCoords; 15 | fColor = aColor; 16 | gl_Position = uProjection * vec4(aPos, -5, 1); 17 | } 18 | 19 | #type fragment 20 | #version 330 core 21 | 22 | in vec2 fTexCoords; 23 | in vec3 fColor; 24 | 25 | uniform sampler2D uFontTexture; 26 | 27 | out vec4 color; 28 | 29 | void main() 30 | { 31 | float c = texture(uFontTexture, fTexCoords).r; 32 | color = vec4(1, 1, 1, c) * vec4(fColor, 1); 33 | } -------------------------------------------------------------------------------- /assets/sdfShader.glsl: -------------------------------------------------------------------------------- 1 | #type vertex 2 | #version 330 core 3 | layout(location=0) in vec2 aPos; 4 | layout(location=1) in vec3 aColor; 5 | layout(location=2) in vec2 aTexCoords; 6 | 7 | out vec2 fTexCoords; 8 | out vec3 fColor; 9 | 10 | uniform mat4 uProjection; 11 | 12 | void main() 13 | { 14 | fTexCoords = aTexCoords; 15 | fColor = aColor; 16 | gl_Position = uProjection * vec4(aPos, -5, 1); 17 | } 18 | 19 | #type fragment 20 | #version 330 core 21 | 22 | in vec2 fTexCoords; 23 | in vec3 fColor; 24 | 25 | uniform sampler2D uFontTexture; 26 | 27 | out vec4 color; 28 | 29 | void main() 30 | { 31 | float upperPointCutoff = 0.5; 32 | float midpointCutoff = 0.49; 33 | float c = texture(uFontTexture, fTexCoords).r; 34 | if (c > upperPointCutoff) 35 | color = vec4(1, 1, 1, 1) * vec4(fColor, 1); 36 | else if (c > midpointCutoff) 37 | { 38 | // [0.45, 5] -> [0, 1] 39 | float smoothC = smoothstep(midpointCutoff, upperPointCutoff, c); 40 | color = vec4(1, 1, 1, smoothC) * vec4(fColor, 1); 41 | } 42 | else 43 | color = vec4(0, 0, 0, 0); 44 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | version '1.0-SNAPSHOT' 6 | 7 | sourceCompatibility = 1.8 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | project.ext.lwjglVersion = "3.2.3" 14 | project.ext.jomlVersion = "1.10.0" 15 | project.ext.lwjglNatives = "natives-windows" 16 | 17 | dependencies { 18 | // FreeType JAR 19 | implementation fileTree('libs') { include '*.jar' } 20 | 21 | implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion") 22 | 23 | implementation "org.lwjgl:lwjgl" 24 | implementation "org.lwjgl:lwjgl-assimp" 25 | implementation "org.lwjgl:lwjgl-glfw" 26 | implementation "org.lwjgl:lwjgl-openal" 27 | implementation "org.lwjgl:lwjgl-opengl" 28 | implementation "org.lwjgl:lwjgl-stb" 29 | runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives" 30 | runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives" 31 | runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" 32 | runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives" 33 | runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" 34 | runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" 35 | implementation "org.joml:joml:${jomlVersion}" 36 | } 37 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /libs/freetype-jni.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingminecraft/FontRendering/741f3c9f1d95f8878ed2c3b830922df240eecfc7/libs/freetype-jni.jar -------------------------------------------------------------------------------- /libs/natives/windows/freetype-jni-64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingminecraft/FontRendering/741f3c9f1d95f8878ed2c3b830922df240eecfc7/libs/natives/windows/freetype-jni-64.dll -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'FontRendering' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/Batch.java: -------------------------------------------------------------------------------- 1 | import Fonts.CFont; 2 | import Fonts.CharInfo; 3 | import org.joml.Matrix4f; 4 | import org.lwjgl.opengl.GL15; 5 | 6 | import static org.lwjgl.opengl.GL11.GL_STACK_OVERFLOW; 7 | import static org.lwjgl.opengl.GL15.*; 8 | import static org.lwjgl.opengl.GL15C.GL_ELEMENT_ARRAY_BUFFER; 9 | import static org.lwjgl.opengl.GL15C.glGenBuffers; 10 | import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray; 11 | import static org.lwjgl.opengl.GL20.glVertexAttribPointer; 12 | import static org.lwjgl.opengl.GL30.glBindVertexArray; 13 | import static org.lwjgl.opengl.GL30.glGenVertexArrays; 14 | import static org.lwjgl.opengl.GL31.GL_TEXTURE_BUFFER; 15 | 16 | public class Batch { 17 | // private float[] vertices = { 18 | // // x, y, r, g, b ux, uy 19 | // 0.5f, 0.5f, 1.0f, 0.2f, 0.11f, 1.0f, 0.0f, 20 | // 0.5f, -0.5f, 1.0f, 0.2f, 0.11f, 1.0f, 1.0f, 21 | // -0.5f, -0.5f, 1.0f, 0.2f, 0.11f, 0.0f, 1.0f, 22 | // -0.5f, 0.5f, 1.0f, 0.2f, 0.11f, 0.0f, 0.0f 23 | // }; 24 | 25 | private int[] indices = { 26 | 0, 1, 3, 27 | 1, 2, 3 28 | }; 29 | 30 | // 25 quads 31 | public static int BATCH_SIZE = 100; 32 | public static int VERTEX_SIZE = 7; 33 | public float[] vertices = new float[BATCH_SIZE * VERTEX_SIZE]; 34 | public int size = 0; 35 | private Matrix4f projection = new Matrix4f(); 36 | 37 | public int vao; 38 | public int vbo; 39 | public Shader shader; 40 | public Shader sdfShader; 41 | public CFont font; 42 | 43 | public void generateEbo() { 44 | int elementSize = BATCH_SIZE * 3; 45 | int[] elementBuffer = new int[elementSize]; 46 | 47 | for (int i=0; i < elementSize; i++) { 48 | elementBuffer[i] = indices[(i % 6)] + ((i / 6) * 4); 49 | } 50 | 51 | int ebo = glGenBuffers(); 52 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); 53 | glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, elementBuffer, GL_STATIC_DRAW); 54 | } 55 | 56 | public void initBatch() { 57 | projection.identity(); 58 | projection.ortho(0, 800, 0, 600, 1f, 100f); 59 | 60 | vao = glGenVertexArrays(); 61 | glBindVertexArray(vao); 62 | 63 | vbo = glGenBuffers(); 64 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 65 | glBufferData(GL_ARRAY_BUFFER, Float.BYTES * VERTEX_SIZE * BATCH_SIZE, GL_DYNAMIC_DRAW); 66 | 67 | generateEbo(); 68 | 69 | int stride = 7 * Float.BYTES; 70 | glVertexAttribPointer(0, 2, GL_FLOAT, false, stride, 0); 71 | glEnableVertexAttribArray(0); 72 | 73 | glVertexAttribPointer(1, 3, GL_FLOAT, false, stride, 2 * Float.BYTES); 74 | glEnableVertexAttribArray(1); 75 | 76 | glVertexAttribPointer(2, 2, GL_FLOAT, false, stride, 5 * Float.BYTES); 77 | glEnableVertexAttribArray(2); 78 | } 79 | 80 | public void flushBatch() { 81 | // Clear the buffer on the GPU, and then upload the CPU contents, and then draw 82 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 83 | glBufferData(GL_ARRAY_BUFFER, Float.BYTES * VERTEX_SIZE * BATCH_SIZE, GL_DYNAMIC_DRAW); 84 | glBufferSubData(GL_ARRAY_BUFFER, 0, vertices); 85 | 86 | // Draw the buffer that we just uploaded 87 | shader.use(); 88 | //sdfShader.use(); 89 | glActiveTexture(GL_TEXTURE0); 90 | glBindTexture(GL_TEXTURE_BUFFER, font.textureId); 91 | //glBindTexture(GL_TEXTURE_BUFFER, Sdf.textureId); 92 | sdfShader.uploadTexture("uFontTexture", 0); 93 | sdfShader.uploadMat4f("uProjection", projection); 94 | 95 | glBindVertexArray(vao); 96 | 97 | glDrawElements(GL_TRIANGLES, size * 6, GL_UNSIGNED_INT, 0); 98 | 99 | // Reset batch for use on next draw call 100 | size = 0; 101 | } 102 | 103 | public void addCharacter(float x, float y, float scale, CharInfo charInfo, int rgb) { 104 | // If we have no more room in the current batch, flush it and start with a fresh batch 105 | if (size >= BATCH_SIZE - 4) { 106 | flushBatch(); 107 | } 108 | 109 | float r = (float)((rgb >> 16) & 0xFF) / 255.0f; 110 | float g = (float)((rgb >> 8) & 0xFF) / 255.0f; 111 | float b = (float)((rgb >> 0) & 0xFF) / 255.0f; 112 | 113 | float x0 = x; 114 | float y0 = y; 115 | float x1 = x + scale * charInfo.width; 116 | float y1 = y + scale * charInfo.height; 117 | 118 | float ux0 = charInfo.textureCoordinates[0].x; float uy0 = charInfo.textureCoordinates[0].y; 119 | float ux1 = charInfo.textureCoordinates[1].x; float uy1 = charInfo.textureCoordinates[1].y; 120 | 121 | int index = size * 7; 122 | vertices[index] = x1; vertices[index + 1] = y0; 123 | vertices[index + 2] = r; vertices[index + 3] = g; vertices[index + 4] = b; 124 | vertices[index + 5] = ux1; vertices[index + 6] = uy0; 125 | 126 | index += 7; 127 | vertices[index] = x1; vertices[index + 1] = y1; 128 | vertices[index + 2] = r; vertices[index + 3] = g; vertices[index + 4] = b; 129 | vertices[index + 5] = ux1; vertices[index + 6] = uy1; 130 | 131 | index += 7; 132 | vertices[index] = x0; vertices[index + 1] = y1; 133 | vertices[index + 2] = r; vertices[index + 3] = g; vertices[index + 4] = b; 134 | vertices[index + 5] = ux0; vertices[index + 6] = uy1; 135 | 136 | index += 7; 137 | vertices[index] = x0; vertices[index + 1] = y0; 138 | vertices[index + 2] = r; vertices[index + 3] = g; vertices[index + 4] = b; 139 | vertices[index + 5] = ux0; vertices[index + 6] = uy0; 140 | 141 | size += 4; 142 | } 143 | 144 | public void addText(String text, int x, int y, float scale, int rgb) { 145 | for (int i=0; i < text.length(); i++) { 146 | char c = text.charAt(i); 147 | 148 | CharInfo charInfo = font.getCharacter(c); 149 | if (charInfo.width == 0) { 150 | System.out.println("Unknown character " + c); 151 | continue; 152 | } 153 | 154 | float xPos = x; 155 | float yPos = y; 156 | addCharacter(xPos, yPos, scale, charInfo, rgb); 157 | x += charInfo.width * scale; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/Fonts/CFont.java: -------------------------------------------------------------------------------- 1 | package Fonts; 2 | 3 | import org.lwjgl.BufferUtils; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.io.File; 9 | import java.nio.ByteBuffer; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.lwjgl.opengl.GL11.*; 14 | 15 | public class CFont { 16 | private String filepath; 17 | private int fontSize; 18 | 19 | private int width, height, lineHeight; 20 | private Map characterMap; 21 | 22 | public int textureId; 23 | 24 | public CFont(String filepath, int fontSize) { 25 | this.filepath = filepath; 26 | this.fontSize = fontSize; 27 | this.characterMap = new HashMap<>(); 28 | generateBitmap(); 29 | } 30 | 31 | public CharInfo getCharacter(int codepoint) { 32 | return characterMap.getOrDefault(codepoint, new CharInfo(0, 0, 0, 0)); 33 | } 34 | 35 | private Font registerFont(String fontFile) { 36 | try { 37 | GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 38 | Font font = Font.createFont(Font.TRUETYPE_FONT, new File(filepath)); 39 | ge.registerFont(font); 40 | return font; 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | return null; 45 | } 46 | 47 | public void generateBitmap() { 48 | Font font = registerFont(filepath); 49 | font = new Font(font.getName(), Font.PLAIN, fontSize); 50 | 51 | // Create fake image to get font information 52 | BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 53 | Graphics2D g2d = img.createGraphics(); 54 | g2d.setFont(font); 55 | FontMetrics fontMetrics = g2d.getFontMetrics(); 56 | 57 | int estimatedWidth = (int)Math.sqrt(font.getNumGlyphs()) * font.getSize() + 1; 58 | width = 0; 59 | height = fontMetrics.getHeight(); 60 | lineHeight = fontMetrics.getHeight(); 61 | int x = 0; 62 | int y = (int)(fontMetrics.getHeight() * 1.4f); 63 | 64 | for (int i=0; i < font.getNumGlyphs(); i++) { 65 | if (font.canDisplay(i)) { 66 | // Get the sizes for each codepoint glyph, and update the actual image width and height 67 | CharInfo charInfo = new CharInfo(x, y, fontMetrics.charWidth(i), fontMetrics.getHeight()); 68 | characterMap.put(i, charInfo); 69 | width = Math.max(x + fontMetrics.charWidth(i), width); 70 | 71 | x += charInfo.width; 72 | if (x > estimatedWidth) { 73 | x = 0; 74 | y += fontMetrics.getHeight() * 1.4f; 75 | height += fontMetrics.getHeight() * 1.4f; 76 | } 77 | } 78 | } 79 | height += fontMetrics.getHeight() * 1.4f; 80 | g2d.dispose(); 81 | 82 | // Create the real texture 83 | img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 84 | g2d = img.createGraphics(); 85 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 86 | g2d.setFont(font); 87 | g2d.setColor(Color.WHITE); 88 | for (int i=0; i < font.getNumGlyphs(); i++) { 89 | if (font.canDisplay(i)) { 90 | CharInfo info = characterMap.get(i); 91 | info.calculateTextureCoordinates(width, height); 92 | g2d.drawString("" + (char)i, info.sourceX, info.sourceY); 93 | } 94 | } 95 | // File outputfile = new File("image.png"); 96 | // try { 97 | // ImageIO.write(img, "png", outputfile); 98 | // } catch (Exception e) { 99 | // 100 | // } 101 | g2d.dispose(); 102 | 103 | uploadTexture(img); 104 | } 105 | 106 | private void uploadTexture(BufferedImage image) { 107 | // Taken from https://stackoverflow.com/questions/10801016/lwjgl-textures-and-strings 108 | 109 | int[] pixels = new int[image.getHeight() * image.getWidth()]; 110 | image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); 111 | 112 | ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4); 113 | for (int y=0; y < image.getHeight(); y++) { 114 | for (int x=0; x < image.getWidth(); x++) { 115 | int pixel = pixels[y * image.getWidth() + x]; 116 | byte alphaComponent = (byte)((pixel >> 24) & 0xFF); 117 | buffer.put(alphaComponent); 118 | buffer.put(alphaComponent); 119 | buffer.put(alphaComponent); 120 | buffer.put(alphaComponent); 121 | } 122 | } 123 | buffer.flip(); 124 | 125 | textureId = glGenTextures(); 126 | 127 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 128 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 129 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 130 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 131 | 132 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 133 | 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 134 | buffer.clear(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/Fonts/CharInfo.java: -------------------------------------------------------------------------------- 1 | package Fonts; 2 | 3 | import org.joml.Vector2f; 4 | 5 | public class CharInfo { 6 | public int sourceX; 7 | public int sourceY; 8 | public int width; 9 | public int height; 10 | 11 | public Vector2f[] textureCoordinates = new Vector2f[4]; 12 | 13 | public CharInfo(int sourceX, int sourceY, int width, int height) { 14 | this.sourceX = sourceX; 15 | this.sourceY = sourceY; 16 | this.width = width; 17 | this.height = height; 18 | } 19 | 20 | public void calculateTextureCoordinates(int fontWidth, int fontHeight) { 21 | float x0 = (float)sourceX / (float)fontWidth; 22 | float x1 = (float)(sourceX + width) / (float)fontWidth; 23 | float y0 = (float)(sourceY - height) / (float)fontHeight; 24 | float y1 = (float)(sourceY) / (float)fontHeight; 25 | 26 | textureCoordinates[0] = new Vector2f(x0, y1); 27 | textureCoordinates[1] = new Vector2f(x1, y0); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | Window window = new Window(); 4 | window.run(); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/Sdf.java: -------------------------------------------------------------------------------- 1 | import com.mlomb.freetypejni.Face; 2 | import com.mlomb.freetypejni.FreeType; 3 | import com.mlomb.freetypejni.Library; 4 | import org.lwjgl.BufferUtils; 5 | 6 | import javax.imageio.ImageIO; 7 | import java.awt.image.BufferedImage; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | 12 | import static com.mlomb.freetypejni.FreeType.*; 13 | import static com.mlomb.freetypejni.FreeTypeConstants.FT_LOAD_RENDER; 14 | import static org.lwjgl.opengl.GL11.*; 15 | import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; 16 | 17 | public class Sdf { 18 | public static int textureId = -1; 19 | 20 | private static float mapRange(float val, float in_min, float in_max, 21 | float out_min, float out_max) { 22 | return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 23 | } 24 | 25 | private static int getPixel(int x, int y, byte[] bitmap, int width, int height) { 26 | if (x >= 0 && x < width && y >= 0 && y < height) { 27 | return (bitmap[x + y * width] & 0xFF) == 0 ? 0 : 1; 28 | } 29 | return 0; 30 | } 31 | 32 | private static float findNearestPixel(int pixelX, int pixelY, byte[] bitmap, 33 | int width, int height, int spread) { 34 | int state = getPixel(pixelX, pixelY, bitmap, width, height); 35 | int minX = pixelX - spread; 36 | int maxX = pixelX + spread; 37 | int minY = pixelY - spread; 38 | int maxY = pixelY + spread; 39 | 40 | float minDistance = spread * spread; 41 | for (int y = minY; y < maxY; y++) { 42 | for (int x = minX; x < maxX; x++) { 43 | int pixelState = getPixel(x, y, bitmap, width, height); 44 | float dxSquared = (x - pixelX) * (x - pixelX); 45 | float dySquared = (y - pixelY) * (y - pixelY); 46 | float distanceSquared = dxSquared + dySquared; 47 | if (pixelState != state) { 48 | minDistance = Math.min(distanceSquared, minDistance); 49 | } 50 | } 51 | } 52 | 53 | minDistance = (float)Math.sqrt(minDistance); 54 | float output = (minDistance - 0.5f) / (spread - 0.5f); 55 | output *= state == 0 ? -1 : 1; 56 | 57 | // Map from [-1, 1] to [0, 1] 58 | return (output + 1) * 0.5f; 59 | } 60 | 61 | public static void generateCodepointBitmap(int codepoint, String fontFile, int fontSize) { 62 | int padding = 15; 63 | int upscaleResolution = 1080; 64 | int spread = upscaleResolution / 2; 65 | 66 | Library library = FreeType.newLibrary(); 67 | assert(library != null); 68 | 69 | Face font = library.newFace(fontFile, 0); 70 | FT_Set_Pixel_Sizes(font.getPointer(), 0, upscaleResolution); 71 | if (FT_Load_Char(font.getPointer(), (char)codepoint, FT_LOAD_RENDER)) { 72 | System.out.println("FreeType could not generate character."); 73 | free(library, font); 74 | return; 75 | } 76 | 77 | int glyphWidth = font.getGlyphSlot().getBitmap().getWidth(); 78 | int glyphHeight = font.getGlyphSlot().getBitmap().getRows(); 79 | byte[] glyphBitmap = new byte[glyphHeight * glyphWidth]; 80 | font.getGlyphSlot().getBitmap().getBuffer() 81 | .get(glyphBitmap, 0, glyphWidth * glyphHeight); 82 | 83 | System.out.println("Glyph width " + glyphWidth); 84 | System.out.println("Glyph Height " + glyphHeight); 85 | 86 | float widthScale = (float)glyphWidth / (float)upscaleResolution; 87 | float heightScale = (float)glyphHeight / (float)upscaleResolution; 88 | int characterWidth = (int)((float)fontSize * widthScale); 89 | int characterHeight = (int)((float)fontSize * heightScale); 90 | int bitmapWidth = characterWidth + padding * 2; 91 | int bitmapHeight = characterHeight + padding * 2; 92 | float bitmapScaleX = (float)glyphWidth / (float)characterWidth; 93 | float bitmapScaleY = (float)glyphHeight / (float)characterHeight; 94 | int[] bitmap = new int[bitmapWidth * bitmapHeight]; 95 | for (int y = -padding; y < characterHeight + padding; y++) { 96 | for (int x = -padding; x < characterWidth + padding; x++) { 97 | int pixelX = (int)mapRange(x, -padding, characterWidth + padding, 98 | -padding * bitmapScaleX, (characterWidth + padding) * bitmapScaleX); 99 | int pixelY = (int)mapRange(y, -padding, characterHeight + padding, 100 | -padding * bitmapScaleY, (characterHeight + padding) * bitmapScaleY); 101 | float val = findNearestPixel(pixelX, pixelY, glyphBitmap, 102 | glyphWidth, glyphHeight, spread); 103 | bitmap[(x + padding) + ((y + padding) * bitmapWidth)] = (int)(val * 255.0f); 104 | } 105 | } 106 | 107 | BufferedImage testImage = new BufferedImage(bitmapWidth, bitmapHeight, BufferedImage.TYPE_INT_ARGB); 108 | int x = 0; 109 | int y = 0; 110 | for (int byteAsInt : bitmap) { 111 | int argb = (255 << 24) | (byteAsInt << 16) | (byteAsInt << 8) | byteAsInt; 112 | testImage.setRGB(x, y, argb); 113 | x++; 114 | if (x >= bitmapWidth) { 115 | x = 0; 116 | y++; 117 | } 118 | if (y >= bitmapHeight) { 119 | break; 120 | } 121 | } 122 | 123 | try { 124 | File output = new File("test.png"); 125 | ImageIO.write(testImage, "png", output); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | 130 | uploadTexture(testImage); 131 | free(library, font); 132 | } 133 | 134 | private static void free(Library library, Face font) { 135 | FT_Done_Face(font.getPointer()); 136 | FT_Done_FreeType(library.getPointer()); 137 | } 138 | 139 | private static void uploadTexture(BufferedImage image) { 140 | // Taken from https://stackoverflow.com/questions/10801016/lwjgl-textures-and-strings 141 | 142 | int[] pixels = new int[image.getHeight() * image.getWidth()]; 143 | image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); 144 | 145 | ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4); 146 | for (int y=0; y < image.getHeight(); y++) { 147 | for (int x=0; x < image.getWidth(); x++) { 148 | int pixel = pixels[y * image.getWidth() + x]; 149 | byte r = (byte)((pixel >> 16) & 0xFF); 150 | buffer.put(r); 151 | buffer.put(r); 152 | buffer.put(r); 153 | buffer.put(r); 154 | } 155 | } 156 | buffer.flip(); 157 | 158 | textureId = glGenTextures(); 159 | 160 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 161 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 162 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 163 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 164 | 165 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 166 | 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 167 | buffer.clear(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/Shader.java: -------------------------------------------------------------------------------- 1 | import org.joml.*; 2 | import org.lwjgl.BufferUtils; 3 | 4 | import javax.print.DocFlavor; 5 | import java.io.IOException; 6 | import java.nio.FloatBuffer; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | 10 | import static org.lwjgl.opengl.GL11.GL_FALSE; 11 | import static org.lwjgl.opengl.GL20.*; 12 | import static org.lwjgl.opengl.GL20.glGetShaderInfoLog; 13 | 14 | public class Shader { 15 | 16 | private int shaderProgramID; 17 | private boolean beingUsed = false; 18 | 19 | private String vertexSource; 20 | private String fragmentSource; 21 | private String filepath; 22 | 23 | public Shader(String filepath) { 24 | this.filepath = filepath; 25 | try { 26 | String source = new String(Files.readAllBytes(Paths.get(filepath))); 27 | String[] splitString = source.split("(#type)( )+([a-zA-Z]+)"); 28 | 29 | // Find the first pattern after #type 'pattern' 30 | int index = source.indexOf("#type") + 6; 31 | int eol = source.indexOf("\r\n", index); 32 | String firstPattern = source.substring(index, eol).trim(); 33 | 34 | // Find the second pattern after #type 'pattern' 35 | index = source.indexOf("#type", eol) + 6; 36 | eol = source.indexOf("\r\n", index); 37 | String secondPattern = source.substring(index, eol).trim(); 38 | 39 | if (firstPattern.equals("vertex")) { 40 | vertexSource = splitString[1]; 41 | } else if (firstPattern.equals("fragment")) { 42 | fragmentSource = splitString[1]; 43 | } else { 44 | throw new IOException("Unexpected token '" + firstPattern + "'"); 45 | } 46 | 47 | if (secondPattern.equals("vertex")) { 48 | vertexSource = splitString[2]; 49 | } else if (secondPattern.equals("fragment")) { 50 | fragmentSource = splitString[2]; 51 | } else { 52 | throw new IOException("Unexpected token '" + secondPattern + "'"); 53 | } 54 | } catch(IOException e) { 55 | e.printStackTrace(); 56 | assert false : "Error: Could not open file for shader: '" + filepath + "'"; 57 | } 58 | 59 | compile(); 60 | } 61 | 62 | public void compile() { 63 | // ============================================================ 64 | // Compile and link shaders 65 | // ============================================================ 66 | int vertexID, fragmentID; 67 | 68 | // First load and compile the vertex shader 69 | vertexID = glCreateShader(GL_VERTEX_SHADER); 70 | // Pass the shader source to the GPU 71 | glShaderSource(vertexID, vertexSource); 72 | glCompileShader(vertexID); 73 | 74 | // Check for errors in compilation 75 | int success = glGetShaderi(vertexID, GL_COMPILE_STATUS); 76 | if (success == GL_FALSE) { 77 | int len = glGetShaderi(vertexID, GL_INFO_LOG_LENGTH); 78 | System.out.println("ERROR: '" + filepath + "'\n\tVertex shader compilation failed."); 79 | System.out.println(glGetShaderInfoLog(vertexID, len)); 80 | assert false : ""; 81 | } 82 | 83 | // First load and compile the vertex shader 84 | fragmentID = glCreateShader(GL_FRAGMENT_SHADER); 85 | // Pass the shader source to the GPU 86 | glShaderSource(fragmentID, fragmentSource); 87 | glCompileShader(fragmentID); 88 | 89 | // Check for errors in compilation 90 | success = glGetShaderi(fragmentID, GL_COMPILE_STATUS); 91 | if (success == GL_FALSE) { 92 | int len = glGetShaderi(fragmentID, GL_INFO_LOG_LENGTH); 93 | System.out.println("ERROR: '" + filepath + "'\n\tFragment shader compilation failed."); 94 | System.out.println(glGetShaderInfoLog(fragmentID, len)); 95 | assert false : ""; 96 | } 97 | 98 | // Link shaders and check for errors 99 | shaderProgramID = glCreateProgram(); 100 | glAttachShader(shaderProgramID, vertexID); 101 | glAttachShader(shaderProgramID, fragmentID); 102 | glLinkProgram(shaderProgramID); 103 | 104 | // Check for linking errors 105 | success = glGetProgrami(shaderProgramID, GL_LINK_STATUS); 106 | if (success == GL_FALSE) { 107 | int len = glGetProgrami(shaderProgramID, GL_INFO_LOG_LENGTH); 108 | System.out.println("ERROR: '" + filepath + "'\n\tLinking of shaders failed."); 109 | System.out.println(glGetProgramInfoLog(shaderProgramID, len)); 110 | assert false : ""; 111 | } 112 | } 113 | 114 | public void use() { 115 | if (!beingUsed) { 116 | // Bind shader program 117 | glUseProgram(shaderProgramID); 118 | beingUsed = true; 119 | } 120 | } 121 | 122 | public void detach() { 123 | glUseProgram(0); 124 | beingUsed = false; 125 | } 126 | 127 | public void uploadMat4f(String varName, Matrix4f mat4) { 128 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 129 | use(); 130 | FloatBuffer matBuffer = BufferUtils.createFloatBuffer(16); 131 | mat4.get(matBuffer); 132 | glUniformMatrix4fv(varLocation, false, matBuffer); 133 | } 134 | 135 | public void uploadMat3f(String varName, Matrix3f mat3) { 136 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 137 | use(); 138 | FloatBuffer matBuffer = BufferUtils.createFloatBuffer(9); 139 | mat3.get(matBuffer); 140 | glUniformMatrix3fv(varLocation, false, matBuffer); 141 | } 142 | 143 | public void uploadVec4f(String varName, Vector4f vec) { 144 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 145 | use(); 146 | glUniform4f(varLocation, vec.x, vec.y, vec.z, vec.w); 147 | } 148 | 149 | public void uploadVec3f(String varName, Vector3f vec) { 150 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 151 | use(); 152 | glUniform3f(varLocation, vec.x, vec.y, vec.z); 153 | } 154 | 155 | public void uploadVec2f(String varName, Vector2f vec) { 156 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 157 | use(); 158 | glUniform2f(varLocation, vec.x, vec.y); 159 | } 160 | 161 | public void uploadFloat(String varName, float val) { 162 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 163 | use(); 164 | glUniform1f(varLocation, val); 165 | } 166 | 167 | public void uploadInt(String varName, int val) { 168 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 169 | use(); 170 | glUniform1i(varLocation, val); 171 | } 172 | 173 | public void uploadTexture(String varName, int slot) { 174 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 175 | use(); 176 | glUniform1i(varLocation, slot); 177 | } 178 | 179 | public void uploadIntArray(String varName, int[] array) { 180 | int varLocation = glGetUniformLocation(shaderProgramID, varName); 181 | use(); 182 | glUniform1iv(varLocation, array); 183 | } 184 | } -------------------------------------------------------------------------------- /src/main/java/Window.java: -------------------------------------------------------------------------------- 1 | import Fonts.CFont; 2 | import Fonts.CharInfo; 3 | import org.joml.Vector2f; 4 | import org.lwjgl.opengl.GL; 5 | 6 | import java.util.Random; 7 | 8 | import static org.lwjgl.glfw.GLFW.*; 9 | import static org.lwjgl.opengl.GL11.glClearColor; 10 | import static org.lwjgl.opengl.GL11C.GL_COLOR_BUFFER_BIT; 11 | import static org.lwjgl.opengl.GL11C.glClear; 12 | import static org.lwjgl.opengl.GL15.*; 13 | import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray; 14 | import static org.lwjgl.opengl.GL20.glVertexAttribPointer; 15 | import static org.lwjgl.opengl.GL30.glBindVertexArray; 16 | import static org.lwjgl.opengl.GL30.glGenVertexArrays; 17 | import static org.lwjgl.opengl.GL31.GL_TEXTURE_BUFFER; 18 | import static org.lwjgl.system.MemoryUtil.NULL; 19 | 20 | public class Window { 21 | 22 | private long window; 23 | private CFont font; 24 | 25 | public Window() { 26 | init(); 27 | font = new CFont("C:/Windows/Fonts/ALGER.ttf", 64); 28 | } 29 | 30 | private void init() { 31 | glfwInit(); 32 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 33 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 34 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 35 | 36 | window = glfwCreateWindow(1920, 1080, "Font Rendering", NULL, NULL); 37 | if (window == NULL) { 38 | System.out.println("Could not create window."); 39 | glfwTerminate(); 40 | return; 41 | } 42 | 43 | glfwMakeContextCurrent(window); 44 | glfwSwapInterval(1); 45 | glfwShowWindow(window); 46 | 47 | // Initialize gl functions for windows using GLAD 48 | GL.createCapabilities(); 49 | } 50 | 51 | public void run() { 52 | // Sdf.generateCodepointBitmap( 53 | // 'G', "C:/Windows/Fonts/arial.ttf", 64); 54 | 55 | Shader fontShader = new Shader("assets/fontShader.glsl"); 56 | Shader sdfShader = new Shader("assets/sdfShader.glsl"); 57 | Batch batch = new Batch(); 58 | batch.shader = fontShader; 59 | batch.sdfShader = sdfShader; 60 | batch.font = font; 61 | batch.initBatch(); 62 | 63 | glEnable(GL_BLEND); 64 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 65 | 66 | CharInfo oneQuad = new CharInfo(0, 0, 1, 1); 67 | oneQuad.calculateTextureCoordinates(1, 1); 68 | 69 | Random random = new Random(); 70 | while (!glfwWindowShouldClose(window)) { 71 | glClear(GL_COLOR_BUFFER_BIT); 72 | glClearColor(0.1f, 0.09f, 0.1f, 1); 73 | 74 | batch.addText("Hello world!", 200, 200, 1f, 0xFF00AB0); 75 | batch.addText("My name is Gabe!", 100, 300, 1.1f, 0xAA01BB); 76 | 77 | String message = ""; 78 | for (int i=0; i < 10; i++) { 79 | message += (char)(random.nextInt('z' - 'a') + 'a'); 80 | } 81 | batch.addText(message, 200, 400, 1.1f, 0xAA01BB); 82 | //batch.addCharacter(0, 0, 620.0f, oneQuad, 0xFF4500); 83 | batch.flushBatch(); 84 | 85 | glfwSwapBuffers(window); 86 | glfwPollEvents(); 87 | } 88 | } 89 | } 90 | --------------------------------------------------------------------------------