Compare commits

...
14 Commits
37 changed files with 637 additions and 372 deletions

No files matched your search

+2 -2
View File
@@ -82,8 +82,8 @@ dependencies {
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.2-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
@@ -6,24 +6,26 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Setter;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Objects;
@@ -57,21 +59,18 @@ public class BatchingFontRenderer {
final boolean isSGA;
final boolean isSplash;
/** For use with modded books. Affects calculations and forces some defaults. */
@Setter
boolean bookMode = false;
private static class FontAAShader {
private static Program fontShader = null;
public static Program getProgram() {
if (fontShader == null) {
String vsh, fsh;
try {
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
fontShader = builder.build();
final String vsh = ShaderLoader.getShaderSource("angelica:fontFilter.vsh");
final String fsh = ShaderLoader.getShaderSource("angelica:fontFilter.fsh");
fontShader = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0)).build();
}
return fontShader;
}
@@ -116,7 +115,10 @@ public class BatchingFontRenderer {
// v, t and tb are floats, c is bytes; 36 bytes total
private static final int VERTEX_SIZE = 36;
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
private static long vertexDataAddress = nmemAllocChecked(rawCapacity);
private static ByteBuffer vertexData = memAlloc(rawCapacity);
private static long vertexDataAddress = memAddress0(vertexData);
private static int vboCapacity;
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
private static int fontVAO = 0;
@@ -167,7 +169,8 @@ public class BatchingFontRenderer {
private void ensureCapacity() {
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
rawCapacity *= 2;
vertexDataAddress = nmemReallocChecked(vertexDataAddress, rawCapacity);
vertexData = memRealloc(vertexData, rawCapacity);
vertexDataAddress = memAddress0(vertexData);
allocateBuffers();
}
@@ -321,8 +324,8 @@ public class BatchingFontRenderer {
// Upload first (to reduce stalls)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
streamUpload(vertexDataAddress, vertexDataPos);
vertexData.limit(vertexDataPos);
vboCapacity = StreamingUploader.upload(vertexData, vboCapacity);
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
@@ -427,12 +430,6 @@ public class BatchingFontRenderer {
idxWriterIndex = 0;
}
private static void streamUpload(long address, int bytes) {
final ByteBuffer data = memByteBuffer(address, bytes);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bytes, GL15.GL_STREAM_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
}
// === Actual text mesh generation
public static boolean charInRange(char what, char fromInclusive, char toInclusive) {
@@ -440,7 +437,7 @@ public class BatchingFontRenderer {
}
public boolean forceDefaults() {
return this.isSGA || this.isSplash;
return this.bookMode || this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
@@ -596,7 +593,7 @@ public class BatchingFontRenderer {
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4 * this.getWhitespaceScale();
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
continue;
}
@@ -646,7 +643,8 @@ public class BatchingFontRenderer {
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
curX += (xAdvance + (curBold ? 1.0f : 0.0f)) + getGlyphSpacing();
if (bookMode) { curX = (int) curX; }
underlineEndX = curX;
strikethroughEndX = curX;
}
@@ -97,7 +97,7 @@ public final class FontProviderUnicode implements FontProvider {
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
return FontConfig.fontShadowOffset * FontConfig.fontShadowOffsetUC;
}
@Override
@@ -75,13 +75,16 @@ public class FontStrategist {
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
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();
@@ -38,7 +38,7 @@ public class FontConfigScreen extends GuiScreen {
private ArrayList<Font> displayedFonts;
private GuiTextField searchBox;
private GuiTextField testArea;
private int sliderPages = 0;
private int lastSliderPageIndex = 0;
private int displayedSliderPage = 0;
private final int sliderWidth = 160;
private final int sliderHeight = 20;
@@ -58,6 +58,7 @@ public class FontConfigScreen extends GuiScreen {
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optShadowOffsetUC = new SliderClone.Option(0, 2, 0.05f);
public FontConfigScreen(GuiScreen parent) {
this.title = I18n.format("options.angelica.fontconfig.title");
@@ -226,6 +227,16 @@ public class FontConfigScreen extends GuiScreen {
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowOffsetUC)
.initialValue(FontConfig.fontShadowOffsetUC)
.setter(value -> FontConfig.fontShadowOffsetUC = value)
.langKey("options.angelica.fontconfig.shadow_offset_unicode")
.formatString("x%3.2f")
.build()
);
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
@@ -236,11 +247,11 @@ public class FontConfigScreen extends GuiScreen {
final int y1 = this.height - 65;
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
this.sliderPages = sliders.size() / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
this.lastSliderPageIndex = (sliders.size() - 1) / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.lastSliderPageIndex);
this.backButton.enabled = (this.displayedSliderPage != 0);
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
this.fwdButton.enabled = (this.displayedSliderPage != this.lastSliderPageIndex);
final int start = 6 * this.displayedSliderPage;
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
@@ -304,7 +315,7 @@ public class FontConfigScreen extends GuiScreen {
}
private void switchPage(int offset) {
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.lastSliderPageIndex);
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
this.initSliders();
}
@@ -71,6 +71,11 @@ public class FontConfig {
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float customFontScale;
@Config.Comment("A shadow offset multiplier applied only to the Unicode font.")
@Config.DefaultFloat(0.5F)
@Config.RangeFloat(min = 0F, max = 2F)
public static float fontShadowOffsetUC;
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
@@ -40,7 +40,7 @@ import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN =
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
private static final Pattern DEFINE_PATTERN =
Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
@@ -8,6 +8,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.render.SelectionBoxRenderer;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
@@ -583,6 +584,7 @@ public class GLStateManager {
GL30.glBindVertexArray(defaultVAO);
boundVAO = defaultVAO;
VertexAttribState.init(defaultVAO);
SelectionBoxRenderer.init();
// Drain any pending GL errors from initialization. In core profile, some legacy queries may generate GL_INVALID_ENUM. The splash thread inherits the
// DrawableGL context and its error state, so stale errors here would cause SplashProgress.checkGLError() to fail.
@@ -1713,7 +1715,18 @@ public class GLStateManager {
}
}
private static int changeFormatIfDeprecated(int internalformat) {
switch (internalformat) {
case GL11.GL_ALPHA4 -> internalformat = GL11.GL_RGBA4;
case GL11.GL_ALPHA8 -> internalformat = GL11.GL_RGBA8;
case GL11.GL_ALPHA12 -> internalformat = GL11.GL_RGBA12;
case GL11.GL_ALPHA16 -> internalformat = GL11.GL_RGBA16;
}
return internalformat;
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromIntBuffer(target, level, internalformat, width, height, border, format, type, pixels));
@@ -1732,6 +1745,7 @@ public class GLStateManager {
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromFloatBuffer(target, level, internalformat, width, height, border, format, type, pixels));
@@ -1745,6 +1759,7 @@ public class GLStateManager {
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromDoubleBuffer(target, level, internalformat, width, height, border, format, type, pixels));
@@ -1758,6 +1773,7 @@ public class GLStateManager {
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels));
@@ -1776,6 +1792,7 @@ public class GLStateManager {
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage2D with buffer offset in display lists not yet supported");
}
@@ -2032,6 +2049,12 @@ public class GLStateManager {
}
}
public static void glVertex2i(int x, int y) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.vertex((float) x, (float) y, 0.0f);
}
}
public static void glVertex2d(double x, double y) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.vertex((float) x, (float) y, 0.0f);
@@ -2049,6 +2072,13 @@ public class GLStateManager {
ImmediateModeRecorder.vertex((float) x, (float) y, (float) z);
}
}
public static void glVertex3i(int x, int y, int z) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.vertex((float) x, (float) y, (float) z);
}
}
public static void glDrawElements(int mode, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
@@ -2310,6 +2340,10 @@ public class GLStateManager {
GL20.glVertexAttribPointer(index, size, type, normalized, stride, offset);
}
public static void glVertexAttribPointer(int index, int size, boolean normalized, int stride, FloatBuffer pointer) {
glVertexAttribPointer(index, size, GL11.GL_FLOAT, normalized, stride, pointer);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, FloatBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
@@ -4005,6 +4039,7 @@ public class GLStateManager {
// Missing GL commands from Mesa cross-check
public static void glTexImage1D(int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage1D in display lists not yet implemented");
}
@@ -4012,6 +4047,15 @@ public class GLStateManager {
}
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
}
GL12.glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
}
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
}
@@ -4921,6 +4965,62 @@ public class GLStateManager {
}
}
public static float glGetTexEnvf(int target, int pname) {
return (float) glGetTexEnvi(target, pname);
}
public static int glGetTexEnvi(int target, int pname) {
if (target != GL11.GL_TEXTURE_ENV) return 0;
final var envState = textures.getTexEnvState(activeTextureUnit.getValue());
return switch (pname) {
case GL11.GL_TEXTURE_ENV_MODE -> envState.mode;
case GL13.GL_COMBINE_RGB -> envState.combineRgb;
case GL13.GL_COMBINE_ALPHA -> envState.combineAlpha;
case GL13.GL_SOURCE0_RGB -> envState.sourceRgb[0];
case GL13.GL_SOURCE1_RGB -> envState.sourceRgb[1];
case GL13.GL_SOURCE2_RGB -> envState.sourceRgb[2];
case GL13.GL_SOURCE0_ALPHA -> envState.sourceAlpha[0];
case GL13.GL_SOURCE1_ALPHA -> envState.sourceAlpha[1];
case GL13.GL_SOURCE2_ALPHA -> envState.sourceAlpha[2];
case GL13.GL_OPERAND0_RGB -> envState.operandRgb[0];
case GL13.GL_OPERAND1_RGB -> envState.operandRgb[1];
case GL13.GL_OPERAND2_RGB -> envState.operandRgb[2];
case GL13.GL_OPERAND0_ALPHA -> envState.operandAlpha[0];
case GL13.GL_OPERAND1_ALPHA -> envState.operandAlpha[1];
case GL13.GL_OPERAND2_ALPHA -> envState.operandAlpha[2];
case GL13.GL_RGB_SCALE -> (int) envState.scaleRgb;
case GL11.GL_ALPHA_SCALE -> (int) envState.scaleAlpha;
default -> 0;
};
}
public static void glGetTexEnv(int target, int pname, IntBuffer params) {
if (target != GL11.GL_TEXTURE_ENV) return;
if (pname == GL11.GL_TEXTURE_ENV_COLOR) {
final var envState = textures.getTexEnvState(activeTextureUnit.getValue());
params.put(params.position(), (int) (envState.envColorR * 2147483647.0f));
params.put(params.position() + 1, (int) (envState.envColorG * 2147483647.0f));
params.put(params.position() + 2, (int) (envState.envColorB * 2147483647.0f));
params.put(params.position() + 3, (int) (envState.envColorA * 2147483647.0f));
} else {
params.put(params.position(), glGetTexEnvi(target, pname));
}
}
public static void glGetTexEnv(int target, int pname, FloatBuffer params) {
if (target != GL11.GL_TEXTURE_ENV) return;
if (pname == GL11.GL_TEXTURE_ENV_COLOR) {
final var envState = textures.getTexEnvState(activeTextureUnit.getValue());
params.put(params.position(), envState.envColorR);
params.put(params.position() + 1, envState.envColorG);
params.put(params.position() + 2, envState.envColorB);
params.put(params.position() + 3, envState.envColorA);
} else {
params.put(params.position(), (float) glGetTexEnvi(target, pname));
}
}
// TexGen generation counter for FFP uniform dirty tracking
public static int texGenGeneration;
@@ -5007,6 +5107,65 @@ public class GLStateManager {
}
}
public static int glGetTexGeni(int coord, int pname) {
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
return textures.getTexGenState(activeTextureUnit.getValue()).getMode(coord);
}
return 0;
}
public static float glGetTexGenf(int coord, int pname) {
return (float) glGetTexGeni(coord, pname);
}
public static double glGetTexGend(int coord, int pname) {
return glGetTexGeni(coord, pname);
}
public static void glGetTexGen(int coord, int pname, IntBuffer params) {
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
params.put(params.position(), glGetTexGeni(coord, pname));
}
}
public static void glGetTexGen(int coord, int pname, FloatBuffer params) {
final var state = textures.getTexGenState(activeTextureUnit.getValue());
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
params.put(params.position(), (float) state.getMode(coord));
} else if (pname == GL11.GL_OBJECT_PLANE) {
final float[] plane = state.getObjectPlane(coord);
params.put(params.position(), plane[0]);
params.put(params.position() + 1, plane[1]);
params.put(params.position() + 2, plane[2]);
params.put(params.position() + 3, plane[3]);
} else if (pname == GL11.GL_EYE_PLANE) {
final float[] plane = state.getEyePlane(coord);
params.put(params.position(), plane[0]);
params.put(params.position() + 1, plane[1]);
params.put(params.position() + 2, plane[2]);
params.put(params.position() + 3, plane[3]);
}
}
public static void glGetTexGen(int coord, int pname, DoubleBuffer params) {
final var state = textures.getTexGenState(activeTextureUnit.getValue());
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
params.put(params.position(), state.getMode(coord));
} else if (pname == GL11.GL_OBJECT_PLANE) {
final float[] plane = state.getObjectPlane(coord);
params.put(params.position(), plane[0]);
params.put(params.position() + 1, plane[1]);
params.put(params.position() + 2, plane[2]);
params.put(params.position() + 3, plane[3]);
} else if (pname == GL11.GL_EYE_PLANE) {
final float[] plane = state.getEyePlane(coord);
params.put(params.position(), plane[0]);
params.put(params.position() + 1, plane[1]);
params.put(params.position() + 2, plane[2]);
params.put(params.position() + 3, plane[3]);
}
}
public static void glPolygonStipple(ByteBuffer pattern) {
guardUnsupportedFFP("glPolygonStipple", "glPolygonStipple is not available in GL 3.3 core profile.");
}
@@ -4,12 +4,10 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer.UploadStrategy;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
@@ -47,7 +45,6 @@ public class TessellatorStreamingDrawer {
private static int repackCapacity;
private static boolean initialized = false;
private static UploadStrategy orphanStrategy = UploadStrategy.BUFFER_SUB_DATA;
static {
// Initial repack buffer: 64KB
@@ -60,12 +57,6 @@ public class TessellatorStreamingDrawer {
if (initialized) return;
initialized = true;
// Select orphan upload strategy from config
if (AngelicaMod.options() != null && AngelicaMod.options().advanced.useMapBufferRange) {
orphanStrategy = UploadStrategy.MAP_BUFFER_RANGE;
}
LOGGER.info("Orphan upload strategy: {}", orphanStrategy);
if (RenderSystem.supportsBufferStorage() && !Boolean.getBoolean("angelica.forceOrphanStreaming")) {
try {
persistentBuffer = new PersistentStreamingBuffer();
@@ -179,11 +170,6 @@ public class TessellatorStreamingDrawer {
if (persistentBuffer != null) {
persistentBuffer.postDraw();
}
for (int i = 0; i < FORMAT_COUNT; i++) {
if (orphanBuffers[i] != null) {
orphanBuffers[i].postDraw();
}
}
}
/**
@@ -231,7 +217,8 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
firstVertex = orphanBuffers[flags].upload(packed, vertexSize);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
GLStateManager.prepareWideLineEmulation(drawMode);
@@ -266,7 +253,7 @@ public class TessellatorStreamingDrawer {
init();
if (orphanVAOs[flags] == 0) {
orphanBuffers[flags] = new OrphanStreamingBuffer(orphanStrategy);
orphanBuffers[flags] = new OrphanStreamingBuffer();
orphanVAOs[flags] = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
@@ -1,142 +1,48 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
/**
* Streaming buffer using the classic orphan pattern with sub-allocation.
* <p>
* Supports two upload strategies:
* <ul>
* <li>{@link UploadStrategy#BUFFER_SUB_DATA} — glBufferData orphan + glBufferSubData (default)</li>
* <li>{@link UploadStrategy#MAP_BUFFER_RANGE} — glMapBufferRange with INVALIDATE hints </li>
* </ul>
* Streaming buffer using the classic orphan pattern.
*/
public class OrphanStreamingBuffer implements StreamingBuffer {
public enum UploadStrategy {
BUFFER_SUB_DATA,
MAP_BUFFER_RANGE
}
private static final int MAP_WRITE_INVALIDATE_BUFFER = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT;
private static final int MAP_WRITE_INVALIDATE_RANGE = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT;
public final class OrphanStreamingBuffer {
private int bufferId;
private int capacity;
private int writePos;
private boolean needsOrphan = true;
private final UploadStrategy strategy;
public OrphanStreamingBuffer() {
this(UploadStrategy.BUFFER_SUB_DATA);
}
public OrphanStreamingBuffer(UploadStrategy strategy) {
this.strategy = strategy;
this.bufferId = GL15.glGenBuffers();
}
@Override
public int upload(ByteBuffer data, int vertexStride) {
final int dataSize = data.remaining();
final int alignUnit = vertexStride * 4;
int alignedPos = ((writePos + alignUnit - 1) / alignUnit) * alignUnit;
public void upload(ByteBuffer data) {
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
boolean freshAllocation = false;
if (alignedPos + dataSize > capacity) {
// Doesn't fit — grow if needed, allocate new storage, reset to offset 0
growIfNeeded(dataSize);
allocateBuffer();
alignedPos = 0;
freshAllocation = true;
} else if (needsOrphan) {
// First upload of the frame — need fresh storage
if (strategy == UploadStrategy.BUFFER_SUB_DATA) {
// SubData strategy: orphan via glBufferData to get new backing store
allocateBuffer();
}
}
if (strategy == UploadStrategy.BUFFER_SUB_DATA) {
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, alignedPos, data);
} else {
// MapRange: choose flags based on whether this is the first upload of the frame
final int flags;
if (freshAllocation) {
// Fresh allocation — nothing to invalidate, just write
flags = MAP_WRITE_INVALIDATE_RANGE;
} else if (needsOrphan) {
// First upload of frame on existing buffer — invalidate entire buffer
flags = MAP_WRITE_INVALIDATE_BUFFER;
} else {
// Subsequent upload — invalidate only our range
flags = MAP_WRITE_INVALIDATE_RANGE;
}
final ByteBuffer mapped = LWJGL.glMapBufferRange(GL15.GL_ARRAY_BUFFER, alignedPos, dataSize, flags);
memCopy(memAddress0(data), memAddress0(mapped), dataSize);
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
}
capacity = StreamingUploader.upload(data, capacity);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
needsOrphan = false;
writePos = alignedPos + dataSize;
return alignedPos / vertexStride;
}
private static final int MIN_CAPACITY = 64 * 1024;
private void growIfNeeded(int needed) {
if (needed > capacity) {
int newCapacity = Math.max(capacity, MIN_CAPACITY);
while (newCapacity < needed) {
newCapacity *= 2;
}
capacity = newCapacity;
}
void upload(StreamingUploader.UploadStrategy strategy, ByteBuffer data) {
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(strategy, data, capacity);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
private void allocateBuffer() {
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, capacity, GL15.GL_STREAM_DRAW);
}
@Override
public int getCapacity() {
return capacity;
}
@Override
public int getBufferId() {
return bufferId;
}
@Override
public void postDraw() {
writePos = 0;
needsOrphan = true;
}
@Override
public void destroy() {
if (bufferId != 0) {
GL15.glDeleteBuffers(bufferId);
bufferId = 0;
}
}
int getWritePos() {
return writePos;
}
UploadStrategy getStrategy() {
return strategy;
}
}
@@ -0,0 +1,68 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.AngelicaMod;
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
public final class StreamingUploader {
public enum UploadStrategy implements NamedState {
BUFFER_DATA("sodium.options.upload_method.buffer_data"),
BUFFER_SUB_DATA("sodium.options.upload_method.buffer_subdata"),
MAP_BUFFER_RANGE("sodium.options.upload_method.map_buffer");
private final String key;
UploadStrategy(String key) {
this.key = key;
}
@Override
public String getKey() {
return this.key;
}
}
private 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(AngelicaMod.options().advanced.streamingUploadStrategy, data, capacity);
}
public static int upload(UploadStrategy strategy, ByteBuffer data, int capacity) {
switch (strategy) {
case BUFFER_DATA -> {
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
case BUFFER_SUB_DATA -> {
if (data.remaining() > capacity) {
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
return capacity;
}
case MAP_BUFFER_RANGE -> {
if (data.remaining() > capacity) {
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
final int dataSize = data.remaining();
final ByteBuffer mapped = LWJGL.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
memCopy(memAddress0(data), memAddress0(mapped), dataSize);
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity;
}
default -> throw new UnsupportedOperationException();
}
}
}
@@ -54,6 +54,15 @@ public class AngelicaLateTweaker implements ITweaker {
exceptions.add("Reika.DragonAPI.ASM.StructureLootHooks");
FMLRelaunchLog.info("[Angelica] Narrowed Reika.DragonAPI.ASM transformer exclusion to allow GL redirection of utility classes");
}
if (exceptions.remove("xaero.common.core")) {
exceptions.add("xaero.common.core.transformer");
FMLRelaunchLog.info("[Angelica] Narrowed xaero.common.core transformer exclusion to allow GL redirection");
}
if (exceptions.remove("xaero.map.core")) {
exceptions.add("xaero.map.core.transformer");
FMLRelaunchLog.info("[Angelica] Narrowed xaero.map.core transformer exclusion to allow GL redirection");
}
} catch (Exception e) {
FMLRelaunchLog.warning("[Angelica] Failed to narrow transformer exclusions: %s", e.getMessage());
}
@@ -185,10 +185,17 @@ public final class AngelicaRedirector {
.add("glTexEnvi")
.add("glTexEnvf")
.add("glTexEnv")
.add("glGetTexEnv")
.add("glGetTexEnvi")
.add("glGetTexEnvf")
.add("glTexGeni")
.add("glTexGenf")
.add("glTexGend")
.add("glTexGen")
.add("glGetTexGen")
.add("glGetTexGeni")
.add("glGetTexGenf")
.add("glGetTexGend")
.add("glTexImage1D")
.add("glTexImage2D")
.add("glTexImage3D")
@@ -229,8 +236,10 @@ public final class AngelicaRedirector {
.add("glTranslatef")
.add("glVertex2d")
.add("glVertex2f")
.add("glVertex2i")
.add("glVertex3d")
.add("glVertex3f")
.add("glVertex3i")
.add("glVertexPointer")
.add("glColorPointer")
.add("glNormalPointer")
@@ -40,6 +40,7 @@ public enum Mixins implements IMixins {
, "angelica.glsm.MixinSplashProgressCaching"
, "angelica.gui.MixinGuiOptions"
, "angelica.optimizations.MixinRendererLivingEntity"
, "angelica.rendering.MixinRenderGlobal_SelectionBox"
)
),
@@ -229,7 +230,6 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderGlobal"
, "shaders.MixinRenderHorse"
, "shaders.MixinRenderItem"
, "shaders.MixinRenderManager"
, "shaders.MixinRenderNameTag"
, "shaders.MixinRenderPlayerArmor"
, "shaders.MixinTileEntityBeaconRenderer"
@@ -238,6 +238,24 @@ public enum Mixins implements IMixins {
)
),
IRIS_SHADERS_RENDER_MANAGER(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris)
.addExcludedMod(TargetedMod.DRAGON_API)
.addClientMixins(
"shaders.MixinRenderManager"
)
),
IRIS_SHADERS_RENDER_MANAGER_DAPI(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris)
.addRequiredMod(TargetedMod.DRAGON_API)
.addClientMixins(
"shaders.MixinRenderManagerDAPI"
)
),
IRIS_RENDERING_NOBACKHAND(new MixinBuilder("Iris Hand Shaders")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris)
@@ -16,6 +16,7 @@ import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.debug.OpenGLDebugging;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.render.CloudRenderer;
@@ -0,0 +1,109 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.uniform.GlUniformFloat2v;
import net.minecraft.util.AxisAlignedBB;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
public final class SelectionBoxRenderer {
private static GlProgram<SelectionBoxUniforms> program;
private static int emptyVao;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final float[] colorBuf = new float[4];
private SelectionBoxRenderer() {}
public static void init() {
final boolean needsGS = GLStateManager.wideLineEmulationEnabled;
final GlShader vs = ShaderLoader.loadShader(ShaderType.VERTEX, "angelica:selection_box.vert.glsl", ShaderConstants.EMPTY);
final GlShader fs = ShaderLoader.loadShader(ShaderType.FRAGMENT, "angelica:selection_box.frag.glsl", ShaderConstants.EMPTY);
final GlProgram.Builder builder = GlProgram.builder("SelectionBox").attachShader(vs).attachShader(fs);
GlShader gs = null;
if (needsGS) {
gs = ShaderLoader.loadShader(ShaderType.GEOM, "angelica:selection_box.geom.glsl", ShaderConstants.EMPTY);
builder.attachShader(gs);
}
program = builder.link(ctx -> new SelectionBoxUniforms(
ctx.bindUniform("u_MVP", GlUniformMatrix4f::new),
ctx.bindUniform("u_Min", GlUniformFloat3v::new),
ctx.bindUniform("u_Max", GlUniformFloat3v::new),
ctx.bindUniform("u_Color", GlUniformFloat4v::new),
needsGS ? ctx.bindUniform("u_ViewportSize", GlUniformFloat2v::new) : null,
needsGS ? ctx.bindUniform("u_LineWidth", GlUniformFloat::new) : null));
vs.destroy();
fs.destroy();
if (gs != null) gs.destroy();
emptyVao = GL30.glGenVertexArrays();
}
public static void draw(AxisAlignedBB aabb, int color) {
if (program == null) return;
final SelectionBoxUniforms uniforms = program.getInterface();
program.bind();
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), mvpMatrix);
uniforms.mvp.set(mvpMatrix);
uniforms.min.set((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ);
uniforms.max.set((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
if (color != -1) {
colorBuf[0] = ((color >> 16) & 0xFF) / 255.0f;
colorBuf[1] = ((color >> 8) & 0xFF) / 255.0f;
colorBuf[2] = (color & 0xFF) / 255.0f;
colorBuf[3] = 1.0f;
} else {
final Color4 c = GLStateManager.getColor();
colorBuf[0] = c.getRed();
colorBuf[1] = c.getGreen();
colorBuf[2] = c.getBlue();
colorBuf[3] = c.getAlpha();
}
uniforms.color.set(colorBuf);
if (uniforms.viewportSize != null) {
uniforms.viewportSize.set(GLStateManager.getViewportState().width, GLStateManager.getViewportState().height);
uniforms.lineWidth.setFloat(GLStateManager.getLineState().getWidth());
}
GLStateManager.glBindVertexArray(emptyVao);
GL11.glDrawArrays(GL11.GL_LINES, 0, 24);
GLStateManager.glBindVertexArray(0);
program.unbind();
}
public static void destroy() {
if (program != null) {
program.destroy();
program = null;
}
if (emptyVao != 0) {
GL30.glDeleteVertexArrays(emptyVao);
emptyVao = 0;
}
}
private record SelectionBoxUniforms(GlUniformMatrix4f mvp, GlUniformFloat3v min, GlUniformFloat3v max, GlUniformFloat4v color, GlUniformFloat2v viewportSize, GlUniformFloat lineWidth) {
}
}
@@ -3,6 +3,7 @@ package me.jellysquid.mods.sodium.client.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
import me.flashyreese.mods.reeses_sodium_options.client.gui.ReeseSodiumVideoOptionsScreen;
@@ -272,14 +273,16 @@ public class SodiumGameOptionPages {
.setImpact(OptionImpact.VARIES)
.setEnabled(GLStateManager.capabilities != null)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_map_buffer_range.name"))
.setTooltip(I18n.format("sodium.options.use_map_buffer_range.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.advanced.useMapBufferRange = value, opts -> opts.advanced.useMapBufferRange)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setImpact(OptionImpact.VARIES)
.build())
.add(OptionImpl.createBuilder(StreamingUploader.UploadStrategy.class, sodiumOpts)
.setName(I18n.format("sodium.options.upload_method.name"))
.setTooltip(I18n.format("sodium.options.upload_method.tooltip"))
.setControl(o ->
new CyclingControl<>(o, StreamingUploader.UploadStrategy.class, StreamingUploader.UploadStrategy.values())
)
.setBinding((opts, value) -> opts.advanced.streamingUploadStrategy = value, opts -> opts.advanced.streamingUploadStrategy)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setImpact(OptionImpact.VARIES)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.enable_deferred_batching.name"))
.setTooltip(I18n.format("sodium.options.enable_deferred_batching.tooltip"))
@@ -6,6 +6,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import net.coderbot.iris.Iris;
@@ -33,7 +34,7 @@ public class SodiumGameOptions {
public boolean useParticleCulling = true;
public boolean allowDirectMemoryAccess = true;
public boolean ignoreDriverBlacklist = false;
public boolean useMapBufferRange = false;
public StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.MAP_BUFFER_RANGE;
public boolean enableDeferredBatching = true;
}
@@ -14,13 +14,10 @@ import net.coderbot.iris.gl.texture.PixelType;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.coderbot.iris.uniforms.SystemTimeUniforms;
import net.minecraft.client.Minecraft;
import org.apache.commons.io.IOUtils;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.function.IntSupplier;
public class CenterDepthSampler {
@@ -45,16 +42,9 @@ public class CenterDepthSampler {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
this.framebuffer.addColorAttachment(0, texture);
ProgramBuilder builder;
try {
String fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/centerDepth.fsh"))), StandardCharsets.UTF_8);
String vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/centerDepth.vsh"))), StandardCharsets.UTF_8);
builder = ProgramBuilder.begin("centerDepthSmooth", vsh, null, fsh, ImmutableSet.of(0, 1, 2));
} catch (IOException e) {
throw new RuntimeException(e);
}
String vsh = ShaderLoader.getShaderSource("angelica:centerDepth.vsh");
String fsh = ShaderLoader.getShaderSource("angelica:centerDepth.fsh");
ProgramBuilder builder = ProgramBuilder.begin("centerDepthSmooth", vsh, null, fsh, ImmutableSet.of(0, 1, 2));
builder.addDynamicSampler(depthSupplier, "depth");
builder.addDynamicSampler(() -> altTexture, "altDepth");
@@ -187,6 +187,7 @@ options.angelica.fontconfig.aamode.aa_none=Font AA: OFF
options.angelica.fontconfig.aamode.aa_4x=Font AA: 4x MSAA
options.angelica.fontconfig.aamode.aa_16x=Font AA: 16x MSAA
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.
@@ -198,6 +199,7 @@ options.angelica.fontconfig.glyph_spacing.tooltip=The spacing between each indiv
options.angelica.fontconfig.font_aa_strength.tooltip=The spacing of samples used in font antialiasing. High values may cause fonts to look blurry, low values may cause aliasing. See what works best with your settings.
options.angelica.fontconfig.aamode.tooltip=The font antialiasing mode. Can significantly improve the appearance and readability of text that's being scaled or viewed at oblique angles, at the expense of some performance.
options.angelica.fontconfig.custom_font_scale.tooltip=A multiplier that affects the scale of custom fonts. Adjust this if text in your font of choice looks under-/over-sized relative to text that does not use custom fonts.
options.angelica.fontconfig.shadow_offset_unicode.tooltip=A shadow offset multiplier applied only to the Unicode font. Set this to 1.0x if, for example, you use a texture pack that replaces portions of the Unicode font with MC lookalikes.
options.angelica.hudCaching=HUD Caching
options.angelica.hudCaching.tooltip=Caches the HUD to a framebuffer and reuses it across frames to reduce rendering overhead. Requires HUD Caching to be enabled at startup. [Experimental]
options.angelica.droppedItemLimit=Dropped Item Render Limit
@@ -227,7 +229,10 @@ sodium.options.multidraw_mode.tooltip=Controls how draw commands are dispatched
sodium.options.multidraw_mode.direct=Direct
sodium.options.multidraw_mode.indirect=Indirect
sodium.options.multidraw_mode.individual=Individual
sodium.options.use_map_buffer_range.name=Use MapBufferRange
sodium.options.use_map_buffer_range.tooltip=Uses glMapBufferRange instead of glBufferSubData for streaming vertex uploads. May improve performance on some drivers.
sodium.options.upload_method.name=Streaming Upload Method
sodium.options.upload_method.tooltip=Changes the way the data gets streamed to the GPU. Some options may lead to better performance for some drivers.
sodium.options.upload_method.buffer_data=BufferData
sodium.options.upload_method.buffer_subdata=BufferSubData
sodium.options.upload_method.map_buffer=MapBufferRange
sodium.options.enable_deferred_batching.name=Deferred Particle Batching
sodium.options.enable_deferred_batching.tooltip=Batches particle draw calls into fewer merged draws per layer. Disable if particles render incorrectly.
@@ -0,0 +1,8 @@
#version 330 core
uniform vec4 u_Color;
out vec4 fragColor;
void main() {
fragColor = u_Color;
}
@@ -0,0 +1,28 @@
#version 330 core
layout(lines) in;
layout(triangle_strip, max_vertices = 4) out;
uniform vec2 u_ViewportSize;
uniform float u_LineWidth;
void main() {
vec4 c0 = gl_in[0].gl_Position;
vec4 c1 = gl_in[1].gl_Position;
vec2 n0 = c0.xy / c0.w;
vec2 n1 = c1.xy / c1.w;
vec2 dir = normalize((n1 - n0) * u_ViewportSize);
vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;
gl_Position = vec4((n0 + offset) * c0.w, c0.z, c0.w);
EmitVertex();
gl_Position = vec4((n0 - offset) * c0.w, c0.z, c0.w);
EmitVertex();
gl_Position = vec4((n1 + offset) * c1.w, c1.z, c1.w);
EmitVertex();
gl_Position = vec4((n1 - offset) * c1.w, c1.z, c1.w);
EmitVertex();
EndPrimitive();
}
@@ -0,0 +1,19 @@
#version 330 core
uniform mat4 u_MVP;
uniform vec3 u_Min;
uniform vec3 u_Max;
const vec3 edges[24] = vec3[24](
vec3(0,0,0), vec3(1,0,0), vec3(1,0,0), vec3(1,0,1),
vec3(1,0,1), vec3(0,0,1), vec3(0,0,1), vec3(0,0,0),
vec3(0,1,0), vec3(1,1,0), vec3(1,1,0), vec3(1,1,1),
vec3(1,1,1), vec3(0,1,1), vec3(0,1,1), vec3(0,1,0),
vec3(0,0,0), vec3(0,1,0), vec3(1,0,0), vec3(1,1,0),
vec3(1,0,1), vec3(1,1,1), vec3(0,0,1), vec3(0,1,1)
);
void main() {
vec3 pos = mix(u_Min, u_Max, edges[gl_VertexID]);
gl_Position = u_MVP * vec4(pos, 1.0);
}
@@ -7,5 +7,8 @@
"injectors":{
"maxShiftBy": 2
},
"compatibilityLevel": "JAVA_17"
"compatibilityLevel": "JAVA_17",
"overwrites": {
"conformVisibility": true
}
}
@@ -7,6 +7,9 @@
"injectors":{
"maxShiftBy": 2
},
"compatibilityLevel": "JAVA_21"
"compatibilityLevel": "JAVA_21",
"overwrites": {
"conformVisibility": true
}
}
@@ -7,6 +7,9 @@
"injectors":{
"maxShiftBy": 2
},
"compatibilityLevel": "JAVA_8"
"compatibilityLevel": "JAVA_8",
"overwrites": {
"conformVisibility": true
}
}
+4 -1
View File
@@ -3,6 +3,9 @@
"minVersion": "0.8.3-GTNH",
"refmap": "mixins.angelica.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8"
"compatibilityLevel": "JAVA_8",
"overwrites": {
"conformVisibility": true
}
}
@@ -4,5 +4,8 @@
"package": "com.gtnewhorizons.angelica.mixins.late",
"refmap": "mixins.angelica.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_17"
"compatibilityLevel": "JAVA_17",
"overwrites": {
"conformVisibility": true
}
}
@@ -4,6 +4,9 @@
"package": "com.gtnewhorizons.angelica.mixins.late",
"refmap": "mixins.angelica.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_21"
"compatibilityLevel": "JAVA_21",
"overwrites": {
"conformVisibility": true
}
}
+4 -1
View File
@@ -4,6 +4,9 @@
"package": "com.gtnewhorizons.angelica.mixins.late",
"refmap": "mixins.angelica.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8"
"compatibilityLevel": "JAVA_8",
"overwrites": {
"conformVisibility": true
}
}
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.render.PanoramaRenderer;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
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;
@@ -12,14 +13,14 @@ import org.spongepowered.asm.mixin.Shadow;
public abstract class MixinGuiMainMenu extends GuiScreen {
@Shadow private int panoramaTimer;
@Shadow private static ResourceLocation[] titlePanoramaPaths;
@Final @Shadow private static ResourceLocation[] titlePanoramaPaths;
/**
* @author Angelica
* @reason Replace panorama with modern equivalent
*/
@Overwrite
private void renderSkybox(int mouseX, int mouseY, float partialTicks) {
public void renderSkybox(int mouseX, int mouseY, float partialTicks) {
PanoramaRenderer.getInstance().renderSkybox(this.panoramaTimer, partialTicks, titlePanoramaPaths, this.mc, this.width, this.height, this.zLevel);
}
}
@@ -0,0 +1,20 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.rendering;
import com.gtnewhorizons.angelica.render.SelectionBoxRenderer;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.util.AxisAlignedBB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(RenderGlobal.class)
public class MixinRenderGlobal_SelectionBox {
/**
* @author Angelica
* @reason Optimize selection box rendering
*/
@Overwrite
public static void drawOutlinedBoundingBox(AxisAlignedBB aabb, int color) {
SelectionBoxRenderer.draw(aabb, color);
}
}
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Dynamic;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(RenderManager.class)
public class MixinRenderManagerDAPI {
@Dynamic
@WrapOperation(
method = "func_147939_a(Lnet/minecraft/entity/Entity;DDDFFZ)Z",
at = @At(remap = false, value = "INVOKE", target = "LReika/DragonAPI/Instantiable/Event/Client/EntityRenderEvent;fire(Lnet/minecraft/client/renderer/entity/Render;Lnet/minecraft/entity/Entity;DDDFF)V"),
require = 1 // Require this if DAPI is present, which should be the case when this mixin is applied.
)
private void iris$wrapDoRenderDragonAPI(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
int entityId = EntityIdHelper.getEntityId(entity);
CapturedRenderingState.INSTANCE.setCurrentEntity(entityId);
ItemIdManager.resetItemId();
original.call(render, entity, x, y, z, entityYaw, partialTicks);
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
}
}
@@ -529,6 +529,41 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("angelica_SceneColor"), "should reference angelica_SceneColor");
}
@Test
void testVersionPatternDoesNotEatNextLine() {
String src = "#version 330\nsomeword\n";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("someword"), "Next-line word must not be eaten by version fixup");
assertTrue(result.contains("#version 330 core"), "Version should be upgraded to core");
}
@Test
void testModernShaderWithoutCoreKeyword() {
String src = """
#version 430
in vec3 Position;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
out vec4 vertexColor;
out float fogDistance;
void main() {
vec4 finalPos = vec4(Position, 1.0);
vec4 modelPos = ModelViewMat * finalPos;
gl_Position = ProjMat * modelPos;
fogDistance = length(modelPos.xz);
vertexColor = vec4(1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("#version 430 core"), "Version should get 'core' profile added");
assertTrue(result.contains("in vec3 Position"), "'in' keyword must not be eaten");
assertTrue(result.contains("ModelViewMat"), "Shader content must be preserved");
assertTrue(result.contains("ProjMat"), "Shader content must be preserved");
assertTrue(result.contains("fogDistance"), "Shader content must be preserved");
}
@Test
void testLegacyQualifierConversion() {
String vertSrc = """
@@ -2,11 +2,11 @@ package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.AngelicaExtension;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer.UploadStrategy;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader.UploadStrategy;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -20,25 +20,10 @@ import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(AngelicaExtension.class)
class StreamingBufferTest {
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_firstUploadReturnsZero(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
try {
ByteBuffer data = memAlloc(128);
fillPattern(data);
data.flip();
assertEquals(0, buf.upload(data, 32), "First upload should return offset 0");
memFree(data);
} finally {
buf.destroy();
}
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_dataIntegrity(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
try {
ByteBuffer data = memAlloc(64);
ByteBuffer readBack = null;
@@ -46,7 +31,7 @@ class StreamingBufferTest {
for (int i = 0; i < 64; i++) data.put((byte) i);
data.flip();
buf.upload(data, 16);
buf.upload(strategy, data);
// Read back via glGetBufferSubData
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
@@ -66,171 +51,6 @@ class StreamingBufferTest {
}
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_multipleUploadsSubAllocate(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
try {
final int stride = 16;
ByteBuffer small = memAlloc(64);
ByteBuffer big = memAlloc(256);
ByteBuffer readBack = null;
try {
// First upload: small
fillPattern(small);
small.flip();
int v1 = buf.upload(small, stride);
assertEquals(0, v1, "First upload should be at offset 0");
// Second upload: larger — sub-allocates after first (64KB min capacity)
fillPattern(big);
big.flip();
int v2 = buf.upload(big, stride);
assertTrue(v2 > 0, "Second upload should sub-allocate at non-zero offset");
// Verify second upload's data at its offset
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
readBack = memAlloc(buf.getCapacity());
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
int byteOffset = v2 * stride;
for (int i = 0; i < 256; i++) {
assertEquals((byte) (i & 0xFF), readBack.get(byteOffset + i), "Mismatch at byte " + i);
}
} finally {
memFree(small);
memFree(big);
if (readBack != null) memFree(readBack);
}
} finally {
buf.destroy();
}
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_subAllocationReturnsIncreasingOffsets(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
try {
final int stride = 16;
final int dataSize = 64; // 4 vertices worth
ByteBuffer data1 = memAlloc(dataSize);
ByteBuffer data2 = memAlloc(dataSize);
ByteBuffer data3 = memAlloc(dataSize);
ByteBuffer readBack = null;
try {
// First upload: offset 0
for (int i = 0; i < dataSize; i++) data1.put((byte) (i + 10));
data1.flip();
int v1 = buf.upload(data1, stride);
assertEquals(0, v1, "First upload should be at vertex 0");
// Second upload: should be at aligned offset after first
for (int i = 0; i < dataSize; i++) data2.put((byte) (i + 20));
data2.flip();
int v2 = buf.upload(data2, stride);
assertTrue(v2 > 0, "Second upload should be at a non-zero vertex offset");
assertEquals(0, v2 % 4, "Vertex offset must be quad-aligned (multiple of 4)");
// Third upload
for (int i = 0; i < dataSize; i++) data3.put((byte) (i + 30));
data3.flip();
int v3 = buf.upload(data3, stride);
assertTrue(v3 > v2, "Third upload should be after second");
assertEquals(0, v3 % 4, "Vertex offset must be quad-aligned");
// Verify all three regions have correct data
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
readBack = memAlloc(buf.getCapacity());
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
for (int i = 0; i < dataSize; i++) {
assertEquals((byte) (i + 10), readBack.get(v1 * stride + i), "Upload 1 mismatch at byte " + i);
assertEquals((byte) (i + 20), readBack.get(v2 * stride + i), "Upload 2 mismatch at byte " + i);
assertEquals((byte) (i + 30), readBack.get(v3 * stride + i), "Upload 3 mismatch at byte " + i);
}
} finally {
memFree(data1);
memFree(data2);
memFree(data3);
if (readBack != null) memFree(readBack);
}
} finally {
buf.destroy();
}
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_overflowResetsToZero(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
try {
final int stride = 16;
// First upload: establishes capacity
ByteBuffer data1 = memAlloc(64);
try {
fillPattern(data1);
data1.flip();
buf.upload(data1, stride);
} finally {
memFree(data1);
}
int capacityAfterFirst = buf.getCapacity();
// Second upload: fills remaining space or overflows
ByteBuffer data2 = memAlloc(capacityAfterFirst); // exactly fills or overflows
try {
fillPattern(data2);
data2.flip();
int v2 = buf.upload(data2, stride);
// Either sub-allocated (if it fit after alignment) or reset to 0 (overflow)
assertEquals(0, v2 % 4, "Offset must be quad-aligned");
} finally {
memFree(data2);
}
} finally {
buf.destroy();
}
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphan_postDrawResetsForNextFrame(UploadStrategy strategy) {
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
try {
final int stride = 16;
ByteBuffer data = memAlloc(64);
try {
// Frame 1: two uploads
fillPattern(data);
data.flip();
buf.upload(data, stride);
data.position(0).limit(64);
int v2 = buf.upload(data, stride);
assertTrue(v2 > 0, "Second upload in frame should be at non-zero offset");
// End of frame
buf.postDraw();
assertEquals(0, buf.getWritePos(), "writePos should reset after postDraw");
// Frame 2: first upload should be at offset 0 again (after orphan)
data.position(0).limit(64);
int v3 = buf.upload(data, stride);
assertEquals(0, v3, "First upload after postDraw should return 0");
} finally {
memFree(data);
}
} finally {
buf.destroy();
}
}
@Test
void persistent_uploadAlignment() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");