Compare commits

...
Author SHA1 Message Date
sisyphussy 864cc4ea82 Fix stuff 2026-06-26 10:03:05 +02:00
sisyphussy 30fe456909 Merge branch 'master' into overlay-font-shaders 2026-06-26 09:53:44 +02:00
sisyphussy 85658bb7fb Merge branch 'master' into overlay-font-shaders 2026-06-21 13:16:14 +02:00
sisyphussy ecf073c09d Merge branch 'master' into overlay-font-shaders 2026-06-14 04:34:54 +02:00
sisyphussy 1df25ac753 Merge branch 'master' into overlay-font-shaders 2026-06-05 19:47:34 +02:00
sisyphussy 3df6a830a2 Batch F3 box rendering 2026-06-05 15:24:57 +02:00
sisyphussy 51a5fd2f59 Merge branch 'master' into overlay-font-shaders 2026-06-04 22:45:21 +02:00
sisyphussy 902f0f4741 stuff 2026-06-04 22:45:18 +02:00
sisyphussy 406ce93c38 Merge branch 'master' into overlay-font-shaders 2026-06-04 19:35:46 +02:00
sisyphussy 5958408019 small changes 2026-06-04 19:28:53 +02:00
sisyphussy 36a3c20db7 Add compat for custom fonts 2026-05-30 21:19:24 +02:00
sisyphussy 7572b829ef Code is a complete mess, but my god are the profiles sexy 2026-05-29 08:36:25 +02:00
sisyphussy 94c2a9d4f5 Fix italic, dinnerbone, strikethrough, underlineh, 2026-05-28 00:46:19 +02:00
sisyphussy e44caddc7b use persistent buffer 2026-05-27 19:57:56 +02:00
sisyphussy a3c3c103fc get rid of FontDrawCmd 2026-05-27 15:55:22 +02:00
sisyphussy 58f771562b further optimizations 2026-05-27 14:49:51 +02:00
sisyphussy ac3dd972b3 make instancing better 2026-05-26 11:22:33 +02:00
sisyphussy ce8f9341ca Make instancing work (somewhat) (barely) 2026-05-25 18:33:25 +02:00
sisyphussy fc88137c3b cleanup 2026-05-25 10:45:54 +02:00
sisyphussy bf3c828f6f Messy but it works 2026-05-24 11:16:07 +02:00
sisyphussy 10e9bc26fb Merge branch 'master' into overlay-font-shaders 2026-05-24 07:34:00 +02:00
sisyphussy a32e034aa5 Convert ResourceLocation -> int texture 2026-05-24 07:33:17 +02:00
sisyphussy 9a5ba2a17c add spotlight 2026-05-21 16:11:03 +02:00
sisyphussy 08cbbbb537 add defines 2026-05-21 00:38:32 +02:00
sisyphussy e72708b2c5 works somehow 2026-05-20 11:56:17 +02:00
sisyphussy 224455b6ca fixes 2026-05-19 16:48:55 +02:00
sisyphussy 5e1dd876f2 test 2026-05-18 18:32:51 +02:00
50 changed files with 3463 additions and 1768 deletions

No files matched your search

+5 -5
View File
@@ -12,11 +12,11 @@ tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.add("-Dangelica.useVulkan=true")
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.useVulkan=true")
//extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
//extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
//extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
//extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
+16 -2
View File
@@ -63,10 +63,13 @@ configurations {
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
resolutionStrategy {
force 'com.github.GTNewHorizons:Hodgepodge:9:dev'
}
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
implementation("com.github.GTNewHorizons:Hodgepodge:9:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
@@ -76,7 +79,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.11.4:dev")
api("com.github.GTNewHorizons:GTNHLib:9:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
@@ -97,6 +100,14 @@ dependencies {
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
api("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
/*
devOnlyNonPublishable('com.github.GTNewHorizons:GT5-Unofficial:9:dev') {
exclude group: 'com.github.GTNewHorizons', module: 'Angelica'
}
*/
// Display List Testing
/*
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-749-GTNH:dev")
@@ -115,6 +126,9 @@ dependencies {
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.8-GTNH:dev")
*/
// Font Testing
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Nuclear-Control:2.7.9:dev")
//devOnlyNonPublishable('com.github.GTNewHorizons:worldedit-gtnh:v0.0.9')
// Notfine Deps
@@ -16,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.hooks.events.SplashDestroyEvent;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
@@ -78,6 +79,7 @@ import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL45;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.KHRDebug;
@@ -96,6 +98,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.IntSupplier;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizons.angelica.glsm.Vendor.AMD;
import static com.gtnewhorizons.angelica.glsm.Vendor.INTEL;
import static com.gtnewhorizons.angelica.glsm.Vendor.MESA;
@@ -507,6 +510,22 @@ public class GLStateManager {
return DisplayListManager.displayListExists(list);
}
public static long glFenceSync(int flags, int i) {
return RENDER_BACKEND.fenceSync(flags, i);
}
public static int glClientWaitSync(long fence, int flags, long l) {
return RENDER_BACKEND.clientWaitSync(fence, flags, l);
}
public static void glDeleteSync(long fence) {
RENDER_BACKEND.deleteSync(fence);
}
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) {
return RENDER_BACKEND.mapBufferRange(target, offset, length, access);
}
public static class GLFeatureSet extends IntOpenHashSet {
private static final long serialVersionUID = 8558779940775721010L;
@@ -1798,7 +1817,38 @@ public class GLStateManager {
}
private static int getBoundTexture(int unit) {
return textures.getTextureUnitBindings(unit).getBinding();
return textures.getTextureUnitBindings(unit).getTexture2D();
}
public static void bindTextureToUnit(int unit, int target, int texture) {
final int activeUnit = activeTextureUnit.getValue();
if (unit == activeTextureUnit.getValue()) {
glBindTexture(target, texture);
return;
}
final TextureBinding textureUnit = textures.getTextureUnitBindings(unit);
if (target == GL11.GL_TEXTURE_2D) {
final int cachedBinding = textureUnit.getTexture2D();
if (cachedBinding != texture) {
glActiveTexture(GL13.GL_TEXTURE0 + unit);
glBindTexture(target, texture);
glActiveTexture(GL13.GL_TEXTURE0);
}
} else if (target == GL30.GL_TEXTURE_2D_ARRAY) {
final int cachedBinding = textureUnit.getTexture2DArray();
if (cachedBinding != texture) {
glActiveTexture(GL13.GL_TEXTURE0 + unit);
glBindTexture(target, texture);
glActiveTexture(GL13.GL_TEXTURE0);
}
} else {
glActiveTexture(GL13.GL_TEXTURE0 + unit);
glBindTexture(target, texture);
glActiveTexture(GL13.GL_TEXTURE0);
}
}
public static void glBindTexture(int target, int texture) {
@@ -1814,13 +1864,6 @@ public class GLStateManager {
deferredDeleteTextures.remove(texture);
}
if (target != GL11.GL_TEXTURE_2D) {
// Non-2D targets (e.g. GL_TEXTURE_2D_ARRAY) pass through without caching
logUncachedTextureTarget(target);
RENDER_BACKEND.bindTexture(target, texture);
return;
}
if (!isCachingEnabled()) {
RENDER_BACKEND.bindTexture(target, texture);
return;
@@ -1828,24 +1871,40 @@ public class GLStateManager {
final int activeUnit = GLStateManager.activeTextureUnit.getValue();
final TextureBinding textureUnit = textures.getTextureUnitBindings(activeUnit);
final int cachedBinding = textureUnit.getBinding();
if (cachedBinding != texture) {
RENDER_BACKEND.bindTexture(target, texture);
textureUnit.setBinding(texture);
if (texture != 0 && activeUnit > maxBoundTextureUnit) {
maxBoundTextureUnit = activeUnit;
}
if (!lockBindCallback && activeTextureUnit.getValue() == 0) {
lockBindCallback = true;
if (GLSMHooks.TEXTURE_BIND.hasListeners()) {
GLSMHooks.textureBindEvent.textureId = texture;
GLSMHooks.TEXTURE_BIND.post(GLSMHooks.textureBindEvent);
if (target == GL11.GL_TEXTURE_2D) {
final int cachedBinding = textureUnit.getTexture2D();
if (cachedBinding != texture) {
RENDER_BACKEND.bindTexture(target, texture);
textureUnit.setTexture2D(texture);
if (texture != 0 && activeUnit > maxBoundTextureUnit) {
maxBoundTextureUnit = activeUnit;
}
if (!lockBindCallback && activeTextureUnit.getValue() == 0) {
lockBindCallback = true;
if (GLSMHooks.TEXTURE_BIND.hasListeners()) {
GLSMHooks.textureBindEvent.textureId = texture;
GLSMHooks.TEXTURE_BIND.post(GLSMHooks.textureBindEvent);
}
RenderSystem.bindTextureToUnit(0, texture);
lockBindCallback = false;
}
RenderSystem.bindTextureToUnit(0, texture);
lockBindCallback = false;
}
} else if (target == GL30.GL_TEXTURE_2D_ARRAY) {
final int cachedBinding = textureUnit.getTexture2DArray();
if (cachedBinding != texture) {
RENDER_BACKEND.bindTexture(target, texture);
textureUnit.setTexture2DArray(texture);
if (texture != 0 && activeUnit > maxBoundTextureUnit) {
maxBoundTextureUnit = activeUnit;
}
}
} else {
// Other targets pass through without caching
logUncachedTextureTarget(target);
RENDER_BACKEND.bindTexture(target, texture);
}
}
private static int changeFormatIfDeprecated(int internalformat) {
@@ -2046,24 +2105,38 @@ public class GLStateManager {
if (id == 0) return;
final int savedUnit = GLStateManager.activeTextureUnit.getValue();
final int savedBinding = textures.getTextureUnitBindings(savedUnit).getBinding();
final int savedBinding = textures.getTextureUnitBindings(savedUnit).getTexture2D();
boolean changedUnit = false;
boolean isTexture2D = TextureInfoCache.INSTANCE.hasEntry(id);
// Unbind from all units that have this texture
for (int i = 0; i <= maxBoundTextureUnit; i++) {
if (textures.getTextureUnitBindings(i).getBinding() == id) {
if (textures.getTextureUnitBindings(i).getTexture2D() == id) {
if (i != savedUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
changedUnit = true;
}
isTexture2D = true;
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, 0);
}
if (textures.getTextureUnitBindings(i).getTexture2DArray() == id) {
if (i != savedUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
changedUnit = true;
}
RENDER_BACKEND.bindTexture(GL30.GL_TEXTURE_2D_ARRAY, 0);
}
}
// Restore active unit, then shrink the texture via a temporary bind
if (changedUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + savedUnit);
}
//GL11.glDeleteTextures(id);
if (!isTexture2D) {
GL11.glDeleteTextures(id); // Only defer delete GL_TEXTURE_2D textures
return;
}
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, id);
RENDER_BACKEND.texParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
suspendPixelUnpackBuffer();
@@ -3145,9 +3218,7 @@ public class GLStateManager {
// Always delete
for (int i = 0; i <= maxBoundTextureUnit; i++) {
if (textures.getTextureUnitBindings(i).getBinding() == id) {
textures.getTextureUnitBindings(i).setBinding(0);
}
textures.getTextureUnitBindings(i).unbindIfEquals(id);
}
}
@@ -3188,6 +3259,9 @@ public class GLStateManager {
splashComplete = true;
drawableGLHolder = null;
drawableGL = null;
if (GLSMHooks.SPLASH_DESTROY.hasListeners()) {
GLSMHooks.SPLASH_DESTROY.post(new SplashDestroyEvent());
}
}
public static void glNewList(int list, int mode) {
@@ -3349,7 +3423,7 @@ public class GLStateManager {
// Restore texture bindings for all units, then restore active unit. activeTextureUnit stores the 0-based index, glActiveTexture needs GL_TEXTURE0 + index.
for (int i = 0; i < MAX_TEXTURE_UNITS; i++) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, textures.getTextureUnitBindings(i).getBinding());
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, textures.getTextureUnitBindings(i).getTexture2D());
}
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + activeTextureUnit.getValue());
}
@@ -3767,10 +3841,6 @@ public class GLStateManager {
return activeTextureUnit.getValue();
}
public static int getListMode() {
return DisplayListManager.getListMode();
}
private static boolean handleRemovedTexParam(int target, int pname, int value) {
if (pname == GL14.GL_GENERATE_MIPMAP) {
if (target == GL11.GL_TEXTURE_2D) {
@@ -4655,6 +4725,23 @@ public class GLStateManager {
restorePixelUnpackBuffer();
}
public static void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, IntBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage3D in display lists not yet implemented - if you see this, please report!");
}
suspendPixelUnpackBuffer();
GL12.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
//RENDER_BACKEND.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
restorePixelUnpackBuffer();
}
public static void glGetTextureSubImage(int texture, int level, int xoffset, int yoffset, int zoffset,
int width, int height, int depth,
int format, int type, int pixels_bufSize, long pixels_buffer_offset
) {
GL45.glGetTextureSubImage(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels_bufSize, pixels_buffer_offset);
}
public static void glCopyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glCopyTexImage1D");
@@ -6081,6 +6168,19 @@ public class GLStateManager {
RENDER_BACKEND.uniformMatrix4(location, transpose, matrices);
}
public static final Matrix4f scratchMvp = new Matrix4f();
public static final FloatBuffer mvpBuffer = memAllocFloat(16);
public static void uploadMVPMatrix(int mvpMatrixLocation) {
getMVPMatrix(scratchMvp);
scratchMvp.get(mvpBuffer);
glUniformMatrix4(mvpMatrixLocation, false, mvpBuffer);
}
public static Matrix4f getMVPMatrix(Matrix4f dest) {
return getProjectionMatrix().mul(getModelViewMatrix(), dest);
}
public static void glGetActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name) {
RENDER_BACKEND.getActiveUniform(program, index, length, size, type, name);
}
@@ -93,7 +93,7 @@ public class DSAARB extends DSAUnsupported {
super.bindTextureToUnit(unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setTexture2D(texture);
}
}
@@ -18,6 +18,7 @@ public final class GLSMHooks {
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<SplashDestroyEvent> SPLASH_DESTROY = EventBus.create(SplashDestroyEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
// Reusable event instances
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class SplashDestroyEvent extends MutableEvent {
}
@@ -5,11 +5,13 @@ import lombok.Setter;
@Getter
public class TextureBinding implements ISettableState<TextureBinding> {
@Setter protected int binding;
@Setter protected int texture2D;
@Setter protected int texture2DArray;
@Override
public TextureBinding set(TextureBinding state) {
this.binding = state.binding;
this.texture2D = state.texture2D;
this.texture2DArray = state.texture2DArray;
return this;
}
@@ -17,10 +19,20 @@ public class TextureBinding implements ISettableState<TextureBinding> {
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof TextureBinding textureBinding)) return false;
return binding == textureBinding.binding;
return texture2D == textureBinding.texture2D
&& texture2DArray == textureBinding.texture2DArray;
}
@Override
public TextureBinding copy() {
return new TextureBinding().set(this);
}
public final void unbindIfEquals(int id) {
if (getTexture2D() == id) {
this.setTexture2D(0);
}
if (getTexture2DArray() == id) {
setTexture2DArray(0);
}
}
}
@@ -19,7 +19,7 @@ public final class StreamingUploader {
MAP_BUFFER_RANGE
}
private static final int MAP_WRITE_INVALIDATE_BUFFER = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
public static final int MAP_WRITE_INVALIDATE_BUFFER = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
public static int upload(ByteBuffer data, int capacity) {
return upload(GLStateManager.getInitConfig().getStreamingUploadStrategy(), data, capacity);
@@ -43,6 +43,11 @@ public class TextureInfoCache {
}
}
public boolean hasEntry(int id) {
if (id < 0) return false;
return cache.containsKey(id);
}
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
if (target == GL11.GL_TEXTURE_2D && level == 0) {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
@@ -0,0 +1,16 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
public final class AngelicaTextPreprocessor implements FontRendering.TextPreprocessor {
@Override
public String apply(final String text) {
return ColorCodeUtils.convertImpl(text);
}
@Override
public boolean handlesAmpCodes() {
return true;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
/**
@@ -18,6 +19,7 @@ public final class ColorCodeUtils {
private ColorCodeUtils() {}
public static final char FORMATTING_CHAR = '§';
public static final String FORMATTING_CHAR_STRING = String.valueOf(FORMATTING_CHAR);
public static final char ESCAPED_AMPERSAND = '';
public static final int SECTION_X_PAYLOAD = 12;
@@ -25,22 +27,28 @@ public final class ColorCodeUtils {
public static final int GRADIENT_PAYLOAD = 28;
public static final int GRADIENT_LENGTH = 30;
/** Valid single {@code &} codes. {@code g} is excluded; {@code &g} only converts as part of a gradient. */
public static final String VALID_SINGLE_CODES = "0123456789abcdefklmnorqzvu";
private static final boolean[] isColorCode = new boolean[255];
private static final String[] SECTION_PREFIX = new String[128];
static {
for (int i = 0; i < VALID_SINGLE_CODES.length(); i++) {
char c = VALID_SINGLE_CODES.charAt(i);
SECTION_PREFIX[c] = String.valueOf(FORMATTING_CHAR) + c;
/** Valid single {@code &} codes. {@code g} is excluded; {@code &g} only converts as part of a gradient. */
final String s = "0123456789abcdefklmnorqzvu";
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
isColorCode[ch] = true;
isColorCode[Character.toUpperCase(ch)] = true;
}
SECTION_PREFIX['x'] = String.valueOf(FORMATTING_CHAR) + 'x';
SECTION_PREFIX['g'] = String.valueOf(FORMATTING_CHAR) + 'g';
}
public static String sectionPrefix(char code) {
char c = (code < 128) ? Character.toLowerCase(code) : code;
return (c < 128) ? SECTION_PREFIX[c] : null;
public static void initPreprocessor() {
if (AngelicaConfig.enableAmpersandConversion) {
FontRendering.setTextPreprocessor(new AngelicaTextPreprocessor());
} else {
FontRendering.setTextPreprocessor((FontRendering.TextPreprocessor) null);
}
}
public static String sectionPrefix(final char code) {
return FORMATTING_CHAR_STRING + code;
}
public static int parseHexPairs(CharSequence str, int start, int count) {
@@ -66,21 +74,7 @@ public final class ColorCodeUtils {
return parseSectionXAt(str, start) != -1;
}
private static String lastConversionInput;
private static String lastConversionOutput;
public static String convertAmpersandToSectionX(String text) {
if (text == null || !AngelicaConfig.enableAmpersandConversion) return text;
if (text == lastConversionInput) return lastConversionOutput;
final String out = convertImpl(text);
lastConversionInput = text;
lastConversionOutput = out;
return out;
}
private static String convertImpl(String text) {
public static String convertImpl(final String text) {
int idx = text.indexOf('&');
if (idx == -1) return text;
@@ -88,6 +82,16 @@ public final class ColorCodeUtils {
StringBuilder sb = null;
int last = 0;
while (idx != -1 && idx + 1 < len) {
final char code = text.charAt(idx + 1);
if (code >= 255) continue;
if (isColorCode[code]) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append(text.charAt(idx + 1));
last = idx + 2;
idx = text.indexOf('&', last);
continue;
}
if (idx > 0 && text.charAt(idx - 1) == '\\') {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx - 1);
@@ -96,27 +100,28 @@ public final class ColorCodeUtils {
idx = text.indexOf('&', last);
continue;
}
if (text.charAt(idx + 1) == '#' && idx + 7 < len) {
boolean validHex = true;
for (int i = 2; i <= 7; i++) {
if (Character.digit(text.charAt(idx + i), 16) == -1) {
validHex = false;
break;
}
}
if (validHex) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append('x');
if (code == '#') {
if (idx + 7 < len) {
boolean validHex = true;
for (int i = 2; i <= 7; i++) {
sb.append(FORMATTING_CHAR).append(text.charAt(idx + i));
if (Character.digit(text.charAt(idx + i), 16) == -1) {
validHex = false;
break;
}
}
if (validHex) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append('x');
for (int i = 2; i <= 7; i++) {
sb.append(FORMATTING_CHAR).append(text.charAt(idx + i));
}
last = idx + 8;
idx = text.indexOf('&', last);
continue;
}
last = idx + 8;
idx = text.indexOf('&', last);
continue;
}
}
if (Character.toLowerCase(text.charAt(idx + 1)) == 'g' && idx + 17 < len
} else if (Character.toLowerCase(code) == 'g' && idx + 17 < len
&& text.charAt(idx + 2) == '&' && text.charAt(idx + 3) == '#'
&& text.charAt(idx + 10) == '&' && text.charAt(idx + 11) == '#') {
boolean valid1 = true, valid2 = true;
@@ -137,13 +142,6 @@ public final class ColorCodeUtils {
continue;
}
}
char code = Character.toLowerCase(text.charAt(idx + 1));
if (VALID_SINGLE_CODES.indexOf(code) != -1) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append(text.charAt(idx + 1));
last = idx + 2;
}
idx = text.indexOf('&', idx + 1);
}
if (sb == null) return text;
@@ -0,0 +1,199 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.CustomFramebuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.IShaderDefinesInjector;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.utils.InstancedHelper;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.ClientProxy.mc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
//TODO move this + shader to gtnhlib
public final class FontOverlayShader extends ShaderProgram {
private int mvpMatrixLocation;
private int uTexelSize;
private int uScale;
private int uTime;
private int uTexBounds;
private final int padding;
public float xStart;
public float xEnd;
public float yStart;
public float yEnd;
private static CustomFramebuffer framebuffer;
private static int vao;
private static int vbo;
private static ByteBuffer buffer;
// public static FontOverlayShader TEMPLATE = new FontOverlayShader(
// new ResourceLocation(AngelicaMod.MOD_ID, "shaders/font/fontShaderTemplate.fsh")
// );
public FontOverlayShader(ResourceLocation fragShader, IShaderDefinesInjector... defines) {
this(fragShader, 4, defines);
}
public FontOverlayShader(ResourceLocation fragShader, int padding, IShaderDefinesInjector... defines) {
super(
loadShaderSource(getVertexShader()),
loadShaderSource(fragShader, defines)
);
this.padding = padding;
this.bindTextureSlots("textFBO", "sceneFBO");
mvpMatrixLocation = this.getUniformLocation("u_MVPMatrix");
uTexelSize = this.getUniformLocation("uTexelSize");
uScale = this.getUniformLocation("uScale");
uTime = this.getUniformLocation("uTime");
uTexBounds = this.getUniformLocation("uTexBounds");
}
public static ResourceLocation getVertexShader() {
return new ResourceLocation(AngelicaMod.MOD_ID, "shaders/font/fontShader.vsh");
}
public boolean begin(BatchingFontRenderer fontRenderer) {
fontRenderer.flushBatch();
xStart = -1;
return true;
}
public void updateBounds(float x, float y, float width, float height) {
if (xStart == -1) {
xStart = x;
yStart = y;
}
xEnd = x + width;
yEnd = y + height;
}
public boolean end(BatchingFontRenderer fontRenderer) {
if (framebuffer == null) {
framebuffer = new CustomFramebuffer(0);
}
if (framebuffer.framebufferWidth != mc.displayWidth || framebuffer.framebufferHeight != mc.displayHeight) {
framebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
}
framebuffer.clearBindFramebuffer();
fontRenderer.flushBatch();
framebuffer.unbindFramebuffer();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, mc.getFramebuffer().framebufferTexture);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
framebuffer.bindFramebufferTexture();
final int padding = this.padding;
this.use();
if (vao == 0) {
buffer = memAlloc(32);
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, InstancedHelper.getQuadEBO());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
// position
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 8, 0);
GLStateManager.glEnableVertexAttribArray(0);
}
long address = memAddress0(buffer);
buffer.limit(32);
addVertex(address, xStart - padding, yStart - padding);
addVertex(address + 8, xEnd + padding, yStart - padding);
addVertex(address + 16, xStart - padding, yEnd + padding);
addVertex(address + 24, xEnd + padding, yEnd + padding);
Matrix4f mvp = GLStateManager.getMVPMatrix(new Matrix4f());
Vector4f temp = new Vector4f();
temp.x = xStart;
temp.y = yEnd;
temp.z = 0;
temp.w = 1;
MatrixHelper.transformVertex(mvp, temp);
float boundXStart = temp.x * 0.5f + 0.5f;
float boundYStart = temp.y * 0.5f + 0.5f;
temp.x = xEnd;
temp.y = yStart;
temp.z = 0;
temp.w = 1;
MatrixHelper.transformVertex(mvp, temp);
float boundXEnd = temp.x * 0.5f + 0.5f;
float boundYEnd = temp.y * 0.5f + 0.5f;
this.uploadBounds(boundXStart, boundXEnd, boundYStart, boundYEnd);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STREAM_DRAW);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glUseProgram(0);
GLStateManager.glBindVertexArray(0);
nmemFree(address);
return false;
}
private void addVertex(long ptr, float x, float y) {
// v, v
memPutFloat(ptr, x);
memPutFloat(ptr + 4, y);
}
@Override
public void use() {
super.use();
GLStateManager.uploadMVPMatrix(mvpMatrixLocation);
if (uTexelSize != -1) {
GLStateManager.glUniform2f(uTexelSize, 1f / mc.displayWidth, 1f / mc.displayHeight);
}
this.uploadTime(uTime);
if (uScale != -1) {
final ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
GLStateManager.glUniform1i(uScale, res.getScaleFactor());
}
}
private void uploadBounds(float minX, float maxX, float minY, float maxY) {
GLStateManager.glUniform4f(uTexBounds, minX, maxX, minY, maxY);
}
public static final class Builder implements IShaderDefinesInjector {
@Override
public void writeDefines(StringBuilder out) {
}
}
}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import net.minecraft.util.ResourceLocation;
public interface FontProvider {
/**
* For use with §k. Should fetch a character of the same width as provided.
*/
char getRandomReplacement(char chr);
boolean isGlyphAvailable(char chr);
float getUStart(char chr);
float getVStart(char chr);
float getXAdvance(char chr);
float getGlyphW(char chr);
float getUSize(char chr);
float getVSize(char chr);
float getShadowOffset();
ResourceLocation getTexture(char chr);
float getYScaleMultiplier();
}
@@ -1,291 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
public final class FontProviderCustom implements FontProvider {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final String FONT_DIR = "fonts/custom/";
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
private float currentFontQuality = FontConfig.customFontQuality;
@Setter
private Font font;
private FontProviderCustom(byte id) {
this.id = id;
Font[] availableFonts = FontStrategist.getAvailableFonts();
String myFontName = switch (this.id) {
case 0 -> FontConfig.customFontNamePrimary;
case 1 -> FontConfig.customFontNameFallback;
default -> null;
};
if (Objects.equals(myFontName, "(none)")) {
this.font = null;
return;
}
int fontPos = -1;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
fontPos = i;
break;
}
}
if (fontPos == -1) {
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
this.font = null;
return;
}
this.font = availableFonts[fontPos].deriveFont(this.currentFontQuality);
}
private static class InstLoader {
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
}
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
public void reloadFont(int fontID) {
this.currentFontQuality = FontConfig.customFontQuality;
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
File[] files = new File(getFontDir()).listFiles();
if (files != null) {
for (File f : files) {
if (!Files.isSymbolicLink(f.toPath())) {
f.delete();
}
}
}
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Map mapTextureObjects = tm.mapTextureObjects;
for (int i = 0; i < ATLAS_COUNT; i++) {
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
if (mapTextureObjects.containsKey(key)) {
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
TextureUtil.deleteTexture(obj.getGlTextureId());
mapTextureObjects.remove(key);
}
}
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
}
private String getFontDir() {
return FONT_DIR + "f" + this.id + "/";
}
private String getAtlasFilename(int atlasId) {
return "f" + this.id + "p" + atlasId;
}
String getAtlasResourceName(int atlasId) {
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
}
String getAtlasFullPath(int atlasId) {
return getFontDir() + getAtlasFilename(atlasId) + ".png";
}
@Value
private class GlyphData {
float uStart;
float vStart;
float xAdvance;
float glyphW;
float uSz;
float vSz;
}
private class FontAtlas {
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
private ResourceLocation texture;
private final int id;
FontAtlas(int id) {
this.id = id;
}
void construct(Font font) {
int atlasChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char) (i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) { atlasChars++; }
}
if (atlasChars == 0) { return; }
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
g2d.dispose();
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
final float charSeparator = currentFontQuality / 3f;
int rowWidth = 0;
int maxRowWidth = 0;
atlasChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
if (atlasChars % atlasTilesX == 0) {
maxRowWidth = Math.max(maxRowWidth, rowWidth);
rowWidth = 0;
}
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
rowWidth += (int) (charSeparator + fm.charWidth(ch));
atlasChars++;
}
}
maxRowWidth = Math.max(maxRowWidth, rowWidth);
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int imageWidth = (int) (maxRowWidth + charSeparator);
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setFont(font);
fm = g2d.getFontMetrics();
int tileX = 0, tileY = 0; // position in atlas tiles
int imgX = (int) charSeparator; // position in pixels
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char) (i + ATLAS_SIZE * this.id);
if (!font.canDisplay(ch)) { continue; }
if (tileX >= atlasTilesX) {
tileX = 0;
imgX = (int) charSeparator;
tileY++;
}
final int charWidth = fm.charWidth(ch);
final float charAspectRatio = (float) charWidth / lineHeight;
final float inset = currentFontQuality / 16;
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
final float vStart = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
final float glyphW = charAspectRatio * 8 + 1;
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
imgX += (int) (charWidth + charSeparator);
tileX++;
}
g2d.dispose();
try {
Files.createDirectories(Paths.get(getFontDir()));
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
} catch (IOException e) {
e.printStackTrace();
}
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
}
}
private FontAtlas getAtlas(char chr) {
int id = chr / ATLAS_SIZE;
FontAtlas fa = this.fontAtlases[id];
if (fa == null) {
fa = new FontAtlas(id);
fa.construct(this.font);
this.fontAtlases[id] = fa;
}
return fa;
}
@Override
public boolean isGlyphAvailable(char chr) {
if (this.font == null) { return false; }
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
}
@Override
public float getVStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
}
@Override
public float getXAdvance(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
}
@Override
public float getGlyphW(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
}
@Override
public float getUSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
}
@Override
public float getVSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return getAtlas(chr).texture;
}
@Override
public float getYScaleMultiplier() {
return FontConfig.customFontScale;
}
}
@@ -1,109 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.util.ResourceLocation;
public final class FontProviderMC implements FontProvider {
/**
* The full list of characters present in the default Minecraft font, excluding the Unicode font
*/
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private FontProviderMC() {}
private static class InstLoader {
static final FontProviderMC instance = new FontProviderMC();
static final FontProviderMC instanceSGA = new FontProviderMC();
}
public static FontProviderMC get(boolean isSGA) {
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
}
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
ResourceLocation locationFontTexture = null;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
public int[] charWidth;
static {
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
}
}
public int lookupMcFontPosition(char ch) {
return MCFONT_ASCII_MAP.get(ch);
}
@Override
public boolean isGlyphAvailable(char chr) {
return MCFONT_ASCII_MAP.containsKey(chr);
}
public char getRandomReplacement(char chr){
int lutIndex = lookupMcFontPosition(chr);
if (lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
return MCFONT_CHARS.charAt(lutIndex);
}
return chr;
}
@Override
public float getUStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return ((lutIndex % 16) * 8) / 128.0F;
}
@Override
public float getVStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (float) ((lutIndex / 16) * 8) / 128.0F;
}
@Override
public float getXAdvance(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex];
}
@Override
public float getGlyphW(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex] - 0.01F;
}
@Override
public float getUSize(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (charWidth[lutIndex] - 1.01F) / 128.0F;
}
@Override
public float getVSize(char chr) {
return 7.99F / 128.0F;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return locationFontTexture;
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -1,113 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import java.io.IOException;
import java.io.InputStream;
public final class FontProviderUnicode implements FontProvider {
private FontProviderUnicode() {
try {
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
//noinspection ResultOfMethodCallIgnored
inputstream.read(this.glyphWidth);
}
catch (IOException ioexception) {
throw new RuntimeException(ioexception);
}
}
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
private final byte[] glyphWidth = new byte[65536];
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
@Override
public boolean isGlyphAvailable(char chr) {
return true;
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
}
@Override
public float getVStart(char chr) {
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
}
@Override
public float getXAdvance(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
return (endColumnF - startColumnF) / 2.0F + 1.0F;
}
@Override
public float getGlyphW(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return chrWidth / 2.0f + 1.0f;
}
@Override
public float getUSize(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return (chrWidth - 0.42f) / 256.0f;
}
@Override
public float getVSize(char chr) {
return (16.0f - 0.42f) / 256.0f;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset * FontConfig.fontShadowOffsetUC;
}
@Override
public ResourceLocation getTexture(char chr) {
final int uniPage = chr / 256;
return getUnicodePageLocation(uniPage);
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -0,0 +1,28 @@
package com.gtnewhorizons.angelica.client.font;
import java.util.ArrayList;
import java.util.List;
public final class FontShaderManager {
public static final List<FontOverlayShader> fontShaders = new ArrayList<>();
public static void registerShader(FontOverlayShader shader) {
fontShaders.add(shader);
}
static boolean isOverlayShader(char ch) {
return ((int) ch) < fontShaders.size();
}
public static FontOverlayShader begin(char ch, BatchingFontRenderer fontRenderer) {
final byte index = (byte) ch;
final FontOverlayShader shader = get(index);
shader.begin(fontRenderer);
return shader;
}
public static FontOverlayShader get(byte index) {
return fontShaders.get(index);
}
}
@@ -2,29 +2,26 @@ package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.HashMultiset;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
import cpw.mods.fml.client.SplashProgress;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.File;
import javax.annotation.Nullable;
import java.awt.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
public class FontStrategist {
import static com.gtnewhorizons.angelica.AngelicaMod.LOGGER;
import static com.gtnewhorizons.angelica.client.font.BatchingFontRenderer.reloadFonts;
public final class FontStrategist {
@Getter
private static final Font[] availableFonts;
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static Font primaryFont;
public static Font secondaryFont;
static {
if (GraphicsEnvironment.isHeadless()) {
@@ -60,70 +57,49 @@ public class FontStrategist {
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
}
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
}
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
defaultResourcePacks.add(fontResourcePack);
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
*/
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
return FontProviderMC.get(true);
}
if (me.bookMode) {
return FontProviderUnicode.get();
}
if (customFontEnabled && !me.isSplash) {
FontProvider fp;
fp = FontProviderCustom.getPrimary();
if (fp.isGlyphAvailable(chr)) { return fp; }
fp = FontProviderCustom.getFallback();
if (fp.isGlyphAvailable(chr)) { return fp; }
return FontProviderUnicode.get();
} else {
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
return FontProviderMC.get(false);
} else {
return FontProviderUnicode.get();
}
reloadCustomFontProviders();
}
}
public static @Nullable Font[] getConfigFonts() {
if (primaryFont != null && secondaryFont != null) {
return new Font[] { primaryFont, secondaryFont };
}
if (primaryFont != null) {
return new Font[] { primaryFont };
}
if (secondaryFont != null) {
return new Font[] { secondaryFont };
}
return null;
}
public static void reloadCustomFontProviders() {
FontProviderCustom.getPrimary().setFont(null);
FontProviderCustom.getFallback().setFont(null);
primaryFont = null;
secondaryFont = null;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
FontProviderCustom.getPrimary().reloadFont(i);
final Font font = availableFonts[i];
final String fontName = font.getFontName();
if (FontConfig.customFontNamePrimary.equals(fontName)) {
primaryFont = font.deriveFont((float) FontConfig.customFontQuality);
}
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
FontProviderCustom.getFallback().reloadFont(i);
if (FontConfig.customFontNameFallback.equals(fontName)) {
secondaryFont = font.deriveFont((float) FontConfig.customFontQuality);
}
}
reloadFonts();
}
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
// noinspection deprecation
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
if (fontRenderer instanceof SplashProgress.SplashFontRenderer) return true;
try {
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
active = active || customSplashClass.isInstance(fontRenderer);
} catch (ClassNotFoundException ignored) {
}
return customSplashClass.isInstance(fontRenderer);
} catch (ClassNotFoundException ignored) {}
return active;
return false;
}
}
@@ -0,0 +1,28 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
public final class GlyphData {
public final float uStart;
public final float vStart;
public final float xStart;
public final float xWidth; // The width of the actual char
public final float xAdvance; // The amount to advance
public final float uSize;
public final float vSize;
// For MC Fonts (where xStart = 0, glyphW = charWidth - 1, xAdvance = charWidth)
public GlyphData(float uStart, float vStart, float glyphW, float uSize, float vSize) {
this(uStart, vStart, 0, glyphW, glyphW + 1, uSize, vSize);
}
public GlyphData(float uStart, float vStart, float xStart, float xWidth, float xAdvance, float uSize, float vSize) {
this.uStart = uStart;
this.vStart = vStart;
this.xAdvance = xAdvance + FontConfig.glyphSpacing;
this.xStart = xStart;
this.xWidth = xWidth;
this.uSize = uSize;
this.vSize = vSize;
}
}
@@ -0,0 +1,252 @@
package com.gtnewhorizons.angelica.client.font.atlas;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import com.gtnewhorizons.angelica.client.font.GlyphData;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.client.Minecraft;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.GlyphVector;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
public final class FontProviderCustom extends FontTextureArray {
private static final int ATLAS_CHARS = 256;
private final Font[] fonts;
private FontProviderCustom(int size, int layers, short[] layersLookupArray, int filter, Font[] fonts) {
super(size, layers, layersLookupArray, filter);
this.fonts = fonts;
}
public static FontProviderCustom createTextureArray(final Font[] fonts) {
final int fontAmount = fonts.length;
if (fontAmount == 0) return null;
final FontMetrics[] fontMetrics = new FontMetrics[fontAmount];
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
for (int i = 0; i < fontAmount; i++) {
g2d.setFont(fonts[i]);
fontMetrics[i] = g2d.getFontMetrics();
}
g2d.dispose();
short[] layerLookup = new short[256];
short layer = 0;
loop:
for (int atlasId = 0; atlasId < 256; atlasId++) {
for (int i = 0; i < 256; i++) {
final char ch = (char) (i + atlasId * 256);
for (Font font : fonts) {
if (font.canDisplay(ch)) {
layerLookup[atlasId] = layer;
layer++;
continue loop;
}
}
// Can't display any chars, skip.
layerLookup[atlasId] = -1;
}
}
final int size = getAtlasSize(fonts, fontMetrics, 2);
return new FontProviderCustom(
size,
layer,
layerLookup,
GL11.GL_LINEAR,
fonts
);
}
private static int getAtlasSize(Font[] fonts, FontMetrics[] fontMetrics, int separator) {
int size = 512;
int maxCharHeight = 0;
for (FontMetrics fm : fontMetrics) {
if (fm.getHeight() > maxCharHeight) maxCharHeight = fm.getHeight();
}
loop:
while (size <= 2048) {
for (int atlasId = 0; atlasId < 256; atlasId++) {
int imgX = 0;
int imgY = 0; //TODO idk
for (int atlasChar = 0; atlasChar < 256; atlasChar++) {
final char ch = (char) (atlasChar + 256 * atlasId);
for (int i = 0; i < fonts.length; i++) {
if (!fonts[i].canDisplay(ch)) continue;
final int lineHeight = fontMetrics[i].getHeight();
final int charHeight = (lineHeight + separator);
final int charWidth = fontMetrics[i].charWidth(ch);
if (imgX + charWidth >= size) {
imgX = 0;
imgY += charHeight;
}
imgX += (charWidth + separator);
}
}
//if (imgX == 0 && imgY == 0) return 0; TODO integrate this somehow
// Total used height includes final row
int usedHeight = imgY + maxCharHeight;
if (usedHeight > size) {
size *= 2;
continue loop;
}
}
return size;
}
System.out.println("Size ended up being over 2048. This is a bug!");
return size;
}
private boolean canDisplayAtlas(int atlasId) {
for (int i = 0; i < ATLAS_CHARS; i++) {
final char ch = (char) (i + ATLAS_CHARS * atlasId);
for (Font font : fonts) {
if (font.canDisplay(ch)) {
return true;
}
}
}
return false;
}
@Override
public ByteBuffer generateGlyphData(int atlasId, GlyphData[] glyphs) {
if (!canDisplayAtlas(atlasId)) return null;
final int imageWidth = textureSize;
final int imageHeight = textureSize;
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int maxCharHeight = 0;
final FontMetrics[] fontMetrics = new FontMetrics[fonts.length];
for (int i = 0; i < fontMetrics.length; i++) {
g2d.setFont(fonts[i]);
final FontMetrics fm = g2d.getFontMetrics();
fontMetrics[i] = fm;
if (fm.getHeight() > maxCharHeight) maxCharHeight = fm.getHeight();
}
final int padding = 2;
int imgX = padding; // position in pixels
int imgY = maxCharHeight + padding;
Font lastFont = null; // Store last font to save on repeated setFont() calls
final float height = BatchingFontRenderer.getStringHeight();
final int atlasBegin = ATLAS_CHARS * atlasId;
for (int atlasChar = 0; atlasChar < ATLAS_CHARS; atlasChar++) {
char ch = (char) (atlasChar + atlasBegin);
final char replacement = GlyphReplacements.getReplacementGlyph(ch);
if (replacement != 0) {
if (FontConfig.enableGlyphReplacements) {
for (Font font : fonts) {
if (font.canDisplay(replacement)) {
ch = replacement;
break;
}
}
}
}
for (int i = 0; i < fonts.length; i++) {
final Font font = fonts[i];
if (font.canDisplay(ch)) {
if (lastFont != font) {
g2d.setFont(font);
lastFont = font;
}
final FontMetrics fm = fontMetrics[i];
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int advanceWidth = fm.charWidth(ch);
final GlyphVector gv = font.createGlyphVector(
g2d.getFontRenderContext(),
Character.toString(ch));
if (imgX + advanceWidth >= imageWidth) {
imgX = padding;
imgY += (maxCharHeight + padding);
}
final Rectangle bounds = gv.getPixelBounds(
g2d.getFontRenderContext(),
0, 0);
g2d.drawGlyphVector(
gv,
imgX - bounds.x,
imgY - desc
);
final float scale = (height / lineHeight) * getGlyphScaleX();
final float glyphW = advanceWidth * scale;
final float xStart = bounds.x * scale;
final float xWidth = bounds.width * scale;
final float uStart = (float) (imgX) / imageWidth;
final float vStart = (float) (imgY - lineHeight) / imageHeight;
final float uSz = bounds.width / (float) imageWidth;
final float vSz = lineHeight / (float) imageHeight;
glyphs[atlasChar] = new GlyphData(uStart, vStart, xStart, xWidth, glyphW, uSz, vSz);
imgX += bounds.width + padding;
break;
}
}
}
g2d.dispose();
if (FontTextureArray.DUMP_ATLASES) {
try {
File dir = new File(Minecraft.getMinecraft().mcDataDir, "debug/fonts");
if (!dir.exists()) dir.mkdirs();
ImageIO.write(image, "png", new File(dir, "custom_font_" + fonts[0].getFontName() + "_" + atlasId + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
return getPixelBuffer(image);
}
static float getGlyphScaleX() {
return (FontConfig.glyphScale * (float) Math.pow(2, FontConfig.glyphAspect));
}
}
@@ -0,0 +1,122 @@
package com.gtnewhorizons.angelica.client.font.atlas;
import com.gtnewhorizon.gtnhlib.client.renderer.textures.TextureLoader;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import com.gtnewhorizons.angelica.client.font.GlyphData;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
public final class FontProviderMinecraft extends FontTextureArray {
public static final GlyphData[] unicodeGlyphData = new GlyphData[256];
public static int unicodeLayer;
public static FontProviderMinecraft create() {
short layer = 0;
final int amountLayers = 256 + 1; // 256 + 1 because of Unicode pages.
final short[] layersLookupArray = new short[amountLayers];
for (int i = 0; i < amountLayers; i++) {
if (TextureLoader.resourceExists(getUnicodePage(i))) {
layersLookupArray[i] = layer;
layer++;
} else {
layersLookupArray[i] = -1;
}
}
layersLookupArray[256] = layer;
unicodeLayer = layer;
System.out.println("Amount layers: " + layer);
System.out.println(Arrays.toString(layersLookupArray));
return new FontProviderMinecraft(256, layer + 1, layersLookupArray, GL11.GL_NEAREST);
}
private FontProviderMinecraft(int size, int layers, short[] layersLookupArray, int filter) {
super(size, layers, layersLookupArray, filter);
loadAtlas(0); // Load first texture from bound texture atlas
final ResourceLocation resource = getUnicodePage(0);
final ByteBuffer pixels = getTextureFromResource(resource);
bind();
if (pixels == null) {
return;
}
final int textureSize = (int) Math.sqrt(pixels.capacity());
GLStateManager.glTexSubImage3D(
GL30.GL_TEXTURE_2D_ARRAY,
0,
0, 0, layers - 1,
textureSize, textureSize,
1,
GL11.GL_RED,
GL11.GL_UNSIGNED_BYTE,
pixels
);
memFree(pixels);
}
@Override
public ByteBuffer generateGlyphData(int atlasId, GlyphData[] glyphs) {
if (atlasId == 0) {
final int size = getBoundTextureSize();
final int[] charWidth = BatchingFontRenderer.asciiCharWidths;
for (int i = 0; i < 256; i++) {
final int index = BatchingFontRenderer.lookupMcFontPosition((char) i);
final float uStart = ((index & 15) * 8) / 256.0F;
final float vStart = ((index >> 4) * 8) / 256.0F;
final float glyphW = charWidth[index] - 1F;
final float uSize = (charWidth[index] - 1F) / 256.0F;
final float vSize = 7.99F / 256.0F;
glyphs[i] = new GlyphData(
uStart, vStart,
glyphW,
uSize,
vSize
);
}
populateUnicodeGlyphs(0, unicodeGlyphData);
return getBoundTextureData(size);
}
populateUnicodeGlyphs(atlasId, glyphs);
return getTextureFromResource(getUnicodePage(atlasId));
}
private void populateUnicodeGlyphs(int atlasId, GlyphData[] glyphs) {
final byte[] glyphWidth = BatchingFontRenderer.glyphWidths;
for (int i = 0; i < 256; i++) {
final char chr = (char) ((atlasId * 256) | i);
int row = glyphWidth[chr] >>> 4;
int column = glyphWidth[chr] & 15;
float uStart = ((float)(chr % 16 * 16) + row) / 256F;
float vStart = ((float)((chr & 255) / 16 * 16)) / 256F;
float uSize = ((column + 1) - row - 0.02F) / 256F;
float vSize = 15.98F / 256.0F;
float glyphW = ((column + 1) - row) / 2.0F + 1.0F;
glyphs[i] = new GlyphData(
uStart, vStart,
glyphW - 1,
uSize,
vSize
);
}
}
private static ResourceLocation getUnicodePage(int atlasId) {
return new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", atlasId));
}
}
@@ -0,0 +1,45 @@
package com.gtnewhorizons.angelica.client.font.atlas;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import com.gtnewhorizons.angelica.client.font.GlyphData;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
public final class FontProviderSGA extends FontTextureArray {
public static FontProviderSGA create() {
final int size = getBoundTextureSize();
return new FontProviderSGA(size, 1, new short[] {0}, GL11.GL_NEAREST);
}
private FontProviderSGA(int size, int layers, short[] layersLookupArray, int filter) {
super(size, layers, layersLookupArray, filter);
loadAtlas(0);
}
@Override
public ByteBuffer generateGlyphData(int atlasId, GlyphData[] glyphs) {
if (atlasId == 0) {
final int[] charWidth = BatchingFontRenderer.sgaCharWidths;
for (int i = 0; i < 256; i++) {
final int index = BatchingFontRenderer.lookupMcFontPosition((char) i);
final float uStart = ((index & 15) * 8) / 256.0F;
final float vStart = ((index >> 4) * 8) / 256.0F;
final float glyphW = charWidth[index] - 1F;
final float uSize = (charWidth[index] - 1F) / 256.0F;
final float vSize = 7.99F / 256.0F;
glyphs[i] = new GlyphData(
uStart, vStart,
glyphW,
uSize,
vSize
);
}
return getBoundTextureData(this.textureSize);
}
return null;
}
}
@@ -0,0 +1,46 @@
package com.gtnewhorizons.angelica.client.font.atlas;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import com.gtnewhorizons.angelica.client.font.GlyphData;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
public final class FontProviderSplash extends FontTextureArray {
public static FontProviderSplash create() {
final int size = getBoundTextureSize();
return new FontProviderSplash(size, 1, new short[] {0}, GL11.GL_NEAREST);
}
private FontProviderSplash(int size, int layers, short[] layersLookupArray, int filter) {
super(size, layers, layersLookupArray, filter);
}
@Override
public ByteBuffer generateGlyphData(int atlasId, GlyphData[] glyphs) {
if (atlasId == 0) {
final int[] charWidth = BatchingFontRenderer.asciiCharWidths;
for (int i = 0; i < 256; i++) {
final int index = BatchingFontRenderer.lookupMcFontPosition((char) i);
final float size = textureSize;
final float uStart = ((index & 15) * 8) / size;
final float vStart = ((index >> 4) * 8) / size;
final float xAdvance = charWidth[index];
final float glyphW = charWidth[index] - 1F;
final float uSize = (charWidth[index] - 1F) / size;
final float vSize = 7.99F / size;
glyphs[i] = new GlyphData(
uStart, vStart,
glyphW,
uSize,
vSize
);
}
return getBoundTextureData(this.textureSize);
}
return null;
}
}
@@ -0,0 +1,290 @@
package com.gtnewhorizons.angelica.client.font.atlas;
import com.gtnewhorizon.gtnhlib.client.renderer.textures.TextureLoader;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.client.font.GlyphData;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
public abstract class FontTextureArray {
protected final int id;
protected final int textureSize;
protected final short[] layersLookup;
protected final GlyphData[][] atlasGlyphs = new GlyphData[256][];
static final boolean DUMP_ATLASES = false;
FontTextureArray(int size, int layers, short[] layersLookupArray, final int filter) {
this.layersLookup = layersLookupArray;
this.id = GLStateManager.glGenTextures();
this.textureSize = size;
bind();
GLStateManager.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MIN_FILTER, filter);
GLStateManager.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MAG_FILTER, filter);
GLStateManager.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER);
GLStateManager.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER);
final FloatBuffer buffer = memAllocFloat(4);
final long ptr = memAddress0(buffer);
memPutFloat(ptr, 1);
memPutFloat(ptr + 4, 1);
memPutFloat(ptr + 8, 1);
memPutFloat(ptr + 12, 1);
GLStateManager.glTexParameter(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_BORDER_COLOR, buffer);
memFree(buffer);
GLStateManager.glTexImage3D(
GL30.GL_TEXTURE_2D_ARRAY,
0,
GL30.GL_R8,
size, size, layers,
0,
GL11.GL_RED,
GL11.GL_UNSIGNED_BYTE,
(ByteBuffer) null
);
}
//
// public GlyphData[] addAtlas(int depth, ResourceLocation resource) {
// try {
// InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
// .getResource(resource).getInputStream();
// final GlyphData[] data = addAtlas(depth, ImageIO.read(inputstream));
// inputstream.close();
// return data;
// } catch (Exception e) {
// e.printStackTrace();
// System.out.println("Failed to initialize resource " + resource + ".");
// //initializedLayers[depth] = true;
// final GlyphData[] glyphData = new GlyphData[256];
// atlasGlyphs[depth] = glyphData;
// return glyphData;
// }
// }
public final GlyphData[] getAtlasGlyphs(final int atlasId) {
GlyphData[] glyphs = atlasGlyphs[atlasId];
if (glyphs == null) {
glyphs = addAtlas(atlasId);
}
return glyphs;
}
public final GlyphData getGlyphData(final char ch) {
return getAtlasGlyphs(ch >> 8)[ch & 255];
}
public final boolean isGlyphAvailable(char ch) {
return getAtlasGlyphs(ch >> 8)[ch & 255] != null;
}
public final int getDepth(char ch) {
return layersLookup[ch >> 8];
}
public int getTexture() {
return id;
}
protected final void loadAtlas(int atlasId) {
if (atlasGlyphs[atlasId] == null) {
addAtlas(atlasId);
}
}
public abstract ByteBuffer generateGlyphData(int atlasId, GlyphData[] glyphs);
protected final GlyphData[] addAtlas(int atlasId) {
final GlyphData[] glyphData = new GlyphData[256];
try {
final ByteBuffer pixels = generateGlyphData(atlasId, glyphData);
bind();
atlasGlyphs[atlasId] = glyphData;
if (pixels == null) {
return glyphData;
}
final int size = (int) Math.sqrt(pixels.capacity());
GLStateManager.glTexSubImage3D(
GL30.GL_TEXTURE_2D_ARRAY,
0,
0, 0, layersLookup[atlasId],
size, size,
1,
GL11.GL_RED,
GL11.GL_UNSIGNED_BYTE,
pixels
);
memFree(pixels);
if (DUMP_ATLASES) {
try {
saveLayerAsPNG(layersLookup[atlasId], size, "test" + layersLookup[atlasId] + ".png");
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
AngelicaMod.LOGGER.error("Failed to initialize glyph data for atlas id " + id + "! Did this get called in another thread?", e);
// Return the data (because it's correct), but don't initialize it (texture is still missing)
return glyphData;
}
return glyphData;
}
public void saveLayerAsPNG(int layer, int size, String fileName) {
ByteBuffer pixels = memAlloc(size * size);
GLStateManager.glGetTextureSubImage(
id,
0,
0, 0, layer,
size, size, 1,
GL11.GL_RED,
GL11.GL_UNSIGNED_BYTE,
size * size,
memAddress(pixels)
);
saveBufferAsPNG(size, pixels, fileName);
}
private void saveBufferAsPNG(int size, ByteBuffer pixels, String fileName) {
File dir = new File(Minecraft.getMinecraft().mcDataDir, "debug/texturearray");
if (!dir.exists()) dir.mkdirs();
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
int[] row = new int[size];
for (int y = 0; y < size; y++) {
int rowOffset = y * size;
for (int x = 0; x < size; x++) {
int i = rowOffset + x;
int c = pixels.get(i) & 0xFF;
row[x] = 0xFF000000 | (c << 16) | (c << 8) | c;
}
// Flip vertically because OpenGL origin is bottom-left
image.setRGB(0, size - 1 - y, size, 1, row, 0, size);
}
// Save PNG
try {
ImageIO.write(image, "PNG", new File(dir, fileName));
} catch (Exception e) {
e.printStackTrace();
}
}
public void bind() {
GLStateManager.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, id);
}
public void unbind() {
GLStateManager.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, 0);
}
public void delete() {
GLStateManager.glDeleteTextures(id);
}
static ByteBuffer getBoundTextureData(final int size) {
final ByteBuffer pixels = memAlloc(size * size);
GLStateManager.glGetTexImage(
GL11.GL_TEXTURE_2D,
0,
GL11.GL_RED,
GL11.GL_UNSIGNED_BYTE,
pixels
);
return pixels;
}
static int getBoundTextureSize() {
return GLStateManager.glGetTexLevelParameteri(
GL11.GL_TEXTURE_2D,
0,
GL11.GL_TEXTURE_WIDTH
);
}
static ByteBuffer getPixelBuffer(BufferedImage image) {
final int width = image.getWidth();
final int height = image.getHeight();
final ByteBuffer pixelBuffer = memAlloc(width * height);
long ptr = memAddress0(pixelBuffer);
// Cheap Path
if (image.getRaster().getDataBuffer() instanceof DataBufferInt dataBufferInt) {
final int[] pixelValues = dataBufferInt.getData();
for (int i : pixelValues) {
memPutByte(ptr++, (byte) ((i >> 24) & 0xFF));
}
pixelBuffer.limit(pixelValues.length);
return pixelBuffer;
}
Raster raster = image.getRaster();
ColorModel colorModel = image.getColorModel();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Object pixel = raster.getDataElements(x, y, null);
int alpha = colorModel.getAlpha(pixel);
memPutByte(ptr++, (byte) alpha);
}
}
pixelBuffer.limit(image.getHeight() * image.getWidth());
return pixelBuffer;
}
static ByteBuffer getTextureFromResource(ResourceLocation resource) {
try {
final BufferedImage image = TextureLoader.getBufferedImage(Minecraft.getMinecraft().getResourceManager()
.getResource(resource));
if (image.getWidth() != image.getHeight()) {
throw new UnsupportedOperationException(resource + "has a different width and height, they cannot get rendered!");
}
return getPixelBuffer(image);
} catch (IOException e) {
System.out.println("Failed to load resource " + resource + ".");
return null;
}
}
}
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.config.ConfigException;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.client.font.FontStrategist;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.gui.element.widget.IrisButton;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
@@ -13,8 +14,9 @@ import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.MathHelper;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import java.awt.Font;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -47,9 +49,9 @@ public class FontConfigScreen extends GuiScreen {
private IrisButton fwdButton;
private IrisButton backButton;
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
SliderClone.Option optFontQuality = new SliderClone.Option(18, 72, 1);
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optBrightness = new SliderClone.Option(0, 10, 1);
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
@@ -83,7 +85,7 @@ public class FontConfigScreen extends GuiScreen {
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
searchBox.setMaxStringLength(64);
fontList = new FontList();
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 0, this.width * 2 / 7, 20);
testArea.setMaxStringLength(512);
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
initButtons();
@@ -124,11 +126,79 @@ public class FontConfigScreen extends GuiScreen {
.height(sliderHeight)
.option(optFontQuality)
.initialValue(FontConfig.customFontQuality)
.setter(this::setFontQuality)
.setter(FontConfig::setCustomFontQuality)
.langKey("options.angelica.fontconfig.font_quality")
.formatString("%2.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optBrightness)
.initialValue(FontConfig.fontBrightness)
.setter(FontConfig::setFontBrightness)
.langKey("options.angelica.fontconfig.brightness")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphAspect)
.initialValue(FontConfig.glyphAspect)
.setter(FontConfig::setGlyphAspect)
.langKey("options.angelica.fontconfig.glyph_aspect")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphScale)
.initialValue(FontConfig.glyphScale)
.setter(FontConfig::setGlyphScale)
.langKey("options.angelica.fontconfig.glyph_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAStrength)
.initialValue(FontConfig.fontAAStrength)
.setter(FontConfig::setFontAAStrength)
.langKey("options.angelica.fontconfig.font_aa_strength")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAMode)
.initialValue(FontConfig.fontAAMode)
.setter(FontConfig::setFontAAMode)
.formatter(this::fontAAModeFormat)
.langKey("options.angelica.fontconfig.aamode")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optWhitespaceScale)
.initialValue(FontConfig.whitespaceScale)
.setter(value -> FontConfig.whitespaceScale = value.intValue())
.langKey("options.angelica.fontconfig.whitespace_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphSpacing)
.initialValue(FontConfig.glyphSpacing)
.setter(FontConfig::setGlyphSpacing)
.langKey("options.angelica.fontconfig.glyph_spacing")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
@@ -139,74 +209,6 @@ public class FontConfigScreen extends GuiScreen {
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphAspect)
.initialValue(FontConfig.glyphAspect)
.setter(value -> FontConfig.glyphAspect = value)
.langKey("options.angelica.fontconfig.glyph_aspect")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphScale)
.initialValue(FontConfig.glyphScale)
.setter(value -> FontConfig.glyphScale = value)
.langKey("options.angelica.fontconfig.glyph_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAStrength)
.initialValue(FontConfig.fontAAStrength)
.setter(value -> FontConfig.fontAAStrength = value.intValue())
.langKey("options.angelica.fontconfig.font_aa_strength")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAMode)
.initialValue(FontConfig.fontAAMode)
.setter(value -> FontConfig.fontAAMode = value.intValue())
.formatter(this::fontAAModeFormat)
.langKey("options.angelica.fontconfig.aamode")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optWhitespaceScale)
.initialValue(FontConfig.whitespaceScale)
.setter(value -> FontConfig.whitespaceScale = value)
.langKey("options.angelica.fontconfig.whitespace_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphSpacing)
.initialValue(FontConfig.glyphSpacing)
.setter(value -> FontConfig.glyphSpacing = value)
.langKey("options.angelica.fontconfig.glyph_spacing")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowCopies)
.initialValue(FontConfig.shadowCopies)
.setter(value -> FontConfig.shadowCopies = value.intValue())
.langKey("options.angelica.fontconfig.shadow_copies")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
@@ -222,7 +224,7 @@ public class FontConfigScreen extends GuiScreen {
.height(sliderHeight)
.option(optCustomFontScale)
.initialValue(FontConfig.customFontScale)
.setter(value -> FontConfig.customFontScale = value)
.setter(FontConfig::setCustomFontScale)
.langKey("options.angelica.fontconfig.custom_font_scale")
.formatString("x%3.2f")
.build()
@@ -279,10 +281,14 @@ public class FontConfigScreen extends GuiScreen {
pos = selectedPrimaryFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
} else {
FontConfig.customFontNamePrimary = "(none)";
}
pos = selectedFallbackFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
} else {
FontConfig.customFontNameFallback = "(none)";
}
FontStrategist.reloadCustomFontProviders();
@@ -307,7 +313,7 @@ public class FontConfigScreen extends GuiScreen {
}
private void toggleCustomFont(IrisButton button) {
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
FontConfig.setEnableCustomFont(!FontConfig.enableCustomFont);
applyChanges(false);
button.displayString = FontConfig.enableCustomFont ?
I18n.format("options.angelica.fontconfig.disable_custom_font") :
@@ -338,9 +344,8 @@ public class FontConfigScreen extends GuiScreen {
};
}
private float lastMouseX = 0;
private float lastMouseY = 0;
private long lastStillTime = 0;
private long hoverTimeStart = 0;
private GuiButton hoveredButton;
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
drawBackground(0);
@@ -374,7 +379,7 @@ public class FontConfigScreen extends GuiScreen {
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
);
}
int cumulativeY = 73;
int cumulativeY = 44 + 5 + 10;
for (String s : this.testAreaInfo) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
@@ -387,13 +392,38 @@ public class FontConfigScreen extends GuiScreen {
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
}
final String displayText = this.testArea.getText().replace('&', '§');
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
this.testArea.getText().replace('&', '§'), this.width / 4
displayText, this.width / 4
);
cumulativeY += this.testArea.height + 4;
for (String s : testText) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(this.width * 5 / 6, cumulativeY, 0);
drawCenteredString(this.fontRendererObj, s + "§r (1.00x Scale)", 0, 0, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT * 2;
GLStateManager.glPopMatrix();
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(this.width * 5 / 6, cumulativeY, 0);
GLStateManager.glScalef(0.75f, 0.75f, 1);
drawCenteredString(this.fontRendererObj, s + "§r (0.75x Scale)", 0, 0, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT * 2 * 0.75;
GLStateManager.glPopMatrix();
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(this.width * 5 / 6, cumulativeY, 0);
GLStateManager.glScalef(0.5f, 0.5f, 1);
drawCenteredString(this.fontRendererObj, s + "§r (0.5x Scale)", 0, 0, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT * 2 * 0.5;
GLStateManager.glPopMatrix();
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(this.width * 5 / 6, cumulativeY, 0);
GLStateManager.glScalef(0.33f, 0.33f, 1);
drawCenteredString(this.fontRendererObj, s + "§r (0.33x Scale)", 0, 0, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT * 2 * 0.33;
GLStateManager.glPopMatrix();
}
super.drawScreen(mouseX, mouseY, delta);
@@ -405,20 +435,19 @@ public class FontConfigScreen extends GuiScreen {
final int left = slider.xPosition;
final int right = slider.xPosition + slider.width;
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
if (mouseX == lastMouseX && mouseY == lastMouseY) {
if (lastStillTime == 0) {
lastStillTime = System.currentTimeMillis();
}
if (lastStillTime + 500L < System.currentTimeMillis()) {
displayTooltip(mouseX, mouseY, slider.tooltipKey);
}
} else {
lastStillTime = 0;
if (guiButton != hoveredButton) {
hoveredButton = guiButton;
hoverTimeStart = System.currentTimeMillis();
}
if (Mouse.isButtonDown(0) || Mouse.isButtonDown(1)) {
hoverTimeStart = System.currentTimeMillis();
}
if (hoverTimeStart + 300L < System.currentTimeMillis()) {
displayTooltip(mouseX, mouseY, slider.tooltipKey);
}
break;
}
lastMouseX = mouseX;
lastMouseY = mouseY;
}
private void displayTooltip(int x, int y, String langKey) {
@@ -511,19 +540,21 @@ public class FontConfigScreen extends GuiScreen {
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
if (mouseButton == 0) {
onElemClicked(slotIndex, false);
if (selectedPrimaryFontListPos == slotIndex) {
selectedPrimaryFontListPos = -1;
currentPrimaryFontName = "(none)";
} else {
selectedPrimaryFontListPos = slotIndex;
currentPrimaryFontName = displayedFonts.get(slotIndex).getFontName();
}
} else if (mouseButton == 1) {
onElemClicked(slotIndex, true);
}
}
protected void onElemClicked(int index, boolean rightClick) {
if (!rightClick) {
selectedPrimaryFontListPos = index;
currentPrimaryFontName = displayedFonts.get(index).getFontName();
} else {
selectedFallbackFontListPos = index;
currentFallbackFontName = displayedFonts.get(index).getFontName();
if (selectedFallbackFontListPos == slotIndex) {
selectedFallbackFontListPos = -1;
currentFallbackFontName = "(none)";
} else {
selectedFallbackFontListPos = slotIndex;
currentFallbackFontName = displayedFonts.get(slotIndex).getFontName();
}
}
applyChanges(false);
}
@@ -151,7 +151,7 @@ public class DeferredDrawBatcher {
*/
static long captureStateKey() {
final int activeUnit = GLStateManager.getActiveTextureUnit();
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(activeUnit).getBinding();
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(activeUnit).getTexture2D();
final int srcRgb = GLStateManager.getBlendState().getSrcRgb();
final int dstRgb = GLStateManager.getBlendState().getDstRgb();
final boolean blendEnabled = GLStateManager.getBlendMode().isEnabled();
@@ -0,0 +1,86 @@
package com.gtnewhorizons.angelica.client.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
public final class FallbackStreamingDrawer extends StreamingDrawer {
private int capacity;
private int pendingDataSize;
private int elementCount;
// private ByteBuffer mappedBuffer;
// private long mappedAddress;
private ByteBuffer buffer;
private long basePtr;
private void ensureCapacity(int needed) {
if (needed + pendingDataSize > capacity) {
resize(capacity * 2);
}
}
FallbackStreamingDrawer(int stride, int elementCapacity, VAOConsumer initVAO) {
super(initVAO, stride);
capacity = elementCapacity * stride;
final int totalCapacity = capacity;
buffer = memAlloc(totalCapacity);
basePtr = memAddress0(buffer);
}
@Override
public void drawElementsInstanced(int mode, int indices_count, int type, long indices_buffer_offset) {
if (this.vaoId == 0) {
super.initVAO();
}
buffer.limit(pendingDataSize);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STREAM_DRAW);
GL31.glDrawElementsInstanced(
GL11.GL_TRIANGLES,
6,
GL11.GL_UNSIGNED_SHORT,
0,
elementCount
);
elementCount = 0;
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
pendingDataSize = 0;
}
@Override
public long writeSection(int needed) {
ensureCapacity(needed);
long pointer = basePtr + pendingDataSize;
pendingDataSize += needed;
elementCount++;
return pointer;
}
public void resize(int newSectionSize) {
capacity = newSectionSize;
buffer = memRealloc(buffer, newSectionSize);
basePtr = memAddress0(buffer);
}
public void destroy() {
GLStateManager.glDeleteBuffers(vboId);
vboId = 0;
}
@Override
public boolean isEmpty() {
return elementCount == 0;
}
}
@@ -0,0 +1,224 @@
package com.gtnewhorizons.angelica.client.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* Triple-buffered persistent VAO/VBO streamer.
* <br>
* Unlike {@link com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer},
* this provides direct writes into persistently mapped GPU memory, bypassing
* temporary upload buffers for lower-overhead streaming.
*/
public final class PersistentStreamingDrawer extends StreamingDrawer {
private static final int NUM_SECTIONS = 4;
private static final int FLAGS =
GL30.GL_MAP_WRITE_BIT
| GL44.GL_MAP_PERSISTENT_BIT
| GL44.GL_MAP_COHERENT_BIT;
private int sectionSize;
//private int totalCapacity;
private ByteBuffer persistentMapping;
private long baseAddress;
private final long[] fences = new long[NUM_SECTIONS];
private int currentSection;
private int sectionWriteOffset;
private int sectionWriteStart;
private long sectionWritePointer;
private int sectionDrawCount;
private static int globalFPSCount;
PersistentStreamingDrawer(int stride, int elementCapacity, VAOConsumer initVAO) {
super(initVAO, stride);
sectionSize = elementCapacity * stride;
final int totalCapacity = getTotalCapacity();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GLStateManager.glBufferStorage(GL15.GL_ARRAY_BUFFER, totalCapacity, FLAGS);
persistentMapping = GLStateManager.glMapBufferRange(
GL15.GL_ARRAY_BUFFER, 0, totalCapacity, FLAGS);
baseAddress = memAddress0(persistentMapping);
sectionWritePointer = baseAddress;
if (persistentMapping == null)
throw new RuntimeException("Persistent mapping failed — check ARB_buffer_storage support.");
}
public static void incrementFPS() { //TODO make this use Display
globalFPSCount = (globalFPSCount + 1) % NUM_SECTIONS;
}
private void ensureCapacity(int needed) {
if (needed + sectionWriteOffset > sectionSize) {
System.out.println("Resizing to " + sectionSize * 2 + " bytes.");
new Exception().printStackTrace();
resize(sectionSize * 2);
}
}
@Override
public long writeSection(int needed) {
if (globalFPSCount != currentSection) {
lockCurrentSection();
advanceSection();
//System.out.println("Moving to section " + currentSection);
}
ensureCapacity(needed);
long pointer = sectionWritePointer + sectionWriteOffset;
sectionWriteOffset += needed;
sectionDrawCount++;
return pointer;
}
// AI slop resize, seems to work well
// Essentially copies any pending draw operations into the new buffer
public void resize(int newSectionSize) {
// Preserve pending batch
int sectionBase = currentSection * sectionSize;
int batchSize = (sectionBase + sectionWriteOffset) - sectionWriteStart;
long tempBuffer = nmemAllocChecked(batchSize);
memCopy(
sectionWritePointer,
tempBuffer,
batchSize
);
// Wait for GPU
for (int i = 0; i < NUM_SECTIONS; i++) {
waitForSection(i);
}
// Destroy old buffer
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GLStateManager.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GLStateManager.glDeleteBuffers(vboId);
// Create new buffer
sectionSize = newSectionSize;
sectionWriteStart = currentSection * sectionSize;
vboId = GLStateManager.glGenBuffers();
final int totalCapacity = getTotalCapacity();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GLStateManager.glBufferStorage(
GL15.GL_ARRAY_BUFFER,
totalCapacity,
FLAGS
);
persistentMapping = GLStateManager.glMapBufferRange(
GL15.GL_ARRAY_BUFFER,
0,
totalCapacity,
FLAGS
);
baseAddress = memAddress0(persistentMapping);
sectionWritePointer = baseAddress + sectionWriteStart;
sectionWriteOffset = batchSize;
vaoId = 0;
// Restore pending batch at offset 0
memCopy(
tempBuffer,
sectionWritePointer,
batchSize
);
nmemFree(tempBuffer);
}
public int getTotalCapacity() {
return sectionSize * NUM_SECTIONS;
}
public void destroy() {
for (int i = 0; i < NUM_SECTIONS; i++) {
if (fences[i] != 0) {
GLStateManager.glDeleteSync(fences[i]);
fences[i] = 0;
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GLStateManager.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GLStateManager.glDeleteBuffers(vboId);
persistentMapping = null;
vboId = 0;
}
private void lockCurrentSection() {
if (fences[currentSection] != 0L)
GLStateManager.glDeleteSync(fences[currentSection]);
fences[currentSection] = GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
private void advanceSection() {
currentSection = (currentSection + 1) % NUM_SECTIONS;
sectionWriteOffset = 0;
sectionWriteStart = currentSection * sectionSize;
sectionWritePointer = baseAddress + sectionWriteStart;
waitForSection(currentSection);
}
private void waitForSection(int section) {
if (fences[section] == 0) return;
while (true) {
int result = GLStateManager.glClientWaitSync(
fences[section], GL32.GL_SYNC_FLUSH_COMMANDS_BIT, 1_000_000_000L);
if (result == GL32.GL_ALREADY_SIGNALED || result == GL32.GL_CONDITION_SATISFIED) break;
if (result == GL32.GL_WAIT_FAILED)
throw new RuntimeException("glClientWaitSync failed for section " + section);
// GL_TIMEOUT_EXPIRED — retry
}
GLStateManager.glDeleteSync(fences[section]);
fences[section] = 0;
}
@Override
public void drawElementsInstanced(
int mode, int indices_count, int type, long indices_buffer_offset
) {
if (this.vaoId == 0) {
super.initVAO();
}
final int start = sectionWriteStart;
final int offset = start / stride;
GL42.glDrawElementsInstancedBaseInstance(
mode,
indices_count,
type,
indices_buffer_offset,
sectionDrawCount,
offset
);
sectionDrawCount = 0;
sectionWriteStart = currentSection * sectionSize + sectionWriteOffset;
}
@Override
public boolean isEmpty() {
return sectionDrawCount == 0;
}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.client.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
public abstract class StreamingDrawer {
protected int vboId;
protected int vaoId;
protected final VAOConsumer vaoConsumer;
protected final int stride;
private static final boolean supportsPersistent;
static {
final ContextCapabilities caps = GLStateManager.capabilities;
// supportsPersistent = caps.OpenGL44
// || (caps.GL_ARB_buffer_storage && caps.GL_ARB_base_instance);
supportsPersistent = false;
}
public static StreamingDrawer create(int stride, int elementCapacity, VAOConsumer vaoConsumer) {
return supportsPersistent ? new PersistentStreamingDrawer(stride, elementCapacity, vaoConsumer)
: new FallbackStreamingDrawer(stride, elementCapacity, vaoConsumer);
}
StreamingDrawer(VAOConsumer vaoConsumer, int stride) {
this.vboId = GL15.glGenBuffers();
this.vaoConsumer = vaoConsumer;
this.stride = stride;
}
public final void initVAO() {
this.vaoId = GLStateManager.glGenVertexArrays();
vaoConsumer.initialize(vaoId, vboId);
}
public final int getVBO() {
return vboId;
}
public final int getVAO() {
return vaoId;
}
public abstract long writeSection(int needed);
public abstract void drawElementsInstanced(
int mode, int indices_count, int type, long indices_buffer_offset
);
public abstract boolean isEmpty();
public final void delete() {
GL15.glDeleteBuffers(vboId);
GL30.glDeleteVertexArrays(vaoId);
}
public final void switchContext() {
GL30.glDeleteVertexArrays(vaoId);
vaoId = 0;
}
}
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.client.streaming;
public interface VAOConsumer {
/**
* Passes in a vertex array object (VAO) and a vertex buffer object (VBO) and expects the program to initialize
* the VAO with the correct pointers using things such as {@link org.lwjgl.opengl.GL20#glVertexAttribPointer}.
* @return The Data size that the VAO expects
*/
void initialize(int vao, int vbo);
}
@@ -1,39 +0,0 @@
package com.gtnewhorizons.angelica.compat;
import java.util.function.Function;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
public class GTNHLibCompat {
public static final boolean HAS_TEXT_PREPROCESSOR;
static {
boolean found = false;
try {
FontRendering.class.getMethod("setTextPreprocessor", Function.class);
found = true;
} catch (NoSuchMethodException ignored) {}
HAS_TEXT_PREPROCESSOR = found;
}
/** Only call when {@link #HAS_TEXT_PREPROCESSOR} is true. */
public static void registerPreprocessor() {
FontRendering.setTextPreprocessor(new FontRendering.TextPreprocessor() {
@Override
public String apply(String text) {
return AngelicaConfig.enableAmpersandConversion
? ColorCodeUtils.convertAmpersandToSectionX(text)
: text;
}
@Override
public boolean handlesAmpCodes() {
return AngelicaConfig.enableAmpersandConversion;
}
});
}
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.config;
import com.gtnewhorizon.gtnhlib.config.Config;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import static com.gtnewhorizons.angelica.client.font.BatchingFontRenderer.reloadShaders;
import static com.gtnewhorizons.angelica.client.font.BatchingFontRenderer.reloadFonts;
@Config(modid = "angelica", category = "customfont")
public class FontConfig {
@@ -8,6 +12,11 @@ public class FontConfig {
@Config.DefaultBoolean(false)
public static boolean enableCustomFont;
public static void setEnableCustomFont(boolean enableCustomFont) {
FontConfig.enableCustomFont = enableCustomFont;
reloadFonts();
}
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
@Config.DefaultString("(none)")
public static String customFontNamePrimary;
@@ -17,19 +26,29 @@ public class FontConfig {
public static String customFontNameFallback;
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
@Config.DefaultInt(30)
@Config.DefaultInt(32)
@Config.RangeInt(min = 6, max = 72)
public static int customFontQuality;
public static void setCustomFontQuality(float customFontQuality) {
FontConfig.customFontQuality = (int) customFontQuality;
reloadFonts();
}
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0F, max = 2F)
public static float fontShadowOffset;
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
@Config.DefaultInt(1)
@Config.RangeInt(min = 1, max = 8)
public static int shadowCopies;
@Config.Comment("Adds extra brightness to fonts. 0 = None, 10 = 2x brighter")
@Config.DefaultInt(0)
@Config.RangeInt(min = 0, max = 10)
public static int fontBrightness;
public static void setFontBrightness(float fontBrightness) {
FontConfig.fontBrightness = (int) fontBrightness;
BatchingFontRenderer.reloadShaders();
}
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
@Config.DefaultInt(2)
@@ -41,35 +60,70 @@ public class FontConfig {
@Config.RangeFloat(min = -1F, max = 1F)
public static float glyphAspect;
public static void setGlyphAspect(float glyphAspect) {
FontConfig.glyphAspect = glyphAspect;
reloadFonts();
}
@Config.Comment("Scale of each glyph, whitespace excluded.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float glyphScale;
public static void setGlyphScale(float glyphScale) {
FontConfig.glyphScale = glyphScale;
reloadFonts();
}
@Config.Comment("Whitespace scale.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float whitespaceScale;
@Config.DefaultInt(1)
@Config.RangeInt(min = 1, max = 3)
public static int whitespaceScale;
public static void setWhitespaceScale(int whitespaceScale) {
FontConfig.whitespaceScale = whitespaceScale;
reloadFonts();
}
@Config.Comment("Adds extra spacing between glyphs.")
@Config.DefaultFloat(0F)
@Config.RangeFloat(min = -2F, max = 2F)
public static float glyphSpacing;
public static void setGlyphSpacing(float glyphSpacing) {
FontConfig.glyphSpacing = glyphSpacing;
reloadFonts();
}
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
@Config.DefaultInt(2)
@Config.RangeInt(min = 0, max = 2)
public static int fontAAMode;
public static void setFontAAMode(float fontAAMode) {
FontConfig.fontAAMode = (int) fontAAMode;
reloadShaders();
}
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
@Config.DefaultInt(7)
@Config.RangeInt(min = 1, max = 24)
public static int fontAAStrength;
public static void setFontAAStrength(float fontAAStrength) {
FontConfig.fontAAStrength = (int) fontAAStrength;
reloadShaders();
}
@Config.Comment("Custom font scale multiplier, for bugfixes.")
@Config.DefaultFloat(1.5F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float customFontScale;
public static float customFontScale; //TODO wat
public static void setCustomFontScale(float customFontScale) {
FontConfig.customFontScale = customFontScale;
reloadFonts();
}
@Config.Comment("A shadow offset multiplier applied only to the Unicode font.")
@Config.DefaultFloat(0.5F)
@@ -82,4 +136,9 @@ public class FontConfig {
"See for additional details: https://github.com/GTNewHorizons/Angelica/issues/1239#issuecomment-3729877936"})
@Config.DefaultBoolean(true)
public static boolean enableGlyphReplacements;
public static void setEnableGlyphReplacements(boolean enableGlyphReplacements) {
FontConfig.enableGlyphReplacements = enableGlyphReplacements;
reloadFonts();
}
}
@@ -54,7 +54,6 @@ public enum Mixins implements IMixins {
, "angelica.gui.MixinGuiOptions"
, "angelica.optimizations.MixinRendererLivingEntity"
, "angelica.rendering.MixinRenderGlobal_SelectionBox"
, "angelica.gui.MixinGuiIngameForge_ModernF3"
)
),
@@ -10,4 +10,8 @@ public interface FontRendererAccessor {
BatchingFontRenderer angelica$getBatcher();
void angelica$bindTexture(ResourceLocation location);
int[] angelica$getCharWidths();
byte[] angelica$getGlyphWidths();
}
@@ -4,6 +4,7 @@ import biomesoplenty.api.content.BOPCBlocks;
import com.google.common.base.Objects;
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
@@ -109,6 +110,7 @@ public final class ClientProxy extends CommonProxy {
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(new EmissiveTextureAutoloader());
ModelRegistry.registerModid(MOD_ID);
ColorCodeUtils.initPreprocessor();
blockError = new BlockError();
}
@@ -0,0 +1,56 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class InstancedHelper {
private static int quadVBO;
private static int quadEBO;
public static int getQuadVBO() {
if (quadVBO > 0) return quadVBO;
quadVBO = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, quadVBO);
final ByteBuffer data = memAlloc(32);
final long address = memAddress0(data);
memPutFloat(address, 0); memPutFloat(address + 4, 0);
memPutFloat(address + 8, 1); memPutFloat(address + 12, 0);
memPutFloat(address + 16, 0); memPutFloat(address + 20, 1);
memPutFloat(address + 24, 1); memPutFloat(address + 28, 1);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
memFree(data);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return quadVBO;
}
public static int getQuadEBO() {
if (quadEBO > 0) return quadEBO;
quadEBO = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, quadEBO);
final ByteBuffer data = memAlloc(12);
final long address = memAddress0(data);
// triangle 1
memPutShort(address, (short) 0);
memPutShort(address + 2, (short) (2));
memPutShort(address + 4, (short) (1));
// triangle 2
memPutShort(address + 6, (short) (2));
memPutShort(address + 8, (short) (3));
memPutShort(address + 10, (short) (1));
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
memFree(data);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
return quadEBO;
}
}
@@ -1,6 +1,7 @@
package me.jellysquid.mods.sodium.client.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
@@ -591,8 +592,11 @@ public class SodiumGameOptionPages {
.setName(I18n.format("options.angelica.texteffects.ampersand_conversion"))
.setTooltip(I18n.format("options.angelica.texteffects.ampersand_conversion.tooltip", "\\&"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableAmpersandConversion = value,
opts -> AngelicaConfig.enableAmpersandConversion)
.setBinding((opts, value) -> {
AngelicaConfig.enableAmpersandConversion = value;
ColorCodeUtils.initPreprocessor();
},
opts -> AngelicaConfig.enableAmpersandConversion)
.build())
.build());
@@ -166,7 +166,7 @@ options.dynamiclights.entity.tooltip=Toggle dynamic lighting for %s entities. Di
options.angelica.search.no_results=No matching options
options.angelica.fontconfig=Font Configuration...
options.angelica.fontconfig.title=Font Configuration
options.angelica.fontconfig.currentfonts=Primary font: §e%s§r Fallback font: §b%s
options.angelica.fontconfig.currentfonts=Primary font: §e%s (LMB)§r Fallback font: §b%s (RMB)
options.angelica.fontconfig.searchprompt=§7§oSearch fonts...
options.angelica.fontconfig.testareaprompt=§7§oTest area...
options.angelica.fontconfig.testareainfo=Enter text below to see how it renders. Use & for formatting.
@@ -177,7 +177,7 @@ options.angelica.fontconfig.reset_values=Reset Values
options.angelica.fontconfig.no_fonts_warning=Custom fonts are not available on your system. Check logs for details.
options.angelica.fontconfig.font_quality=Custom Font Quality: %s
options.angelica.fontconfig.shadow_offset=Shadow Offset: %s
options.angelica.fontconfig.shadow_copies=Shadow Copies: %s
options.angelica.fontconfig.brightness=Brightness: %s
options.angelica.fontconfig.bold_copies=Bold Copies: %s
options.angelica.fontconfig.glyph_scale=Glyph Scale: %s
options.angelica.fontconfig.glyph_aspect=Glyph Aspect: %s
@@ -191,7 +191,7 @@ options.angelica.fontconfig.custom_font_scale=Custom Font Scale: %s
options.angelica.fontconfig.shadow_offset_unicode=Unicode Shadow Offset: %s
options.angelica.fontconfig.font_quality.tooltip=The quality at which custom fonts are rendered, making them less pixelated at the expense of some performance and memory.
options.angelica.fontconfig.shadow_offset.tooltip=The distance at which the fonts' shadows are drawn. Also affects the way §lbold§r text is drawn.
options.angelica.fontconfig.shadow_copies.tooltip=The number of shadows to be drawn behind text, all offset so as to provide a fake 3D effect.
options.angelica.fontconfig.brightness.tooltip=Increases the brightness of the text, resulting in more readable text for lower resolutions. Most noticeable for custom fonts.
options.angelica.fontconfig.bold_copies.tooltip=The number of text copies drawn when rendering bold text. Can improve the appearance of bold text with thinner custom fonts.
options.angelica.fontconfig.glyph_scale.tooltip=The scale of each individual glyph. Extreme values might make your game unplayable.
options.angelica.fontconfig.glyph_aspect.tooltip=The aspect ratio of each individual glyph.
@@ -0,0 +1,120 @@
#version 330 core
uniform sampler2DArray sampler0; // Minecraft fonts
#ifdef MULTISAMPLING
uniform sampler2DArray sampler1; // Custom fonts
#endif
uniform float strength;
flat in vec4 tB; // uMin, vMin, uMax, vMax
flat in vec4 color;
flat in uint layer;
#ifdef MULTISAMPLING
flat in uint secondTexture;
#endif
in vec2 texCoord;
out vec4 fragColor;
/*
Hacky MSAA and anisotropic filtering. Cursed beyond belief. There _may_ have been simpler means of
achieving the produced effects, but this appears to work without noticeable performance losses.
*/
float totalWt;
float aaSample(vec2 uv, float du, float dv, float factorU, float factorV) {
float distSquared = sqrt(du * du + dv * dv);
float weight = exp(-distSquared / 6);
totalWt += weight;
float finalU = uv.x + factorU * du;
float finalV = uv.y + factorV * dv;
if (finalU < tB.x || finalV < tB.y || finalU > tB.z || finalV > tB.w) return 0.0f;
return weight * texture(sampler0, vec3(finalU, finalV, layer)).r;
}
float easeOut(float t) {
t = clamp(t, 0.0, 1.0);
return 1.0 - pow(1.0 - t, 2.0);
}
void main() {
vec4 col = color;
float alpha = 0;
// For custom fonts, don't apply the AA as it results in a blurry look.
#ifdef MULTISAMPLING
if (secondTexture != 0u) {
alpha = texture(sampler1, vec3(texCoord, layer)).r;
if (alpha < 0.05) discard;
#ifdef FONT_BRIGHTNESS
alpha = min(alpha * FONT_BRIGHTNESS, 1);
#endif
fragColor = vec4(color.rgb, alpha * color.a);
return;
}
#endif
#if AA_MODE == 0
alpha = texture(sampler0, vec3(texCoord, layer)).r;
#else
vec2 texScaled = texCoord * AA_STRENGTH;
float fu = abs(dFdx(texScaled.x)) + abs(dFdy(texScaled.x));
float fv = abs(dFdx(texScaled.y)) + abs(dFdy(texScaled.y));
float res = 0;
totalWt = 0;
#if AA_MODE == 1
res += aaSample(texCoord, 2, 6, fu, fv);
res += aaSample(texCoord, 6, -2, fu, fv);
res += aaSample(texCoord, -2, -6, fu, fv);
res += aaSample(texCoord, -6, 2, fu, fv);
#elif AA_MODE == 2
res += aaSample(texCoord, 1, 1, fu, fv);
res += aaSample(texCoord, -1, -3, fu, fv);
res += aaSample(texCoord, -3, 2, fu, fv);
res += aaSample(texCoord, 4, -1, fu, fv);
res += aaSample(texCoord, -5, -2, fu, fv);
res += aaSample(texCoord, 2, 5, fu, fv);
res += aaSample(texCoord, 5, 3, fu, fv);
res += aaSample(texCoord, 3, -5, fu, fv);
res += aaSample(texCoord, -2, 6, fu, fv);
res += aaSample(texCoord, 0, -7, fu, fv);
res += aaSample(texCoord, -4, -6, fu, fv);
res += aaSample(texCoord, -6, 4, fu, fv);
res += aaSample(texCoord, -8, 0, fu, fv);
res += aaSample(texCoord, 7, -4, fu, fv);
res += aaSample(texCoord, 6, 7, fu, fv);
res += aaSample(texCoord, -7, -8, fu, fv);
#endif
alpha = res / totalWt;
#endif
#ifdef FONT_BRIGHTNESS
alpha = min(alpha * FONT_BRIGHTNESS, 1);
#endif
if (alpha < 0.05) discard;
fragColor = vec4(color.rgb, alpha * color.a);
}
@@ -0,0 +1,42 @@
#version 330 core
layout(location = 0) in vec2 quadCorner;
layout(location = 1) in vec4 glyphRect;
layout(location = 2) in vec4 uvRect; // uMin, vMin, uSize, vSize
layout(location = 3) in vec4 aColor;
layout(location = 4) in uint aLayer;
layout(location = 5) in uint flags;
uniform mat4 u_MVPMatrix;
flat out vec4 tB;
flat out vec4 color;
flat out uint layer;
#ifdef MULTISAMPLING
flat out uint secondTexture;
#endif
out vec2 texCoord;
void main() {
vec2 pos = glyphRect.xy + quadCorner * glyphRect.zw;
if ((flags & FLAG_ITALIC) != 0u) {
pos.x += (1.0 - 2.0 * quadCorner.y);
}
vec2 uv = uvRect.xy + quadCorner * uvRect.zw;
if ((flags & FLAG_DINNERBONE) != 0u) {
uv = vec2(uvRect.x + quadCorner.x * uvRect.z, uvRect.y + (1.0 - quadCorner.y) * uvRect.w);
}
gl_Position = u_MVPMatrix * vec4(pos, 0.0, 1.0);
texCoord = uv;
color = aColor;
tB = vec4(uvRect.xy, uvRect.x + uvRect.z, uvRect.y + uvRect.w);
layer = aLayer;
#ifdef MULTISAMPLING
secondTexture = (flags & FLAG_SECOND_TEXTURE);
#endif
}
@@ -0,0 +1,320 @@
#version 330 core
// ----- GLOW -----
#define USE_GLOW
//#define RGB_GLOW
#define GLOW_COLOR vec3(0.882, 0.686, 1.0)
// ----- SPARKLES -----
#define USE_SPARKLES
#define SPARKLE_COLOR vec3(1)
// ----- WAVE -----
//#define USE_WAVE
#define WAVE_COLOR vec3(1)
// ----- TEXT -----
#define TEXT_COLOR vec3(0, 0, 0)
// ----- SPOTLIGHT -----
#define USE_SPOTLIGHT
#define SPOTLIGHT_COLOR GLOW_COLOR
#define SPOTLIGHT_STRENGTH 0.333
uniform sampler2D textFBO;
uniform sampler2D sceneFBO;
uniform vec2 uTexelSize;
uniform float uTime;
uniform int uScale; //TODO integrate this
uniform vec4 uTexBounds;
const int MAX_RADIUS = 16;
in vec2 texCoord;
out vec4 fragColor;
vec3 mixHexColors(int colorA, int colorB, float t) {
t = clamp(t, 0.0, 1.0);
// Convert packed hex ints to normalized RGB vec3
vec3 a = vec3(
float((colorA >> 16) & 0xFF),
float((colorA >> 8) & 0xFF),
float(colorA & 0xFF)
) / 255.0;
vec3 b = vec3(
float((colorB >> 16) & 0xFF),
float((colorB >> 8) & 0xFF),
float(colorB & 0xFF)
) / 255.0;
return mix(a, b, t);
}
float random(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
vec2 random2(vec2 p) {
float r = random(p);
return vec2(r, random(p + r));
}
float glow(vec2 uv) {
int uRadius = 6;
float dist = 1e10;
for (int y = -MAX_RADIUS; y <= MAX_RADIUS; y++) {
for (int x = -MAX_RADIUS; x <= MAX_RADIUS; x++) {
if (abs(x) > uRadius || abs(y) > uRadius) continue;
// Manhatten for better readability
//float currDist = abs(x) + abs(y);
float currDist = max(abs(x), abs(y)) + min(abs(x), abs(y)) * 0.5;
vec2 offset = vec2(x, y) * uTexelSize;
if (texture(textFBO, vec2(uv.x + offset.x, uv.y + offset.y)).a > 0.1) {
if (dist > currDist) {
dist = currDist;
}
}
}
}
if (dist == 1e10) return 0;
dist = dist - 1.5;
//TODO
//float jitter = (fract(sin(uTime + (texCoord.x + texCoord.y) * 2) * 43758.0) - 0.5) * 5;
//dist += jitter;
//TODO
//if (dist <= 0) return 1;
//else return 0;
float timeGlow = (sin(uTime * 2.0)) * 0.2;
float d = dist;
//d = d / (2 + timeGlow);
float glow = exp(-d * 0.7);
//float d = (dist / (1 + (sin(uTime * 2.0)) * 0.1));
//return min(1.0 / pow(d, 1.2), 1);
return min(glow, 1);
}
// Adapted from https://www.shadertoy.com/view/3d33zM
float starfield(vec2 uv) {
vec2 resolution = 1.0 / uTexelSize;
vec2 fragCoord = uv * resolution;
float widthHeightRatio = resolution.x / resolution.y;
float t = uTime * 0.015;
float dist = 0.0;
const float layers = 4.0;
const float scale = 256.0;
const float star_size = 1.0 / 10;
vec2 centre = vec2(0.5);
for (float i = 0.0; i < layers; i++) {
float depth = fract(i/layers);
centre.x = 0.5 + 0.1 * t * depth;
centre.y = 0.5 + 0.1 * t * depth;
vec2 uv = centre - fragCoord / resolution;
uv.y /= widthHeightRatio;
uv *= mix(scale, 0.0, depth);
vec2 index = floor(uv);
vec2 seed = 20.0 * i + index;
vec2 local_uv = fract(i + uv) - 0.5;
vec2 pos = 0.8 * (random2(seed) - 0.5);
float phase = 128.0 * random(seed);
vec2 d = (local_uv - pos) * star_size;
float len = length(d);
// Core
float radial = max(0.0, 1.0 - len * 3.5);
radial = pow(radial, 30.0);
// Fade spikes with distance from center
float spikeFade = pow(max(0.0, 1.0 - len * 6.0), 4.0);
// Compact sparkle spikes
float spikeX = pow(max(0.0, 1.0 - abs(d.x) * 35.0), 8.0);
float spikeY = pow(max(0.0, 1.0 - abs(d.y) * 35.0), 8.0);
float spikeD1 = pow(max(0.0, 1.0 - abs(d.x + d.y) * 25.0), 8.0);
float spikeD2 = pow(max(0.0, 1.0 - abs(d.x - d.y) * 25.0), 8.0);
// Apply radial fade to spikes
float spikes =
(spikeX + spikeY) * 0.12 +
(spikeD1 + spikeD2) * 0.05;
spikes *= spikeFade;
// Final sparkle
float sparkle = radial * 0.9 + spikes;
float speed = mix(0.5, 3.0, random(seed + 17.0));
float twinkle =
sin(uTime * speed + phase) * 0.5 + 0.5;
twinkle = pow(twinkle, 6.0);
float brightness =
mix(0.2, 1.0, random(seed + 42.0));
dist += sparkle * twinkle * brightness * depth;
}
return dist * 2;
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec3 rgb(vec2 uv) {
float h = (uv.x + uv.y) * 4 + uTime / 10; // 0 → 1 hue
return hsv2rgb(vec3(h, 1.0, 1.0));
}
float wave(vec2 uv) {
float speed = 0.25;
float pos = fract(uTime * speed);
float lines = 5.0;
float x = uv.x * lines;
float id = floor(x);
float local = fract(x);
float dist = abs(local - pos);
float thickness = 0.1;
return 1.0 - smoothstep(0.0, thickness, dist);
}
float spotlight(
vec2 uv,
float minX,
float maxX,
float minY,
float maxY
) {
vec2 center = vec2(
(minX + maxX) * 0.5,
(minY + maxY) * 0.5
);
vec2 radius = vec2(
(maxX - minX) * 0.5,
(maxY - minY) * 0.5
);
vec2 p = abs((uv - center) / radius);
float dist = pow(
pow(p.x, 4.0) + pow(p.y, 4.0),
1.0 / 4.0
);
return (1.0 - smoothstep(0.0, 1.0, dist)) * SPOTLIGHT_STRENGTH;
}
//0xfda63a orang
//0xfda63a
//0xe1afff
void main() {
float minX = uTexBounds.x;
float maxX = uTexBounds.y;
float minY = uTexBounds.z;
float maxY = uTexBounds.w;
vec2 relativeUV = vec2(texCoord.x - minX, texCoord.y - minY);
vec3 overlay = vec3(0);
#ifdef USE_SPARKLES
if (
texCoord.x >= minX - uTexelSize.x * 2 &&
texCoord.x <= maxX + uTexelSize.x * 2 &&
texCoord.y >= minY - uTexelSize.y * 4 &&
texCoord.y <= maxY + uTexelSize.y * 4
) {
float sparkle = starfield(vec2(texCoord.x - minX, texCoord.y - minY));
if (sparkle > 0.05) {
// glow sits in front of sparkle
overlay = mix(overlay, SPARKLE_COLOR, sparkle);
}
}
#endif
#ifdef USE_SPOTLIGHT
overlay = mix(overlay, SPOTLIGHT_COLOR, spotlight(texCoord, minX, maxX, minY, maxY));
#endif
#ifdef USE_GLOW
float glowVal = glow(texCoord);
if (glowVal > 0.1) {
#ifdef RGB_GLOW
vec3 glowColor = rgb(relativeUV);
#else
vec3 glowColor = GLOW_COLOR;
#endif
#ifdef USE_WAVE
glowColor = mix(glowColor, vec3(1, 1, 1), wave(relativeUV));
#endif
overlay = mix(overlay, glowColor, glowVal);
}
#endif
// Since Minecraft doesn't have HDR colors, I need an alternative approach to blending the colors together
vec4 scene = texture(sceneFBO, texCoord);
vec3 base = scene.rgb;
vec3 finalColor = vec3(1.0) - (vec3(1.0) - base) * (vec3(1.0) - overlay);
// Mix based on text color
vec4 textColor = texture(textFBO, texCoord);
if (textColor.a > 0.1) {
finalColor = mix(finalColor, TEXT_COLOR, textColor.a);
}
fragColor = vec4(finalColor, 1.0);
}
@@ -0,0 +1,12 @@
#version 330 core
layout(location = 0) in vec2 a_Position;
uniform mat4 u_MVPMatrix;
out vec2 texCoord;
void main() {
gl_Position = u_MVPMatrix * vec4(a_Position, 0.0, 1.0);
texCoord = (gl_Position.xy / gl_Position.w) * 0.5 + 0.5;
}
@@ -0,0 +1,24 @@
#version 330 core
// Given Uniforms:
uniform sampler2D textFBO; // The texture of the text's Framebuffer (use this for the post-processing)
uniform sampler2D sceneFBO; // The texture of the scene (for better scene blending)
uniform vec2 uTexelSize; // The size of each pixel (1 / displayWidth, 1 / displayHeight)
uniform float uTime; // The time, measured in seconds
uniform int uScale; // The ScaledResolution scale
uniform vec4 uTexBounds; // The bounds of the texture
// In variables:
in vec2 texCoord;
// Out variables:
out vec4 fragColor;
// Example:
void main() {
vec4 scene = texture(sceneFBO, texCoord);
vec4 textColor = texture(textFBO, texCoord);
fragColor = vec4(mix(vec3(0, 0, 0), textColor.rgb, textColor.a), 1);
}
@@ -1,79 +0,0 @@
#version 330 core
uniform sampler2D sampler;
uniform int aaMode;
uniform float strength;
uniform float alphaTestRef;
in vec4 color;
in vec4 tB;
in vec2 texCoord;
out vec4 fragColor;
/*
Hacky MSAA and anisotropic filtering. Cursed beyond belief. There _may_ have been simpler means of
achieving the produced effects, but this appears to work without noticeable performance losses.
*/
float totalWt;
float txSample(vec2 uv, float du, float dv, float factorU, float factorV) {
float distSquared = sqrt(du * du + dv * dv);
float weight = exp(-distSquared / 6);
totalWt += weight;
float finalU = uv.x + factorU * du;
float finalV = uv.y + factorV * dv;
if (finalU < tB.x || finalU > tB.y || finalV < tB.z || finalV > tB.w) {
return 0.0f;
}
return weight * texture(sampler, vec2(finalU, finalV)).a;
}
void main() {
vec4 col = color;
float original_alpha = col.a;
if (texCoord.s != 0 || texCoord.t != 0) {
if (aaMode == 0) {
// No AA - single texture sample
col.a = original_alpha * texture(sampler, texCoord).a;
} else {
vec2 texScaled = texCoord * strength;
float res = 0;
float fu = abs(dFdx(texScaled.x)) + abs(dFdy(texScaled.x));
float fv = abs(dFdx(texScaled.y)) + abs(dFdy(texScaled.y));
totalWt = 0;
if (aaMode == 1) {
res += txSample(texCoord, 2, 6, fu, fv);
res += txSample(texCoord, 6, -2, fu, fv);
res += txSample(texCoord, -2, -6, fu, fv);
res += txSample(texCoord, -6, 2, fu, fv);
} else {
res += txSample(texCoord, 1, 1, fu, fv);
res += txSample(texCoord, -1, -3, fu, fv);
res += txSample(texCoord, -3, 2, fu, fv);
res += txSample(texCoord, 4, -1, fu, fv);
res += txSample(texCoord, -5, -2, fu, fv);
res += txSample(texCoord, 2, 5, fu, fv);
res += txSample(texCoord, 5, 3, fu, fv);
res += txSample(texCoord, 3, -5, fu, fv);
res += txSample(texCoord, -2, 6, fu, fv);
res += txSample(texCoord, 0, -7, fu, fv);
res += txSample(texCoord, -4, -6, fu, fv);
res += txSample(texCoord, -6, 4, fu, fv);
res += txSample(texCoord, -8, 0, fu, fv);
res += txSample(texCoord, 7, -4, fu, fv);
res += txSample(texCoord, 6, 7, fu, fv);
res += txSample(texCoord, -7, -8, fu, fv);
}
res /= totalWt;
col.a = original_alpha * res;
}
}
if (col.a <= alphaTestRef) {
discard;
}
fragColor = col;
}
@@ -1,19 +0,0 @@
#version 330 core
layout(location = 0) in vec2 a_Position;
layout(location = 1) in vec2 a_TexCoord0;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec4 a_TexBounds;
uniform mat4 u_MVPMatrix;
out vec4 tB;
out vec4 color;
out vec2 texCoord;
void main() {
gl_Position = u_MVPMatrix * vec4(a_Position, 0.0, 1.0);
texCoord = a_TexCoord0;
color = a_Color;
tB = a_TexBounds;
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.mixins.early.angelica;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.client.streaming.PersistentStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
@@ -9,7 +11,6 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.GameSettings;
import org.embeddedt.embeddium.impl.render.frame.RenderAheadManager;
import org.lwjgl.input.Keyboard;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -125,5 +126,6 @@ public abstract class MixinMinecraft {
@Inject(method = "runTick", at = @At("RETURN"))
private void angelica$streamingBufferEndFrame(CallbackInfo ci) {
TessellatorStreamingDrawer.endFrame();
PersistentStreamingDrawer.incrementFPS();
}
}
@@ -1,15 +1,11 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.fontrenderer;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizon.gtnhlib.util.font.IFontParameters;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_LENGTH;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.compat.GTNHLibCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
@@ -17,23 +13,26 @@ import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Random;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_LENGTH;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
/**
* Fixes the horrible performance of FontRenderer
* @author eigenraven
*/
@Mixin(FontRenderer.class)
@Mixin(value = FontRenderer.class, priority = 999)
public abstract class MixinFontRenderer implements FontRendererAccessor, IFontParameters {
@Shadow
@@ -115,21 +114,27 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
@Shadow(remap = false)
protected abstract void bindTexture(ResourceLocation location);
@Shadow
protected abstract void renderStringAtPos(String p_78255_1_, boolean p_78255_2_);
@Shadow
protected abstract void enableAlpha();
@Shadow
protected abstract void resetStyles();
@Unique
public BatchingFontRenderer angelica$batcher;
@Inject(method = "<init>", at = @At("TAIL"))
private void angelica$injectBatcher(GameSettings settings, ResourceLocation fontLocation, TextureManager texManager,
boolean unicodeMode, CallbackInfo ci) {
angelica$batcher = new BatchingFontRenderer((FontRenderer) (Object) this, this.charWidth, this.colorCode, this.locationFontTexture);
if (GTNHLibCompat.HAS_TEXT_PREPROCESSOR) {
GTNHLibCompat.registerPreprocessor();
}
angelica$batcher = new BatchingFontRenderer((FontRenderer) (Object) this, this.colorCode, this.locationFontTexture);
}
@Unique
private static boolean angelica$charInRange(char what, char fromInclusive, char toInclusive) {
return (what >= fromInclusive) && (what <= toInclusive);
@Inject(method = "readFontTexture", at = @At("RETURN"))
private void angelica$onReadFontTexture(CallbackInfo ci) {
angelica$batcher.initializeTextures();
}
/**
@@ -137,101 +142,127 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
* Batched font renderer is not compatible with display lists, and won't really
* help performance when display lists are already being used anyway.
*/
@Inject(method = "drawString(Ljava/lang/String;IIIZ)I", at = @At("HEAD"), cancellable = true)
public void angelica$BatchedFontRendererDrawString(String text, int x, int y, int argb, boolean dropShadow, CallbackInfoReturnable<Integer> cir)
{
if (GLStateManager.getListMode() == 0) {
cir.setReturnValue(angelica$drawStringBatched(text, x, y, argb, dropShadow));
/**
* @author Sisyphus
* @reason Optimize Font rendering performance
*/
@Overwrite
public int drawString(String text, int x, int y, int argb, boolean dropShadow) {
if (text == null || text.isEmpty()) return 0;
text = FontRendering.preprocessText(text);
if (DisplayListManager.getListMode() == 0) {
return angelica$drawStringBatched(text, x, y, argb, dropShadow);
}
this.enableAlpha();
this.resetStyles();
int width;
if (dropShadow) {
width = this.renderString(text, x + 1, y + 1, argb, true);
width = Math.max(width, this.renderString(text, x, y, argb, false));
} else {
width = this.renderString(text, x, y, argb, false);
}
return width;
}
/**
* See above explanation about batched renderer in display lists.
* @author Sisyphus
* @reason Optimize Font rendering performance
*/
@Inject(method = "renderString", at = @At("HEAD"), cancellable = true)
public void angelica$BatchedFontRendererRenderString(String text, int x, int y, int argb, boolean dropShadow, CallbackInfoReturnable<Integer> cir) {
if (GLStateManager.getListMode() == 0) {
cir.setReturnValue(angelica$drawStringBatched(text, x, y, argb, dropShadow));
@Overwrite
private int renderString(String text, int x, int y, int argb, boolean dropShadow) {
if (text == null || text.isEmpty()) return 0;
text = FontRendering.preprocessText(text);
if (DisplayListManager.getListMode() == 0) {
return angelica$drawStringBatched(text, x, y, argb, dropShadow);
}
if (this.bidiFlag) {
text = this.bidiReorder(text);
}
if ((argb & 0xfc000000) == 0) {
argb |= 0xff000000;
}
this.red = (float) (argb >> 16 & 255) / 255.0F;
this.blue = (float) (argb >> 8 & 255) / 255.0F;
this.green = (float) (argb & 255) / 255.0F;
this.alpha = (float) (argb >> 24 & 255) / 255.0F;
this.posX = (float) x;
this.posY = (float) y;
this.renderStringAtPos(text, dropShadow);
return (int) this.posX;
}
@Override
public int angelica$drawStringBatched(String text, int x, int y, int argb, boolean dropShadow) {
if (text == null)
{
return 0;
public final int angelica$drawStringBatched(String text, int x, int y, int argb, boolean dropShadow) {
if (this.bidiFlag) {
text = this.bidiReorder(text);
}
else
{
text = ColorCodeUtils.convertAmpersandToSectionX(text);
if (this.bidiFlag)
{
text = this.bidiReorder(text);
}
if ((argb & 0xfc000000) == 0)
{
argb |= 0xff000000;
}
this.red = (float)(argb >> 16 & 255) / 255.0F;
this.blue = (float)(argb >> 8 & 255) / 255.0F;
this.green = (float)(argb & 255) / 255.0F;
this.alpha = (float)(argb >> 24 & 255) / 255.0F;
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
this.posX = (float)x;
this.posY = (float)y;
return (int) angelica$batcher.drawString(x, y, argb, dropShadow, unicodeFlag, text, 0, text.length());
if ((argb & 0xfc000000) == 0) {
argb |= 0xff000000;
}
this.red = (float) (argb >> 16 & 255) / 255.0F;
this.blue = (float) (argb >> 8 & 255) / 255.0F;
this.green = (float) (argb & 255) / 255.0F;
this.alpha = (float) (argb >> 24 & 255) / 255.0F;
this.posX = (float) x;
this.posY = (float) y;
return angelica$batcher.drawString(text, x, y, argb, dropShadow);
}
/**
* @author Sisyphus
* @reason Optimize Font rendering performance
*/
@Overwrite
public int getCharWidth(char c) {
return (int) angelica$batcher.getCharWidthFine(c);
}
@Override
public BatchingFontRenderer angelica$getBatcher() {
public final byte[] angelica$getGlyphWidths() {
return glyphWidth;
}
@Override
public final int[] angelica$getCharWidths() {
return this.charWidth;
}
@Override
public final void angelica$bindTexture(ResourceLocation location) {
this.bindTexture(location);
}
@Override
public final BatchingFontRenderer angelica$getBatcher() {
return angelica$batcher;
}
@Override
public void angelica$bindTexture(ResourceLocation location) { this.bindTexture(location); }
@ModifyConstant(method = "getCharWidth", constant = @Constant(intValue = 7))
private int angelica$maxCharWidth(int original) {
return Integer.MAX_VALUE;
}
@Inject(method = "getCharWidth", at = @At("HEAD"), cancellable = true)
public void getCharWidth(char c, CallbackInfoReturnable<Integer> cir) {
cir.setReturnValue((int) angelica$getBatcher().getCharWidthFine(c));
}
@Override
public float getGlyphScaleX() {
return angelica$getBatcher().getGlyphScaleX();
}
@Override
public float getGlyphScaleY() {
return angelica$getBatcher().getGlyphScaleY();
}
@Override
public float getGlyphSpacing() {
return angelica$getBatcher().getGlyphSpacing();
}
@Override
public float getWhitespaceScale() {
return angelica$getBatcher().getWhitespaceScale();
return angelica$batcher.getWhitespaceScale();
}
@Override
public float getShadowOffset() {
return angelica$getBatcher().getShadowOffset();
return angelica$batcher.getShadowOffset();
}
@Override
public float getCharWidthFine(char chr) {
return angelica$getBatcher().getCharWidthFine(chr);
return angelica$batcher.getCharWidthFine(chr);
}
@Inject(method = "getFormatFromString", at = @At("HEAD"), cancellable = true)
@@ -247,7 +278,7 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
@Unique
private static String angelica$extractFormat(String text) {
text = ColorCodeUtils.convertAmpersandToSectionX(text);
text = FontRendering.preprocessText(text);
final int len = text.length();
if (len < 2) return "";
@@ -317,18 +348,13 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
return result.toString();
}
@ModifyVariable(method = "getStringWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeWidth(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
}
@ModifyVariable(method = "listFormattedStringToWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeListWrap(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
return FontRendering.preprocessText(str);
}
@ModifyVariable(method = "wrapFormattedStringToWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeWrap(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
return FontRendering.preprocessText(str);
}
}
@@ -1,35 +1,126 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.fontrenderer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraftforge.client.GuiIngameForge;
import org.lwjgl.opengl.GL11;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
@Mixin(GuiIngameForge.class)
public class MixinGuiIngameForge {
@Shadow(remap = false)
private FontRenderer fontrenderer;
@Inject(
@Unique private ByteBuffer angelica$data = memAlloc(2048);
@Unique private int angelica$ptrOffset = 0;
@Unique private long angelica$basePtr = memAddress0(angelica$data);
@Redirect(
method = "renderHUDText",
remap = false,
at = @At(
value = "INVOKE",
target = "net/minecraftforge/client/event/RenderGameOverlayEvent$Text.<init>(Lnet/minecraftforge/client/event/RenderGameOverlayEvent;Ljava/util/ArrayList;Ljava/util/ArrayList;)V"),
remap = false)
private void angelica$startF3TextBatching(int width, int height, CallbackInfo ci) {
FontRendererAccessor fra = (FontRendererAccessor) (Object) fontrenderer;
fra.angelica$getBatcher().beginBatch();
target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;III)I",
ordinal = 0,
remap = true))
private int renderBackgroundLeft(FontRenderer fontRenderer, String text, int x, int y, int color) {
angelica$writeQuad(
x - 1, y - 1,
x + fontRenderer.getStringWidth(text) + 1,
y + fontRenderer.FONT_HEIGHT
);
return fontRenderer.drawString(text, x, y, 0xe0e0e0);
}
@Redirect(
method = "renderHUDText",
remap = false,
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;III)I",
ordinal = 1,
remap = true))
private int renderBackgroundRight(FontRenderer fontRenderer, String text, int x, int y, int color) {
x += 8;
angelica$writeQuad(
x - 1, y - 1,
x + fontRenderer.getStringWidth(text) + 1, y + fontRenderer.FONT_HEIGHT
);
return fontRenderer.drawString(text, x, y, 0xe0e0e0);
}
@Unique
private void angelica$writeQuad(int left, int bottom, int right, int top) {
if ((angelica$ptrOffset + 48) > angelica$data.capacity()) {
angelica$data = memRealloc(angelica$data, angelica$data.capacity() * 2);
angelica$basePtr = memAddress0(angelica$data);
}
final long ptr = angelica$basePtr + angelica$ptrOffset;
memPutFloat(ptr, left);
memPutFloat(ptr + 4, top);
memPutFloat(ptr + 8, 0);
memPutFloat(ptr + 12, right);
memPutFloat(ptr + 16, top);
memPutFloat(ptr + 20, 0);
memPutFloat(ptr + 24, right);
memPutFloat(ptr + 28, bottom);
memPutFloat(ptr + 32, 0);
memPutFloat(ptr + 36, left);
memPutFloat(ptr + 40, bottom);
memPutFloat(ptr + 44, 0);
angelica$ptrOffset += 48;
}
@Inject(
method = "renderHUDText",
at = @At(value = "INVOKE", target = "net/minecraft/profiler/Profiler.endSection()V"))
private void angelica$endF3TextBatching(int width, int height, CallbackInfo ci) {
at = @At("HEAD"),
remap = false)
private void angelica$startF3Batching(int width, int height, CallbackInfo ci) {
FontRendererAccessor fra = (FontRendererAccessor) (Object) fontrenderer;
fra.angelica$getBatcher().beginBatch();
angelica$ptrOffset = 0;
}
@Inject(
method = "renderHUDText",
at = @At("RETURN"), remap = false)
private void angelica$endF3Batching(int width, int height, CallbackInfo ci) {
angelica$data.limit(angelica$ptrOffset);
GLStateManager.glColor4f(0.3137f, 0.3137f, 0.3137f, 0.5647f); // 0x90505050
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
TessellatorStreamingDrawer.drawPacked(
angelica$data,
GL11.GL_QUADS,
VertexFlags.POSITION_BIT,
angelica$ptrOffset / 12
);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.glDisable(GL11.GL_BLEND);
// Font rendering has to go AFTER batched rects
FontRendererAccessor fra = (FontRendererAccessor) (Object) fontrenderer;
fra.angelica$getBatcher().endBatch();
}
@@ -1,39 +0,0 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.gui;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraftforge.client.GuiIngameForge;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(GuiIngameForge.class)
public class MixinGuiIngameForge_ModernF3 {
@Redirect(
method = "renderHUDText",
remap = false,
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;III)I",
ordinal = 0,
remap = true))
private int renderBackgroundLeft(FontRenderer fontRenderer, String text, int x, int y, int color) {
Gui.drawRect(x - 1, y - 1, x + fontRenderer.getStringWidth(text) + 1, y + fontRenderer.FONT_HEIGHT, 0x90505050);
return fontRenderer.drawString(text, x, y, 0xe0e0e0);
}
@Redirect(
method = "renderHUDText",
remap = false,
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/FontRenderer;drawStringWithShadow(Ljava/lang/String;III)I",
ordinal = 1,
remap = true))
private int renderBackgroundRight(FontRenderer fontRenderer, String text, int x, int y, int color) {
x += 8;
Gui.drawRect(x - 1, y - 1, x + fontRenderer.getStringWidth(text) + 1, y + fontRenderer.FONT_HEIGHT, 0x90505050);
return fontRenderer.drawString(text, x, y, 0xe0e0e0);
}
}