Compare commits

...
18 Commits
Author SHA1 Message Date
Jason Mitchell c5c4b290b1 Add missing overload (#1495) 2026-03-10 11:50:58 -07:00
Jason Mitchell 4b46de4566 Botania fix take two (#1501) 2026-03-10 10:22:06 -07:00
Jason Mitchell 9f2a71be4e Add missing overloads (#1500) 2026-03-10 10:06:12 -07:00
Jason Mitchell 79fe08417c Fix overwrite visibility crash (#1499) 2026-03-10 09:45:50 -07:00
Jason Mitchell 5de64abdef Maybe make Xaero work on j8 (#1494) 2026-03-09 19:47:41 -07:00
Death Fuel 2e07465166 UI Fix (#1492) 2026-03-09 16:22:44 -07:00
Death Fuel 2b610b9aea Better font rendering MC parity + book mode (#1489) 2026-03-09 11:49:33 -07:00
MalTeeez ab192ddd2a Split MixinRenderManager into two for DAPI compat (#1480) 2026-03-08 06:07:01 +01:00
Jason Mitchell cb5e2ed7d4 Lazy init entity lighting maps (#1476) 2026-03-07 14:01:55 -08:00
ah-OOG-ah e914ef7563 Actually sleep in FPS limiter (#1471) 2026-03-07 12:20:28 -08:00
Jason Mitchell 748dbd55a1 So secret you can't see them. (#1475) 2026-03-07 11:48:24 -08:00
Jason Mitchell 140c0c2285 Snapshot VAO/VBO data during display list compilation, like mesa does. (#1473) 2026-03-07 11:17:08 -08:00
sisyphussy af26442b98 Fix intel tests (#1442) 2026-03-07 10:12:56 -08:00
danyadevandAlexdoru ef07ee9f77 Refactor CeleritasBlockTransform (#1455)
Co-authored-by: Alexdoru <[email protected]>
2026-03-07 17:26:39 +01:00
Jason Mitchell 1b8087e8a3 Emulate glLineWidth on core using geom shaders (#1468) 2026-03-06 19:05:06 -08:00
Jason Mitchell 9107ea9ae5 Fix load -> enter -> leave: panorama rendering, cleanup state & fix test (#1467) 2026-03-06 19:01:43 -08:00
Jason Mitchell 938a6bda90 Replace panorama with modern opengl equivalent (#1466) 2026-03-06 16:01:27 -08:00
Jason Mitchell e9f6f72741 More core profile followons (#1464) 2026-03-06 15:50:30 -08:00
60 changed files with 2522 additions and 726 deletions

No files matched your search

+1 -1
View File
@@ -72,7 +72,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.33:dev")
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
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;
@@ -57,6 +58,10 @@ 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;
@@ -440,7 +445,7 @@ public class BatchingFontRenderer {
}
public boolean forceDefaults() {
return this.isSGA || this.isSplash;
return this.bookMode || this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
@@ -596,7 +601,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 +651,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();
}
@@ -48,6 +48,11 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace main menu panorama with modern equivalent.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enablePanoramaBlurShader;
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -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. ",
@@ -177,7 +177,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glDeleteBuffers(int buffer) {
GL15.glDeleteBuffers(buffer);
GLStateManager.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@@ -189,7 +189,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glGenTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
final IntBuffer buffer = getIntArrayBuffer(textures.length);
GL11.glGenTextures(buffer);
buffer.get(textures);
}
@@ -201,7 +201,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glDeleteTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
final IntBuffer buffer = getIntArrayBuffer(textures.length);
buffer.put(textures);
buffer.flip();
GLStateManager.glDeleteTextures(buffer);
@@ -353,7 +353,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void setBoundTexture(int unit, int texture) {
int currentUnit = GLStateManager.getActiveTextureUnit();
final int currentUnit = GLStateManager.getActiveTextureUnit();
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
}
@@ -1,12 +1,13 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -35,7 +36,6 @@ public class FeedbackManager {
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
private static final IntBuffer attribBuf = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
@FunctionalInterface
private interface IndexReader {
@@ -58,16 +58,11 @@ public class FeedbackManager {
}
public static int glRenderMode(int mode) {
int result = 0;
switch (renderMode) {
case GL11.GL_FEEDBACK:
result = (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
break;
case GL11.GL_RENDER:
case GL11.GL_SELECT:
result = 0;
break;
}
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
AngelicaTweaker.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
@@ -130,12 +125,19 @@ public class FeedbackManager {
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
int stride = queryPositionStride();
if (stride == 0) stride = 12; // tightly packed 3 floats
final long posOffset = queryPositionOffset();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
@@ -146,10 +148,9 @@ public class FeedbackManager {
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
int stride = queryPositionStride();
if (stride == 0) stride = 12;
final int stride = positionStride();
final int elementSize = elementSizeForType(type);
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
@@ -158,12 +159,12 @@ public class FeedbackManager {
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < indexCount; i++) {
final int idx = readIndex(indexBuf, type, i);
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = queryPositionOffset();
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
@@ -171,7 +172,7 @@ public class FeedbackManager {
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> readIndex(indexBuf, type, i) - base, stride);
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
@@ -202,8 +203,7 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
int stride = queryPositionStride();
if (stride == 0) stride = 12;
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -221,12 +221,6 @@ public class FeedbackManager {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int queryPositionStride() {
attribBuf.clear();
GL20.glGetVertexAttrib(0, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE, attribBuf);
return attribBuf.get(0);
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
@@ -238,24 +232,6 @@ public class FeedbackManager {
};
}
private static int elementSizeForType(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> 1;
case GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_UNSIGNED_INT -> 4;
default -> 4;
};
}
private static int readIndex(ByteBuffer buf, int type, int i) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> buf.get(i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> buf.getShort(i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> buf.getInt(i * 4);
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
@@ -268,13 +244,8 @@ public class FeedbackManager {
}
}
private static long queryPositionOffset() {
final ByteBuffer buf = GL20.glGetVertexAttribPointer(0, GL20.GL_VERTEX_ATTRIB_ARRAY_POINTER, 8);
return buf == null ? 0L : buf.getLong(0);
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = queryPositionOffset();
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
@@ -31,7 +31,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClientArrayState;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
@@ -285,8 +285,6 @@ public class GLStateManager {
private static int clientActiveTextureUnit = 0;
protected static final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
// Client array state (not push/pop tracked, but needed for display list compilation)
@Getter private static final ClientArrayState clientArrayState = new ClientArrayState();
@Getter protected static final TextureUnitArray textures = new TextureUnitArray();
@Getter protected static final BlendStateStack blendState = new BlendStateStack();
@@ -337,6 +335,10 @@ public class GLStateManager {
// Line width range queried at init from GL_ALIASED_LINE_WIDTH_RANGE
static float lineWidthMin = 1.0f;
static float lineWidthMax = 1.0f;
/** True when the driver cannot render wide lines natively (Mesa forward-compat). GS emulation handles widths > 1.0. */
public static boolean wideLineEmulationEnabled = false;
/** Set per draw call: true when the current draw is a line primitive that needs GS expansion. Read by {@link com.gtnewhorizons.angelica.glsm.ffp.VertexKey}. */
public static boolean wideLineEmulationActive = false;
// Point state (GL_POINT_BIT)
@Getter protected static final PointStateStack pointState = new PointStateStack();
@@ -441,6 +443,7 @@ public class GLStateManager {
modelViewMatrix.clear();
projectionMatrix.clear();
vaoEboMap.clear();
VertexAttribState.reset();
}
/**
@@ -498,6 +501,11 @@ public class GLStateManager {
final String glVendor = GL11.glGetString(GL11.GL_VENDOR);
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
// The initial mask value should be defined as all 1's. However, some drivers have it set to 0's.
// To ensure consistency & correctness across all drivers, we're setting them to 0xFF.
GL11.glStencilFunc(stencilState.getFuncFront(), stencilState.getRefFront(), 0xFF);
GL11.glStencilMask(0xFF);
// Compute stencil bit mask — driver clamps stencil masks to buffer depth
// GL_STENCIL_BITS was removed in core profile; query via default FBO attachment
final int stencilBits = GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
@@ -513,6 +521,35 @@ public class GLStateManager {
GL11.glGetFloat(GL12.GL_ALIASED_LINE_WIDTH_RANGE, lwRange);
lineWidthMin = lwRange.get(0);
lineWidthMax = lwRange.get(1);
final float queriedMax = lineWidthMax;
// Probe whether the driver actually accepts the queried max (Mesa forward-compat rejects > 1.0)
if (lineWidthMax > 1.0f) {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
GL11.glLineWidth(lineWidthMax);
if (GL11.glGetError() != GL11.GL_NO_ERROR) {
// Binary search for the actual accepted max between 1.0 and queriedMax
float lo = 1.0f, hi = queriedMax;
while (hi - lo > 0.01f) {
final float mid = (lo + hi) * 0.5f;
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
GL11.glLineWidth(mid);
if (GL11.glGetError() == GL11.GL_NO_ERROR) {
lo = mid;
} else {
hi = mid;
}
}
lineWidthMax = lo;
}
GL11.glLineWidth(1.0f);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
wideLineEmulationEnabled = lineWidthMax <= 1.0f;
if (wideLineEmulationEnabled) {
LOGGER.info("GL line width: aliased range [{}, {}], probe at {} rejected, native [{}, {}], GS emulation active for [{}, {}]", lineWidthMin, queriedMax, queriedMax, lineWidthMin, lineWidthMax, lineWidthMax, queriedMax);
} else {
LOGGER.info("GL line width: aliased range [{}, {}], probe at {} accepted, native [{}, {}]", lineWidthMin, queriedMax, queriedMax, lineWidthMin, lineWidthMax);
}
// Sync default vertex attribs with GLSM cache initial values.
GL20.glVertexAttrib4f(1, 1.0f, 1.0f, 1.0f, 1.0f); // COLOR = white
@@ -545,6 +582,7 @@ public class GLStateManager {
defaultVAO = GL30.glGenVertexArrays();
GL30.glBindVertexArray(defaultVAO);
boundVAO = defaultVAO;
VertexAttribState.init(defaultVAO);
// 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.
@@ -1994,6 +2032,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);
@@ -2011,6 +2055,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!");
@@ -2019,6 +2070,7 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
GL11.glDrawElements(mode, indices);
}
@@ -2031,6 +2083,7 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
@@ -2047,6 +2100,7 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
@@ -2063,6 +2117,7 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, count, type, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
@@ -2076,6 +2131,11 @@ public class GLStateManager {
if (recordMode != RecordMode.NONE) {
if (isVAOBound()) {
DisplayListManager.recordDrawElements(mode, indices_count, type, indices_buffer_offset);
} else if (isVBOBound() || VertexAttribState.hasVBOBoundAttrib()) {
final DirectTessellator result = ImmediateModeRecorder.processDrawElementsFromVBO(mode, indices_count, type, indices_buffer_offset, boundEBO);
if (result != null) {
DisplayListManager.addImmediateModeDraw(result);
}
} else {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
}
@@ -2087,6 +2147,7 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices_count, type, indices_buffer_offset);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
@@ -2109,11 +2170,7 @@ public class GLStateManager {
public static void glDrawArrays(int mode, int first, int count) {
if (DisplayListManager.isRecording()) {
if (isVAOBound() || isVBOBound()) { // TODO VBO rendering needs to be replaced with proper VAO compilation
DisplayListManager.recordDrawArrays(mode, first, count);
return;
}
final DirectTessellator result = ImmediateModeRecorder.processDrawArrays(mode, first, count);
final DirectTessellator result = ImmediateModeRecorder.processDrawArraysFromAttribs(mode, first, count);
if (result != null) {
DisplayListManager.addImmediateModeDraw(result);
}
@@ -2123,6 +2180,7 @@ public class GLStateManager {
FeedbackManager.processDrawArrays(mode, first, count);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
@@ -2131,9 +2189,6 @@ public class GLStateManager {
}
}
// Client array state interception methods (for glDrawArrays conversion)
// State is tracked globally in clientArrayState, which ImmediateModeRecorder reads from directly.
public static void glVertexPointer(int size, int stride, IntBuffer pointer) {
glVertexPointer(size, GL11.GL_INT, stride, MemoryUtilities.memByteBuffer(pointer));
}
@@ -2143,32 +2198,36 @@ public class GLStateManager {
}
public static void glVertexPointer(int size, int stride, FloatBuffer pointer) {
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexPointer(int size, int type, int stride, ByteBuffer pointer) {
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
}
public static void glVertexPointer(int size, int type, int stride, long pointer_buffer_offset) {
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer_buffer_offset);
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer_buffer_offset);
}
public static void glColorPointer(int size, int stride, FloatBuffer pointer) {
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_FLOAT, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_FLOAT, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glColorPointer(int size, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_DOUBLE, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glColorPointer(int size, int type, int stride, ByteBuffer pointer) {
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
}
public static void glColorPointer(int size, boolean unsigned, int stride, ByteBuffer pointer) {
final int type = unsigned ? GL11.GL_UNSIGNED_BYTE : GL11.GL_BYTE;
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
}
public static void glColorPointer(int size, int type, int stride, long pointer_buffer_offset) {
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer_buffer_offset);
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer_buffer_offset);
}
public static void glNormalPointer(int stride, FloatBuffer pointer) {
@@ -2184,29 +2243,29 @@ public class GLStateManager {
}
public static void glNormalPointer(int type, int stride, ByteBuffer pointer) {
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
}
public static void glNormalPointer(int type, int stride, FloatBuffer pointer) {
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, ShortBuffer pointer) {
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_SHORT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_SHORT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, IntBuffer pointer) {
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
}
public static void glTexCoordPointer(int size, int type, int stride, ByteBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
GL20.glVertexAttribPointer(loc, size, type, false, stride, pointer);
glVertexAttribPointer(loc, size, type, false, stride, pointer);
}
public static void glTexCoordPointer(int size, int stride, FloatBuffer pointer) {
@@ -2224,41 +2283,111 @@ public class GLStateManager {
public static void glTexCoordPointer(int size, int type, int stride, FloatBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
GL20.glVertexAttribPointer(loc, size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(loc, size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, ShortBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
GL20.glVertexAttribPointer(loc, size, GL11.GL_SHORT, false, stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(loc, size, GL11.GL_SHORT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, IntBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
GL20.glVertexAttribPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
glVertexAttribPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
GL20.glVertexAttribPointer(loc, size, type, false, stride, pointer_buffer_offset);
glVertexAttribPointer(loc, size, type, false, stride, pointer_buffer_offset);
}
public static void glEnableClientState(int cap) {
final int location = clientStateToAttributeLocation(cap);
if (location >= 0) GL20.glEnableVertexAttribArray(location);
if (location >= 0) glEnableVertexAttribArray(location);
final int flag = clientStateToVertexFlag(cap);
if (flag != 0) ShaderManager.getInstance().enableClientVertexFlag(flag);
}
public static void glDisableClientState(int cap) {
final int location = clientStateToAttributeLocation(cap);
if (location >= 0) GL20.glDisableVertexAttribArray(location);
if (location >= 0) glDisableVertexAttribArray(location);
final int flag = clientStateToVertexFlag(cap);
if (flag != 0) ShaderManager.getInstance().disableClientVertexFlag(flag);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long offset) {
VertexAttribState.set(index, size, type, normalized, stride, offset, boundVBO);
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);
GL20.glVertexAttribPointer(index, size, type, normalized, stride, bb);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, DoubleBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
GL20.glVertexAttribPointer(index, size, type, normalized, stride, bb);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, IntBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
GL20.glVertexAttribPointer(index, size, type, normalized, stride, bb);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ShortBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
GL20.glVertexAttribPointer(index, size, type, normalized, stride, bb);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, pointer, boundVBO);
GL20.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, long offset) {
VertexAttribState.set(index, size, type, false, stride, offset, boundVBO);
GL30.glVertexAttribIPointer(index, size, type, stride, offset);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, false, stride, pointer, boundVBO);
GL30.glVertexAttribIPointer(index, size, type, stride, pointer);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, IntBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, false, stride, bb, boundVBO);
GL30.glVertexAttribIPointer(index, size, type, stride, bb);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ShortBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, false, stride, bb, boundVBO);
GL30.glVertexAttribIPointer(index, size, type, stride, bb);
}
public static void glEnableVertexAttribArray(int index) {
VertexAttribState.setEnabled(index, true);
GL20.glEnableVertexAttribArray(index);
}
public static void glDisableVertexAttribArray(int index) {
VertexAttribState.setEnabled(index, false);
GL20.glDisableVertexAttribArray(index);
}
private static int clientStateToAttributeLocation(int cap) {
return switch (cap) {
case GL11.GL_VERTEX_ARRAY -> Usage.POSITION.getAttributeLocation();
@@ -2910,7 +3039,7 @@ public class GLStateManager {
GL11.glDepthRange(viewportState.depthRangeNear, viewportState.depthRangeFar);
}
if ((mask & GL11.GL_LINE_BIT) != 0) {
GL11.glLineWidth(lineState.getWidth());
GL11.glLineWidth(Math.max(lineWidthMin, Math.min(lineWidthMax, lineState.getWidth())));
}
if ((mask & GL11.GL_POINT_BIT) != 0) {
GL11.glPointSize(pointState.getSize());
@@ -3914,7 +4043,6 @@ public class GLStateManager {
}
public static void glLineWidth(float width) {
width = Math.max(lineWidthMin, Math.min(lineWidthMax, width));
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordLineWidth(width);
@@ -3927,10 +4055,14 @@ public class GLStateManager {
if (caching) {
lineState.setWidth(width);
}
GL11.glLineWidth(width);
GL11.glLineWidth(Math.max(lineWidthMin, Math.min(lineWidthMax, width)));
}
}
public static void prepareWideLineEmulation(int drawMode) {
wideLineEmulationActive = wideLineEmulationEnabled && (drawMode == GL11.GL_LINES || drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP) && lineState.getWidth() > 1.0f;
}
public static void glShaderSource(int shader, CharSequence source) {
String src = source.toString();
if (ShaderManager.getInstance().isEnabled()) {
@@ -4456,6 +4588,30 @@ public class GLStateManager {
}
}
public static void glDeleteBuffers(int buffer) {
if (buffer == 0) return;
invalidateDeletedBuffer(buffer);
GL15.glDeleteBuffers(buffer);
}
public static void glDeleteBuffers(IntBuffer buffers) {
for (int i = 0; i < buffers.remaining(); i++) {
invalidateDeletedBuffer(buffers.get(buffers.position() + i));
}
GL15.glDeleteBuffers(buffers);
}
private static void invalidateDeletedBuffer(int buffer) {
if (buffer == 0) return;
if (boundVBO == buffer) {
boundVBO = 0;
}
if (boundEBO == buffer) {
boundEBO = 0;
vaoEboMap.put(boundVAO, 0);
}
}
public static void glBindBuffer(int target, int buffer) {
if (target == GL15.GL_ARRAY_BUFFER) {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
@@ -4479,6 +4635,7 @@ public class GLStateManager {
if (boundVAO != array) {
boundVAO = array;
boundEBO = vaoEboMap.get(array);
VertexAttribState.onBindVertexArray(array);
UniversalVAO.bindVertexArray(array);
if (ShaderManager.getInstance().isEnabled()) {
ShaderManager.getInstance().onBindVertexArray(array);
@@ -4488,11 +4645,13 @@ public class GLStateManager {
public static void glDeleteVertexArrays(int array) {
ShaderManager.getInstance().onDeleteVertexArray(array);
VertexAttribState.onDeleteVertexArray(array);
vaoEboMap.remove(array);
if (array == boundVAO) {
// Deleting the bound VAO implicitly unbinds it. Rebind the default VAO.
boundVAO = defaultVAO;
boundEBO = vaoEboMap.get(defaultVAO);
VertexAttribState.onBindVertexArray(defaultVAO);
UniversalVAO.bindVertexArray(defaultVAO);
}
UniversalVAO.deleteVertexArrays(array);
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -142,13 +143,7 @@ public final class QuadConverter {
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex;
switch (type) {
case GL11.GL_UNSIGNED_INT: bytesPerIndex = 4; break;
case GL11.GL_UNSIGNED_SHORT: bytesPerIndex = 2; break;
case GL11.GL_UNSIGNED_BYTE: bytesPerIndex = 1; break;
default: return;
}
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
@@ -132,7 +132,6 @@ public class RenderSystem {
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
GLStateManager.lineWidthMax = 1.0f;
}
}
}
@@ -0,0 +1,142 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Cylinder;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Cylinder}.
*/
@SuppressWarnings("unused")
public class AngelicaCylinder extends Cylinder {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
final float da, dr, dz, nz, nsign;
float r, x, y, z;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
da = 2.0f * PI / slices;
dr = (topRadius - baseRadius) / stacks;
dz = height / stacks;
nz = (baseRadius - topRadius) / height;
if (super.drawStyle == GLU_POINT) {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glVertex3f(x * r, y * r, z);
z += dz;
r += dr;
}
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// Draw rings
if (super.drawStyle == GLU_LINE) {
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * r, y * r, z);
}
GLStateManager.glEnd();
z += dz;
r += dr;
}
} else {
// draw one ring at each end
if (baseRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
}
GLStateManager.glEnd();
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
}
}
// draw length lines
GLStateManager.glBegin(GL_LINES);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_FILL) {
final float ds = 1.0f / slices;
final float dt = 1.0f / stacks;
float t = 0.0f;
z = 0.0f;
r = baseRadius;
for (int j = 0; j < stacks; j++) {
float s = 0.0f;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
final float angle = (i == slices) ? 0.0f : i * da;
x = sin(angle);
y = cos(angle);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * r, y * r, z);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t + dt);
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
s += ds;
}
GLStateManager.glEnd();
r += dr;
t += dt;
z += dz;
}
}
}
}
@@ -0,0 +1,134 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Disk;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Disk}.
*/
@SuppressWarnings("unused")
public class AngelicaDisk extends Disk {
private static final float PI = (float) Math.PI;
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
final float da, dr;
if (super.normals != GLU_NONE) {
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
}
da = 2.0f * PI / slices;
dr = (outerRadius - innerRadius) / loops;
switch (super.drawStyle) {
case GLU_FILL -> {
final float dtc = 2.0f * outerRadius;
float sa, ca;
float r1 = innerRadius;
for (int l = 0; l < loops; l++) {
final float r2 = r1 + dr;
if (super.orientation == GLU_OUTSIDE) {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = 0; s <= slices; s++) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
} else {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = slices; s >= 0; s--) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
}
r1 = r2;
}
}
case GLU_LINE -> {
/* draw loops */
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int s = 0; s < slices; s++) {
final float a = s * da;
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
}
GLStateManager.glEnd();
}
/* draw spokes */
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
GLStateManager.glBegin(GL_LINE_STRIP);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius * l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
}
GLStateManager.glEnd();
}
case GLU_SILHOUETTE -> {
if (innerRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0.0f; a < 2.0 * PI; a += da) {
final float x = innerRadius * sin(a);
final float y = innerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0; a < 2.0f * PI; a += da) {
final float x = outerRadius * sin(a);
final float y = outerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
default -> {
}
}
}
}
@@ -0,0 +1,244 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.PartialDisk;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
/**
* Core-profile compatible replacement for {@link PartialDisk}.
*/
@SuppressWarnings("unused")
public class AngelicaPartialDisk extends PartialDisk {
private static final float PI = (float) Math.PI;
private static final int CACHE_SIZE = 240;
private final float[] sinCache = new float[CACHE_SIZE];
private final float[] cosCache = new float[CACHE_SIZE];
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
final float deltaRadius, angleOffset;
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
final int slices2, finish;
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
return;
}
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
if (sweepAngle < 0) {
startAngle += sweepAngle;
sweepAngle = -sweepAngle;
}
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
/* Compute length (needed for normal calculations) */
deltaRadius = outerRadius - innerRadius;
angleOffset = startAngle / 180.0f * PI;
for (int i = 0; i <= slices; i++) {
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
sinCache[i] = sin(angle);
cosCache[i] = cos(angle);
}
if (sweepAngle == 360.0f) {
sinCache[slices] = sinCache[0];
cosCache[slices] = cosCache[0];
}
switch (super.normals) {
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
default -> {
}
}
switch (super.drawStyle) {
case GLU_FILL -> {
if (innerRadius == .0f) {
finish = loops - 1;
/* Triangle strip for inner polygons */
GLStateManager.glBegin(GL_TRIANGLE_FAN);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(0.5f, 0.5f);
}
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.orientation == GLU_OUTSIDE) {
for (int i = slices; i >= 0; i--) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
} else {
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
} else {
finish = loops;
}
for (int j = 0; j < finish; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
texHigh = radiusHigh / outerRadius / 2;
}
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.orientation == GLU_OUTSIDE) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
} else {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
}
GLStateManager.glEnd();
}
case GLU_LINE -> {
if (innerRadius == outerRadius) {
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
}
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
break;
}
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
}
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
case GLU_SILHOUETTE -> {
if (sweepAngle < 360.0f) {
for (int i = 0; i <= slices; i += slices) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
for (int j = 0; j <= loops; j += loops) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
if (innerRadius == outerRadius) break;
}
}
default -> {
}
}
}
}
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Sphere;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Sphere}.
*/
@SuppressWarnings("unused")
public class AngelicaSphere extends Sphere {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float radius, int slices, int stacks) {
final float drho, dtheta, ds, dt, nsign;
float rho, theta, x, y, z, s, t;
final int imin, imax;
final boolean normals;
normals = super.normals != GLU_NONE;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
drho = PI / stacks;
dtheta = 2.0f * PI / slices;
if (super.drawStyle == GLU_FILL) {
if (!super.textureFlag) {
// draw +Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(drho);
y = cos(theta) * sin(drho);
z = nsign * cos(drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
ds = 1.0f / slices;
dt = 1.0f / stacks;
t = 1.0f;
if (super.textureFlag) {
imin = 0;
imax = stacks;
} else {
imin = 1;
imax = stacks - 1;
}
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
for (int i = imin; i < imax; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
s = 0.0f;
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
x = -sin(theta) * sin(rho + drho);
y = cos(theta) * sin(rho + drho);
z = nsign * cos(rho + drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t - dt);
s += ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
t -= dt;
}
if (!super.textureFlag) {
// draw -Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
rho = PI - drho;
s = 1.0f;
for (int j = slices; j >= 0; j--) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
s -= ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// draw stack lines
for (int i = 1; i < stacks; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
// draw slice lines
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= stacks; i++) {
rho = i * drho;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_POINT) {
// top and bottom-most points
GLStateManager.glBegin(GL_POINTS);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
// loop over stacks
for (int i = 1; i < stacks - 1; i++) {
rho = i * drho;
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
}
GLStateManager.glEnd();
}
}
}
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* Generates GLSL 330 core geometry shaders for wide line emulation.
*/
public final class GeometryShaderGenerator {
private GeometryShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("#version 330 core\n\n");
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append("uniform vec2 u_ViewportSize;\n");
sb.append("uniform float u_LineWidth;\n\n");
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("in vec4 v_TexCoord0_gs[];\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1_gs[];\n");
}
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
sb.append('\n');
sb.append("void main() {\n");
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
emitVertex(sb, key, 0, "+offset");
emitVertex(sb, key, 0, "-offset");
emitVertex(sb, key, 1, "+offset");
emitVertex(sb, key, 1, "-offset");
sb.append(" EndPrimitive();\n");
sb.append("}\n");
return sb.toString();
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String ci = "c" + endpointIdx;
final String ni = "n" + endpointIdx;
final String idx = "[" + endpointIdx + "]";
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
}
if (key.lightmapEnabled()) {
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
}
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
}
@@ -8,12 +8,15 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A compiled and linked FFP emulation program (vertex + fragment shader pair). Owns the GL program handle and caches uniform locations.
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
@@ -82,6 +85,10 @@ public class Program {
// Clip planes
public int locClipPlanes = -1;
// Wide line emulation (geometry shader)
public int locViewportSize = -1;
public int locLineWidth = -1;
// Fragment uniforms
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
@@ -151,6 +158,10 @@ public class Program {
// Clip planes
locClipPlanes = loc("u_ClipPlane[0]");
// Wide line emulation
locViewportSize = loc("u_ViewportSize");
locLineWidth = loc("u_LineWidth");
// Fragment
for (int i = 0; i < 4; i++) {
locSampler[i] = loc("u_Sampler" + i);
@@ -172,50 +183,61 @@ public class Program {
}
/**
* Compile a shader, link it into a program, and return the FFPProgram.
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
*/
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc) {
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
final int id = PROGRAM_COUNTER.getAndIncrement();
final int vs = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + Long.toHexString(vk.pack()));
final int fs = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
final int program = GL20.glCreateProgram();
GL20.glAttachShader(program, vs);
GL20.glAttachShader(program, fs);
GL20.glLinkProgram(program);
int shaderCount = 0;
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
int program = 0;
try {
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
if (geomSrc != null) {
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
}
final String log = RenderSystem.getProgramInfoLog(program);
if (!log.isEmpty()) {
LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", Long.toHexString(vk.pack()), fk, log);
program = GL20.glCreateProgram();
for (int i = 0; i < shaderCount; i++) GL20.glAttachShader(program, shaders[i]);
GL20.glLinkProgram(program);
final String log = RenderSystem.getProgramInfoLog(program);
if (!log.isEmpty()) {
LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", vkHex, fk, log);
}
if (GL20.glGetProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders — they're linked into the program
for (int i = 0; i < shaderCount; i++) {
GL20.glDetachShader(program, shaders[i]);
GL20.glDeleteShader(shaders[i]);
}
shaderCount = 0;
final Program ffpProgram = new Program(program, vk, fk);
final int previousProgram = GLStateManager.getActiveProgram();
GL20.glUseProgram(program);
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) GL20.glUniform1i(ffpProgram.locSampler[i], i);
}
GL20.glUseProgram(previousProgram);
return ffpProgram;
} catch (RuntimeException e) {
if (program != 0) GL20.glDeleteProgram(program);
for (int i = 0; i < shaderCount; i++) GL20.glDeleteShader(shaders[i]);
throw e;
}
final int linkStatus = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
if (linkStatus != GL11.GL_TRUE) {
GL20.glDeleteProgram(program);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
throw new RuntimeException("FFP shader link failed (vk=0x" + Long.toHexString(vk.pack()) + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + Long.toHexString(vk.pack()) + ",f=" + id + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders — they're linked into the program
GL20.glDetachShader(program, vs);
GL20.glDetachShader(program, fs);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
final Program ffpProgram = new Program(program, vk, fk);
final int previousProgram = GLStateManager.getActiveProgram();
GL20.glUseProgram(program);
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) GL20.glUniform1i(ffpProgram.locSampler[i], i);
}
GL20.glUseProgram(previousProgram);
return ffpProgram;
}
private static int compileShader(int type, String src, String name) {
@@ -223,7 +245,15 @@ public class Program {
GL20.glShaderSource(shader, src);
GL20.glCompileShader(shader);
final String typeName = (type == GL20.GL_VERTEX_SHADER) ? "vertex" : "fragment";
final String typeName = switch (type) {
case GL20.GL_VERTEX_SHADER -> "vertex";
case GL20.GL_FRAGMENT_SHADER -> "fragment";
case GL32.GL_GEOMETRY_SHADER -> "geometry";
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
case GL43.GL_COMPUTE_SHADER -> "compute";
default -> "unknown(0x" + Integer.toHexString(type) + ")";
};
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
final String log = RenderSystem.getShaderInfoLog(shader);
@@ -32,6 +32,7 @@ public class ShaderCache {
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
@@ -108,7 +109,8 @@ public class ShaderCache {
uniqueFragCount++;
}
final Program program = Program.create(vk, fk, vertSrc, fragSrc);
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
@@ -126,6 +128,9 @@ public class ShaderCache {
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
@@ -216,6 +221,10 @@ public class ShaderCache {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
@@ -244,8 +253,11 @@ public class ShaderCache {
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), seen.size(), outputDir);
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
@@ -264,6 +276,7 @@ public class ShaderCache {
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
LOGGER.debug("FFP shader cache cleared");
@@ -234,6 +234,7 @@ public class TessellatorStreamingDrawer {
firstVertex = orphanBuffers[flags].upload(packed, vertexSize);
}
GLStateManager.prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw(flags);
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
@@ -60,6 +60,9 @@ public class Uniforms {
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
private float lastLineWidth = Float.NaN;
private int lastViewportWidth = -1;
private int lastViewportHeight = -1;
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
@@ -144,6 +147,11 @@ public class Uniforms {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
@@ -450,6 +458,21 @@ public class Uniforms {
}
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
GL20.glUniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
GL20.glUniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
}
}
private void uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
@@ -42,6 +42,7 @@ public final class VertexKey {
private static final int BIT_TEXGEN_Q = 31;
// bits 31-33: texGenModeQ (3 bits)
private static final int BIT_CLIP_PLANES = 34;
private static final int BIT_WIDE_LINE = 35;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -92,6 +93,8 @@ public final class VertexKey {
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
/** Whether any clip plane is enabled. */
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
/** Whether wide line GS emulation is active for this variant. */
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
/** Whether vertex color replaces material ambient in this variant. */
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -223,6 +226,11 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
return bits;
}
@@ -248,10 +256,10 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b]",
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled());
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
}
@@ -37,7 +37,15 @@ public final class VertexShaderGenerator {
}
sb.append("}\n");
return sb.toString();
String source = sb.toString();
if (key.wideLineEmulation()) {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
@@ -2,22 +2,24 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClientArrayState;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
*/
public final class ImmediateModeRecorder {
private static final Logger LOGGER = LogManager.getLogger("ImmediateModeRecorder");
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
/** Separate tessellator for the splash thread. Nulled after splash completes. */
@@ -157,192 +159,292 @@ public final class ImmediateModeRecorder {
tessellator().reset();
}
/**
* Process a glDrawArrays call during display list recording.
* Reads vertex data from client arrays (tracked globally in GLStateManager) and converts to immediate mode geometry.
*
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
* @param first Starting index in arrays
* @param count Number of vertices
* @return Result containing converted geometry, or null if no geometry produced
*/
public static DirectTessellator processDrawArrays(int mode, int first, int count) {
final ClientArrayState cas = GLStateManager.getClientArrayState();
final Buffer vertexPointer = cas.isVertexArrayEnabled() ? cas.getVertexPointer() : null;
final Buffer colorPointer = cas.isColorArrayEnabled() ? cas.getColorPointer() : null;
final Buffer normalPointer = cas.isNormalArrayEnabled() ? cas.getNormalPointer() : null;
final Buffer texCoordPointer = cas.isTexCoordArrayEnabled() ? cas.getTexCoordPointer() : null;
private static final int LOC_POSITION = Usage.POSITION.getAttributeLocation();
private static final int LOC_COLOR = Usage.COLOR.getAttributeLocation();
private static final int LOC_UV0 = Usage.PRIMARY_UV.getAttributeLocation();
private static final int LOC_UV1 = Usage.SECONDARY_UV.getAttributeLocation();
private static final int LOC_NORMAL = Usage.NORMAL.getAttributeLocation();
private static final int NUM_ATTRIBS = 1 + Arrays.stream(Usage.values())
.mapToInt(Usage::getAttributeLocation)
.max().orElse(0);
return convertClientArrays(
mode, first, count,
vertexPointer,
cas.getVertexPointerType(), cas.getVertexPointerSize(), cas.getVertexPointerStride(),
colorPointer,
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride(),
normalPointer,
cas.getNormalPointerType(), cas.getNormalPointerStride(),
texCoordPointer,
cas.getTexCoordPointerType(), cas.getTexCoordPointerSize(), cas.getTexCoordPointerStride()
);
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
private static final class AttribReader {
ByteBuffer data;
int stride;
int baseOffset;
int size;
int loc;
VertexAttribState.Attrib attrib;
}
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
private static int nonPositionCount;
private static final int[] nonPositionIndices = new int[NUM_ATTRIBS - 1];
private static int positionReaderIndex = -1;
static {
for (int i = 0; i < NUM_ATTRIBS; i++) {
readers[i] = new AttribReader();
}
}
/**
* Convert client array data to immediate mode.
* Called by glDrawArrays during display list recording.
* Like Mesa's save_DrawArrays, reads vertex data from arrays and emits as immediate mode.
* Process a glDrawArrays call during display list recording.
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
*/
public static DirectTessellator convertClientArrays(
int mode, int first, int count,
java.nio.Buffer vertexPointer, int vertexType, int vertexSize, int vertexStride,
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride,
java.nio.Buffer normalPointer, int normalType, int normalStride,
java.nio.Buffer texCoordPointer, int texCoordType, int texCoordSize, int texCoordStride
) {
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final int readVBOCount = readbackAttribVBOs(first, count);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
return readVerticesFromBuffer(mode, first, count);
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Process a glDrawElements call during display list recording when a VBO is bound.
*/
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
if (eboId == 0) return null;
final int readVBOCount = readbackAttribVBOs(-1, 0);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboSize = GL15.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboSize <= 0) {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
final int eboReadOffset = (int) indicesOffset;
final int eboReadSize = indicesCount * indexSize;
if (eboReadOffset + eboReadSize > eboSize) {
LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
try {
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
} finally {
MemoryUtilities.memFree(eboData);
}
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Read data for all enabled attribs into scratchAttribBuffers.
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
*/
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
int readVBOCount = 0;
final int prevVBO = GLStateManager.getBoundVBO();
final boolean partialRead = firstVertex >= 0;
for (int i = 0; i < NUM_ATTRIBS; i++) {
scratchAttribBuffers[i] = null;
scratchAttribBaseOffsets[i] = 0;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
if (a.vboId != 0) {
// VBO-backed attrib — read from GPU
ByteBuffer existing = null;
int existingIdx = -1;
for (int j = 0; j < readVBOCount; j++) {
if (scratchVBOIds[j] == a.vboId) {
existing = scratchVBOBuffers[j];
existingIdx = j;
break;
}
}
if (existing != null) {
scratchAttribBuffers[i] = existing;
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
} else {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
}
final long readOffset;
final int readSize;
if (partialRead) {
final int stride = a.effectiveStride();
long rangeStart = a.offset + (long) firstVertex * stride;
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
for (int k = 0; k < NUM_ATTRIBS; k++) {
if (k == i) continue;
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
if (!ak.enabled || ak.vboId != a.vboId) continue;
final int ks = ak.effectiveStride();
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
+ (long) ak.size * ak.typeSizeBytes());
}
rangeStart = Math.max(0, rangeStart);
rangeEnd = Math.min(bufferSize, rangeEnd);
readOffset = rangeStart;
readSize = (int) (rangeEnd - rangeStart);
} else {
readOffset = 0;
readSize = bufferSize;
}
if (readSize <= 0) continue;
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
scratchAttribBuffers[i] = buf;
scratchAttribBaseOffsets[i] = readOffset;
scratchVBOIds[readVBOCount] = a.vboId;
scratchVBOReadOffsets[readVBOCount] = readOffset;
scratchVBOBuffers[readVBOCount] = buf;
readVBOCount++;
}
} else if (a.clientPointer != null) {
// Client-memory attrib — use pointer directly (no allocation needed)
scratchAttribBuffers[i] = a.clientPointer;
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
return readVBOCount;
}
private static void freeReadbackBuffers(int readVBOCount) {
for (int i = 0; i < readVBOCount; i++) {
MemoryUtilities.memFree(scratchVBOBuffers[i]);
scratchVBOBuffers[i] = null;
}
}
private static void prepareReaders() {
nonPositionCount = 0;
positionReaderIndex = -1;
for (int i = 0; i < NUM_ATTRIBS; i++) {
final ByteBuffer buf = scratchAttribBuffers[i];
if (buf == null) continue;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
final AttribReader r = readers[i];
r.data = buf;
r.stride = a.effectiveStride();
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
r.size = a.size;
r.loc = i;
r.attrib = a;
if (i == LOC_POSITION) {
positionReaderIndex = i;
} else {
nonPositionIndices[nonPositionCount++] = i;
}
}
}
private static DirectTessellator readVerticesFromBuffer(int mode, int first, int count) {
begin(mode);
final DirectTessellator t = tessellator();
// Hoist type checks and stride calculations outside the loop for performance.
// Mesa's algorithm: offset_bytes = index * effective_stride
// effective_stride = (stride == 0) ? (numComponents * sizeof(element)) : stride
// Determine color reader
final ArrayReader colorReader;
if (colorPointer == null) {
colorReader = null;
} else if (colorType == GL11.GL_FLOAT && colorPointer instanceof FloatBuffer fb) {
final int colorEffectiveStride = (colorStride == 0) ? colorSize * 4 : colorStride;
final int strideInFloats = colorEffectiveStride / 4;
colorReader = (idx) -> {
final int offset = idx * strideInFloats;
final float r = fb.get(offset);
final float g = fb.get(offset + 1);
final float b = fb.get(offset + 2);
final float a = (colorSize == 4) ? fb.get(offset + 3) : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else if ((colorType == GL11.GL_UNSIGNED_BYTE || colorType == GL11.GL_BYTE) && colorPointer instanceof ByteBuffer bb) {
final int colorEffectiveStride = (colorStride == 0) ? colorSize : colorStride;
colorReader = (idx) -> {
final int offset = idx * colorEffectiveStride;
final float r = (bb.get(offset) & 0xFF) / 255.0f;
final float g = (bb.get(offset + 1) & 0xFF) / 255.0f;
final float b = (bb.get(offset + 2) & 0xFF) / 255.0f;
final float a = (colorSize == 4) ? (bb.get(offset + 3) & 0xFF) / 255.0f : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else {
// Unsupported color type - use white
colorReader = (idx) -> GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
// Determine normal reader
final ArrayReader normalReader;
if (normalPointer == null) {
normalReader = null;
} else if (normalType == GL11.GL_FLOAT && normalPointer instanceof FloatBuffer fb) {
final int effectiveStride = (normalStride == 0) ? 3 * 4 : normalStride;
final int strideInFloats = effectiveStride / 4;
normalReader = (idx) -> {
final int offset = idx * strideInFloats;
setNormal(fb.get(offset), fb.get(offset + 1), fb.get(offset + 2));
};
} else if (normalType == GL11.GL_BYTE && normalPointer instanceof ByteBuffer bb) {
final int effectiveStride = (normalStride == 0) ? 3 : normalStride;
normalReader = (idx) -> {
final int offset = idx * effectiveStride;
setNormal(bb.get(offset) / 127.0f, bb.get(offset + 1) / 127.0f, bb.get(offset + 2) / 127.0f);
};
} else {
normalReader = null;
}
// Determine texcoord reader
final ArrayReader texCoordReader;
if (texCoordPointer == null) {
texCoordReader = null;
} else if (texCoordType == GL11.GL_FLOAT && texCoordPointer instanceof FloatBuffer fb) {
final int effectiveStride = (texCoordStride == 0) ? texCoordSize * 4 : texCoordStride;
final int strideInFloats = effectiveStride / 4;
texCoordReader = (idx) -> {
final int offset = idx * strideInFloats;
final float s = fb.get(offset);
final float tt = (texCoordSize >= 2) ? fb.get(offset + 1) : 0.0f;
setTexCoord(s, tt);
};
} else {
texCoordReader = null;
}
// Determine vertex reader
final ArrayReader vertexReader;
if (vertexPointer == null) {
vertexReader = null;
} else if (vertexType == GL11.GL_FLOAT && vertexPointer instanceof FloatBuffer fb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInFloats = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInFloats;
final float x = fb.get(offset);
final float y = fb.get(offset + 1);
final float z = (vertexSize >= 3) ? fb.get(offset + 2) : 0.0f;
t.addVertex(x, y, z);
};
} else if (vertexType == GL11.GL_DOUBLE && vertexPointer instanceof DoubleBuffer db) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 8 : vertexStride;
final int strideInDoubles = effectiveStride / 8;
vertexReader = (idx) -> {
final int offset = idx * strideInDoubles;
final float x = (float) db.get(offset);
final float y = (float) db.get(offset + 1);
final float z = (vertexSize >= 3) ? (float) db.get(offset + 2) : 0.0f;
t.addVertex(x, y, z);
};
} else if (vertexType == GL11.GL_INT && vertexPointer instanceof IntBuffer ib) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInInts = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInInts;
final float x = ib.get(offset);
final float y = ib.get(offset + 1);
final float z = (vertexSize >= 3) ? ib.get(offset + 2) : 0.0f;
t.addVertex(x, y, z);
};
} else if (vertexType == GL11.GL_SHORT && vertexPointer instanceof ShortBuffer sb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 2 : vertexStride;
final int strideInShorts = effectiveStride / 2;
vertexReader = (idx) -> {
final int offset = idx * strideInShorts;
final float x = sb.get(offset);
final float y = sb.get(offset + 1);
final float z = (vertexSize >= 3) ? sb.get(offset + 2) : 0.0f;
t.addVertex(x, y, z);
};
} else {
// Unsupported vertex type
vertexReader = null;
}
// Main loop - no type checks, just function calls
for (int i = first; i < first + count; i++) {
if (normalReader != null) {
normalReader.read(i);
}
if (texCoordReader != null) {
texCoordReader.read(i);
}
if (colorReader != null) {
colorReader.read(i);
}
if (vertexReader != null) {
vertexReader.read(i);
}
readVertexFromBuffer(t, i);
}
return end();
}
@FunctionalInterface
private interface ArrayReader {
void read(int index);
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = 0; i < indicesCount; i++) {
final int index = readIndex(eboData, indexType, eboOffset, i);
readVertexFromBuffer(t, index);
}
return end();
}
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
return switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> ebo.getShort(eboOffset + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> ebo.getInt(eboOffset + i * 4);
default -> i;
};
}
private static void readVertexFromBuffer(DirectTessellator t, int vertexIndex) {
for (int i = 0; i < nonPositionCount; i++) {
final AttribReader r = readers[nonPositionIndices[i]];
final int base = r.baseOffset + vertexIndex * r.stride;
emitAttrib(t, r, base);
}
if (positionReaderIndex >= 0) {
final AttribReader r = readers[positionReaderIndex];
final int base = r.baseOffset + vertexIndex * r.stride;
t.addVertex(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
}
}
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
if (r.loc == LOC_NORMAL) {
t.setNormal(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
r.attrib.readComponent(r.data, base, 2));
} else if (r.loc == LOC_UV0) {
t.setTextureUV(
r.attrib.readComponent(r.data, base, 0),
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
} else if (r.loc == LOC_UV1) {
if (r.attrib.type == GL11.GL_SHORT) {
final int s = r.data.getShort(base) & 0xFFFF;
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
t.setBrightness((u << 16) | s);
} else if (r.attrib.type == GL11.GL_FLOAT) {
final int s = (int) r.data.getFloat(base);
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
t.setBrightness((u << 16) | (s & 0xFFFF));
}
} else if (r.loc == LOC_COLOR) {
final float cr = r.attrib.readComponent(r.data, base, 0);
final float cg = r.attrib.readComponent(r.data, base, 1);
final float cb = r.attrib.readComponent(r.data, base, 2);
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
}
}
}
@@ -1,166 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import java.nio.Buffer;
/**
* Tracks OpenGL client array state (glVertexPointer, glColorPointer, etc.)
* This state is NOT part of the server attribute stack (push/pop attrib),
* but is needed for display list compilation to capture glDrawArrays calls.
*
* When a display list is compiled, glDrawArrays reads from the current
* vertex/color arrays at compile time (not playback time). We need to track
* this state globally so ImmediateModeRecorder can access it.
*/
public class ClientArrayState {
// Vertex array state
private boolean vertexArrayEnabled = false;
private Buffer vertexPointer = null;
private int vertexPointerSize = 4;
private int vertexPointerType = GL11.GL_FLOAT;
private int vertexPointerStride = 0;
// Color array state
private boolean colorArrayEnabled = false;
private Buffer colorPointer = null;
private int colorPointerSize = 4;
private int colorPointerType = GL11.GL_FLOAT;
private int colorPointerStride = 0;
// Vertex array methods
public void setVertexPointer(int size, int type, int stride, Buffer pointer) {
this.vertexPointerSize = size;
this.vertexPointerType = type;
this.vertexPointerStride = stride;
this.vertexPointer = pointer;
}
public boolean isVertexArrayEnabled() {
return vertexArrayEnabled;
}
public void setVertexArrayEnabled(boolean enabled) {
this.vertexArrayEnabled = enabled;
}
public Buffer getVertexPointer() {
return vertexPointer;
}
public int getVertexPointerSize() {
return vertexPointerSize;
}
public int getVertexPointerType() {
return vertexPointerType;
}
public int getVertexPointerStride() {
return vertexPointerStride;
}
// Normal array state
private boolean normalArrayEnabled = false;
private Buffer normalPointer = null;
private int normalPointerType = GL11.GL_FLOAT;
private int normalPointerStride = 0;
// TexCoord array state
private boolean texCoordArrayEnabled = false;
private Buffer texCoordPointer = null;
private int texCoordPointerSize = 4;
private int texCoordPointerType = GL11.GL_FLOAT;
private int texCoordPointerStride = 0;
// Color array methods
public void setColorPointer(int size, int type, int stride, Buffer pointer) {
this.colorPointerSize = size;
this.colorPointerType = type;
this.colorPointerStride = stride;
this.colorPointer = pointer;
}
public boolean isColorArrayEnabled() {
return colorArrayEnabled;
}
public void setColorArrayEnabled(boolean enabled) {
this.colorArrayEnabled = enabled;
}
public Buffer getColorPointer() {
return colorPointer;
}
public int getColorPointerSize() {
return colorPointerSize;
}
public int getColorPointerType() {
return colorPointerType;
}
public int getColorPointerStride() {
return colorPointerStride;
}
// Normal array methods
public void setNormalPointer(int type, int stride, Buffer pointer) {
this.normalPointerType = type;
this.normalPointerStride = stride;
this.normalPointer = pointer;
}
public boolean isNormalArrayEnabled() {
return normalArrayEnabled;
}
public void setNormalArrayEnabled(boolean enabled) {
this.normalArrayEnabled = enabled;
}
public Buffer getNormalPointer() {
return normalPointer;
}
public int getNormalPointerType() {
return normalPointerType;
}
public int getNormalPointerStride() {
return normalPointerStride;
}
// TexCoord array methods
public void setTexCoordPointer(int size, int type, int stride, Buffer pointer) {
this.texCoordPointerSize = size;
this.texCoordPointerType = type;
this.texCoordPointerStride = stride;
this.texCoordPointer = pointer;
}
public boolean isTexCoordArrayEnabled() {
return texCoordArrayEnabled;
}
public void setTexCoordArrayEnabled(boolean enabled) {
this.texCoordArrayEnabled = enabled;
}
public Buffer getTexCoordPointer() {
return texCoordPointer;
}
public int getTexCoordPointerSize() {
return texCoordPointerSize;
}
public int getTexCoordPointerType() {
return texCoordPointerType;
}
public int getTexCoordPointerStride() {
return texCoordPointerStride;
}
}
@@ -0,0 +1,166 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
current[index].enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
public static boolean hasVBOBoundAttrib() {
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].enabled && current[i].vboId != 0) return true;
}
return false;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -81,6 +81,17 @@ public class AngelicaTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
throw new RuntimeException(e);
}
verifyDependencies();
// Register a scoped redirector early so classes prematurely loaded by other coremods during discovery/injectData
// (e.g. Mycelium using HookLoader.class.getName()) still get GL calls redirected to GLSM. Only targets specific mod
// packages to avoid interfering with mixin delegation on game classes. AngelicaLateTweaker removes this registration once
// mixins are setup.
if (FMLLaunchHandler.side().isClient()) {
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (!rfbLoaded) {
Launch.classLoader.registerTransformer("com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer");
}
}
}
private static void verifyDependencies() {
@@ -139,7 +150,7 @@ public class AngelicaTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
// specific IClassTransformers that will run last in the transformer chain.
// If we were to register them normally in getASMTransformerClass(),
// they would be sorted at index 0 which we do not want.
boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (!rfbLoaded) {
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.AngelicaLateTweaker");
if (AngelicaConfig.enableCeleritas) {
@@ -157,7 +168,7 @@ public class AngelicaTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
@Override
public String getMixinConfig() {
int v = Runtime.version().feature();
final int v = Runtime.version().feature();
if (v >= 21) return "mixins.angelica.early.j21.json";
if (v >= 17) return "mixins.angelica.early.j17.json";
return "mixins.angelica.early.json";
@@ -174,7 +185,7 @@ public class AngelicaTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
}
private static void addLwjgl3ifyExclusions() {
var handle = SharedConfig.getRfbTransformers().stream()
final var handle = SharedConfig.getRfbTransformers().stream()
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
.findFirst()
.orElse(null);
@@ -30,13 +30,13 @@ public class CeleritasBlockTransformer implements IClassTransformer {
if (transformedName.startsWith(exclusion)) return basicClass;
}
// Here could be a `inner.shouldTransform(basicClass)` check to exit early from transforming,
// but we also need to call `inner.trackBlockSubclasses` in this case and somehow pass
// the name of a class and a superclass, which we can do only after parsing the whole constant pool.
// This transformer is merely a fallback when FRB / lwjgl3ify is not present, so we can afford
// it being not fully optimized
final ClassReader cr = new ClassReader(basicClass);
inner.trackBlockSubclasses(cr.getClassName(), cr.getSuperName());
if (!inner.shouldTransform(basicClass)) {
return basicClass;
}
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
final boolean changed = inner.transformClassNode(transformedName, cn);
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.loading.fml.transformers;
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
/**
* A scoped redirector that only transforms classes from known-misbehaving (core)mod packages.
* <p>
* Some mods prematurely load classes that call GL functions during coremod discovery/injectData.
* Those classes get missed by the late-registered {@link AngelicaRedirectorTransformer}, so their
* GL calls permanently bypass GLSM.
* <p>
* This transformer is registered in the {@code AngelicaTweaker} constructor (before any
* coremod's {@code injectData}) and is removed by {@code AngelicaLateTweaker} once the
* full redirector is in its proper post-mixin position.
* <p>
* The mixin service will add this to its delegation list, but it no-ops on game classes
* since it only targets specific mod packages.
*/
public class EarlyRedirectorTransformer implements IClassTransformer {
private static final String[] TARGET_PACKAGES = {
"cn.tesseract.mycelium.",
};
private final AngelicaRedirector inner = new AngelicaRedirector();
private final String[] exclusions = inner.getTransformerExclusions();
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
boolean targeted = false;
for (String pkg : TARGET_PACKAGES) {
if (transformedName.startsWith(pkg)) {
targeted = true;
break;
}
}
if (!targeted) return basicClass;
for (String exclusion : exclusions) {
if (transformedName.startsWith(exclusion)) return basicClass;
}
if (!inner.shouldTransform(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
final boolean changed = inner.transformClassNode(transformedName, cn);
if (changed) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.loading.fml.tweakers;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
@@ -12,6 +13,8 @@ import java.util.Set;
public class AngelicaLateTweaker implements ITweaker {
private static final String EARLY_REDIRECTOR_CLASS = "com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer";
@Override
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
// no-op
@@ -30,7 +33,7 @@ public class AngelicaLateTweaker implements ITweaker {
@SuppressWarnings("unchecked")
private static void narrowTransformerExclusions(LaunchClassLoader classLoader) {
try {
final Field exceptionsField = LaunchClassLoader.class.getDeclaredField("transformerExceptions");
final Field exceptionsField = Launch.classLoader.getClass().getDeclaredField("transformerExceptions");
exceptionsField.setAccessible(true);
final Set<String> exceptions = (Set<String>) exceptionsField.get(classLoader);
@@ -51,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());
}
@@ -62,9 +74,20 @@ public class AngelicaLateTweaker implements ITweaker {
}
@Override
@SuppressWarnings("unchecked")
public String[] getLaunchArguments() {
// Run after Mixins, but before LWJGl3ify
String transformer = "com.gtnewhorizons.angelica.loading.fml.transformers.AngelicaRedirectorTransformer";
// Remove the scoped early redirector (no longer needed) and register the full one in its proper position: after mixins
try {
final Field transformersField = Launch.classLoader.getClass().getDeclaredField("transformers");
transformersField.setAccessible(true);
final List<IClassTransformer> transformers = (List<IClassTransformer>) transformersField.get(Launch.classLoader);
transformers.removeIf(t -> t.getClass().getName().equals(EARLY_REDIRECTOR_CLASS));
} catch (Exception e) {
FMLRelaunchLog.warning("[Angelica] Failed to remove EarlyRedirectorTransformer: %s", e.getMessage());
}
final String transformer = "com.gtnewhorizons.angelica.loading.fml.transformers.AngelicaRedirectorTransformer";
FMLRelaunchLog.finer("Registering transformer %s", transformer);
Launch.classLoader.registerTransformer(transformer);
return new String[0];
@@ -54,14 +54,8 @@ public class RFBCeleritasBlockTransformer implements RfbClassTransformer {
return false;
}
if (inner.shouldTransform(classNode.getOriginalBytes())) {
return true;
}
// Track possible block subclasses even if we don't need to transform this class
inner.trackBlockSubclasses(metadata.binaryThisName, metadata.binarySuperName);
return false;
inner.trackBlockSubclasses(metadata.binaryThisName(), metadata.binarySuperName());
return inner.shouldTransform(classNode.getOriginalBytes());
}
@Override
@@ -12,6 +12,7 @@ import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import java.util.ArrayList;
import java.util.HashMap;
@@ -70,6 +71,7 @@ public final class AngelicaRedirector {
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
private static final Map<String, Map<String, String>> interfaceRedirects = new HashMap<>();
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> gluRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
static {
@@ -227,8 +229,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")
@@ -268,6 +272,7 @@ public final class AngelicaRedirector {
);
methodRedirects.put(GL15, RedirectMap.newMap()
.add("glBindBuffer")
.add("glDeleteBuffers")
);
methodRedirects.put(GL20, RedirectMap.newMap()
.add("glBlendEquationSeparate")
@@ -306,11 +311,15 @@ public final class AngelicaRedirector {
.add("glGetAttachedShaders")
.add("glGetShaderSource")
.add("glGetUniform")
.add("glVertexAttribPointer")
.add("glEnableVertexAttribArray")
.add("glDisableVertexAttribArray")
);
methodRedirects.put(GL30, RedirectMap.newMap()
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glBindFramebuffer")
.add("glVertexAttribIPointer")
);
// MINECRAFT
@@ -408,9 +417,15 @@ public final class AngelicaRedirector {
.add("glBindVertexArray")
);
gluRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
gluRedirects.put("org/lwjgl/util/glu/Cylinder", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaCylinder");
gluRedirects.put("org/lwjgl/util/glu/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
gluRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk");
final List<String> stringsToSearch = new ArrayList<>(32);
stringsToSearch.add(VaoFunctions);
stringsToSearch.add(LWJGLService);
stringsToSearch.addAll(gluRedirects.keySet());
final String glPrefix = "org/lwjgl/opengl/GL";
for (var entry : new HashMap<>(methodRedirects).entrySet()) {
stringsToSearch.add(entry.getKey());
@@ -425,6 +440,7 @@ public final class AngelicaRedirector {
public String[] getTransformerExclusions() {
return new String[]{
"org.lwjgl",
"com.gtnewhorizon.gtnhlib.asm",
"com.gtnewhorizons.angelica.glsm.",
"com.gtnewhorizons.angelica.transform",
"me.eigenraven.lwjgl3ify"
@@ -446,7 +462,19 @@ public final class AngelicaRedirector {
}
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof MethodInsnNode mNode) {
if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = gluRedirects.get(tNode.desc);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting {} in {} from {} to {}", tNode.getOpcode() == Opcodes.NEW ? "NEW" : "CHECKCAST", transformedName, tNode.desc, redirect);
}
tNode.desc = redirect;
changed = true;
}
}
} else if (node instanceof MethodInsnNode mNode) {
if (mNode.desc.equals("(I)V") && (mNode.owner.equals(GL11) || mNode.owner.equals(GL11C)) && (mNode.name.equals("glEnable") || mNode.name.equals("glDisable"))) {
final AbstractInsnNode prevNode = node.getPrevious();
String name = null;
@@ -527,6 +555,17 @@ public final class AngelicaRedirector {
}
}
}
// Redirect <init> calls for quadric classes
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = gluRedirects.get(mNode.owner);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting <init> in {} from {} to {}", transformedName, mNode.owner, redirect);
}
mNode.owner = redirect;
changed = true;
}
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
@@ -25,10 +25,21 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class CeleritasBlockTransform {
public final class CeleritasBlockTransform implements Opcodes {
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final Logger LOGGER = LogManager.getLogger("CeleritasBlockTransformer");
private static final String BlockClass = "net/minecraft/block/Block";
private static final String ThreadedBlockData = "com/gtnewhorizons/angelica/glsm/ThreadedBlockData";
private final Map<String, String> fieldNameToRedirect = new HashMap<>();
private final Set<String> blockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Block subclass owners we shouldn't redirect because they shadow some fields we want to redirect
private final Set<String> blockSubclassExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final ClassConstantPoolParser cstPoolParser;
private final Map<String, String> blockFieldRedirects = new HashMap<>();
private final MethodHandle celeritasEnabledGetter;
private boolean isCeleritasEnabled;
public CeleritasBlockTransform(boolean isObf) {
final List<Pair<String, String>> mappings = ImmutableList.of(
@@ -41,65 +52,47 @@ public final class CeleritasBlockTransform {
);
for (Pair<String, String> pair : mappings) {
final String name = isObf ? pair.getRight() : pair.getLeft();
this.blockFieldRedirects.put(name, pair.getLeft());
this.fieldNameToRedirect.put(name, pair.getLeft());
}
this.cstPoolParser = new ClassConstantPoolParser(this.blockFieldRedirects.keySet().toArray(new String[0]));
}
this.cstPoolParser = new ClassConstantPoolParser(this.fieldNameToRedirect.keySet().toArray(new String[0]));
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final Logger LOGGER = LogManager.getLogger("CeleritasBlockTransformer");
private static final String BlockClass = "net/minecraft/block/Block";
private static final String BlockPackage = "net/minecraft/block/Block";
private static final String ThreadedBlockData = "com/gtnewhorizons/angelica/glsm/ThreadedBlockData";
/** All classes in <tt>net.minecraft.block.*</tt> are the block subclasses save for these. */
private static final String[] VanillaBlockExclusions = {
"net/minecraft/block/IGrowable",
"net/minecraft/block/ITileEntityProvider",
"net/minecraft/block/BlockEventData",
"net/minecraft/block/BlockSourceImpl",
"net/minecraft/block/material/"
};
blockSubclasses.add(BlockClass);
private static final Set<String> moddedBlockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Block owners we *shouldn't* redirect because they shadow one of our fields
private static final Set<String> blockOwnerExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Needed because the config is loaded in LaunchClassLoader, but we need to access it in the parent system loader.
private static final MethodHandle angelicaConfigCeleritasEnabledGetter;
private static boolean isCeleritasEnabled;
static {
try {
// Needed because the config is loaded in LaunchClassLoader, but we need to access it in the parent system loader.
final Class<?> angelicaConfig = Class.forName("com.gtnewhorizons.angelica.config.AngelicaConfig", true, Launch.classLoader);
angelicaConfigCeleritasEnabledGetter = MethodHandles.lookup().findStaticGetter(angelicaConfig, "enableCeleritas", boolean.class);
celeritasEnabledGetter = MethodHandles.lookup().findStaticGetter(angelicaConfig, "enableCeleritas", boolean.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static boolean isCeleritasEnabled() {
public void setCeleritasSetting() {
isCeleritasEnabled = true;
}
private boolean isCeleritasEnabled() {
if (isCeleritasEnabled) return true;
try {
return (boolean) angelicaConfigCeleritasEnabledGetter.invokeExact();
return (boolean) celeritasEnabledGetter.invokeExact();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private boolean isVanillaBlockSubclass(String className) {
if (className.startsWith(BlockPackage)) {
for (String exclusion : VanillaBlockExclusions) {
if (className.startsWith(exclusion)) {
return false;
}
}
return true;
}
return false;
}
// This method needs to be called for every class, including the ones we don't want to transform
public void trackBlockSubclasses(String className, String superClassName) {
// It works because the deepest subclasses are always transformed first due to
// ForgeEventTransformer recursive superclass loading
if (blockSubclasses.contains(superClassName)) {
blockSubclasses.add(className);
private boolean isBlockSubclass(String className) {
return isVanillaBlockSubclass(className) || moddedBlockSubclasses.contains(className);
if (blockSubclassExclusions.contains(superClassName)) {
LOGGER.info("Class {} extends a class with shadowed block bounds fields and will be skipped from redirecting", className);
blockSubclassExclusions.add(className);
}
}
}
public String[] getTransformerExclusions() {
@@ -111,12 +104,6 @@ public final class CeleritasBlockTransform {
};
}
public void trackBlockSubclasses(String className, String classSuperName) {
if (!isVanillaBlockSubclass(className) && isBlockSubclass(classSuperName)) {
moddedBlockSubclasses.add(className);
}
}
public boolean shouldTransform(byte[] classBytes) {
return cstPoolParser.find(classBytes);
}
@@ -126,80 +113,66 @@ public final class CeleritasBlockTransform {
if (!isCeleritasEnabled()) {
return false;
}
boolean changed = false;
if ("net.minecraft.block.Block".equals(transformedName)) {
changed = cn.fields.removeIf(field -> blockFieldRedirects.containsKey(field.name));
changed = cn.fields.removeIf(field -> fieldNameToRedirect.containsKey(field.name));
} else {
trackBlockShadowingFields(cn);
}
trackBlockSubclasses(cn.name, cn.superName);
// Check if this class shadows any fields of the parent class
if (moddedBlockSubclasses.contains(cn.name)) {
// If a superclass shadows, then so do we, because JVM will resolve a reference on our class to that
// superclass
boolean doWeShadow = false;
if (blockOwnerExclusions.contains(cn.superName)) {
doWeShadow = true;
} else {
// Check if we declare any known field names
for (FieldNode field : cn.fields) {
if (blockFieldRedirects.containsKey(field.name)) {
doWeShadow = true;
break;
}
}
}
if (doWeShadow) {
LOGGER.info("Class '{}' shadows one or more block bounds fields, these accesses won't be redirected!", cn.name);
blockOwnerExclusions.add(cn.name);
}
}
for (MethodNode mn : cn.methods) {
for (AbstractInsnNode node : mn.instructions.toArray()) {
if ((node.getOpcode() == Opcodes.GETFIELD || node.getOpcode() == Opcodes.PUTFIELD) && node instanceof FieldInsnNode fNode) {
if (!blockOwnerExclusions.contains(fNode.owner) && isBlockSubclass(fNode.owner)) {
String fieldRedirect = blockFieldRedirects.get(fNode.name);
if (fieldRedirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting Block.{} in {} to thread-safe wrapper", fNode.name, transformedName);
}
// Perform the redirect
fNode.name = fieldRedirect; // use unobfuscated name
fNode.owner = ThreadedBlockData;
// Inject getter before the field access, to turn Block -> ThreadedBlockData
final MethodInsnNode getter = new MethodInsnNode(Opcodes.INVOKESTATIC, ThreadedBlockData, "get", "(L" + BlockClass + ";)L" + ThreadedBlockData + ";", false);
if (node.getOpcode() == Opcodes.GETFIELD) {
mn.instructions.insertBefore(fNode, getter);
} else if (node.getOpcode() == Opcodes.PUTFIELD) {
// FIXME: this code assumes doubles
// Stack: Block, double
final InsnList beforePut = new InsnList();
beforePut.add(new InsnNode(Opcodes.DUP2_X1));
// Stack: double, Block, double
beforePut.add(new InsnNode(Opcodes.POP2));
// Stack: double, Block
beforePut.add(getter);
// Stack: double, ThreadedBlockData
beforePut.add(new InsnNode(Opcodes.DUP_X2));
// Stack: ThreadedBlockData, double, ThreadedBlockData
beforePut.add(new InsnNode(Opcodes.POP));
// Stack: ThreadedBlockData, double
mn.instructions.insertBefore(fNode, beforePut);
}
changed = true;
}
}
}
}
}
changed |= redirectBlockBoundFields(cn, transformedName);
return changed;
}
public void setCeleritasSetting() {
isCeleritasEnabled = true;
private void trackBlockShadowingFields(ClassNode cn) {
if (blockSubclasses.contains(cn.name)) {
for (FieldNode field : cn.fields) {
if (fieldNameToRedirect.containsKey(field.name)) {
LOGGER.info("Class '{}' shadows one or more block bounds fields, these accesses won't be redirected!", cn.name);
blockSubclassExclusions.add(cn.name);
break;
}
}
}
}
private boolean redirectBlockBoundFields(ClassNode cn, String transformedName) {
boolean changed = false;
for (MethodNode mn : cn.methods) {
for (AbstractInsnNode node = mn.instructions.getFirst(); node != null; node = node.getNext()) {
if ((node.getOpcode() == GETFIELD || node.getOpcode() == PUTFIELD) && node instanceof FieldInsnNode fNode) {
String newFieldName = fieldNameToRedirect.get(fNode.name);
if (newFieldName != null && blockSubclasses.contains(fNode.owner) && !blockSubclassExclusions.contains(fNode.owner)) {
if (LOG_SPAM) {
LOGGER.info("Redirecting Block.{} in {} to thread-safe wrapper", fNode.name, transformedName);
}
// Perform the redirect
fNode.name = newFieldName; // use unobfuscated name
fNode.owner = ThreadedBlockData;
// Inject getter before the field access, to turn Block -> ThreadedBlockData
final MethodInsnNode getter = new MethodInsnNode(INVOKESTATIC, ThreadedBlockData, "get", "(L" + BlockClass + ";)L" + ThreadedBlockData + ";", false);
if (node.getOpcode() == GETFIELD) {
mn.instructions.insertBefore(fNode, getter);
} else if (node.getOpcode() == PUTFIELD) {
// FIXME: this code assumes doubles
// Stack: Block, double
final InsnList beforePut = new InsnList();
beforePut.add(new InsnNode(DUP2_X1));
// Stack: double, Block, double
beforePut.add(new InsnNode(POP2));
// Stack: double, Block
beforePut.add(getter);
// Stack: double, ThreadedBlockData
beforePut.add(new InsnNode(DUP_X2));
// Stack: ThreadedBlockData, double, ThreadedBlockData
beforePut.add(new InsnNode(POP));
// Stack: ThreadedBlockData, double
mn.instructions.insertBefore(fNode, beforePut);
}
changed = true;
}
}
}
}
return changed;
}
}
@@ -29,16 +29,17 @@ public enum Mixins implements IMixins {
.addClientMixins(
"angelica.MixinActiveRenderInfo"
, "angelica.MixinEntityRenderer"
, "angelica.MixinFMLClientHandler"
, "angelica.MixinForgeHooksClient_CoreProfile"
, "angelica.MixinGameSettings"
, "angelica.MixinMinecraft"
, "angelica.MixinMinecraftServer"
, "angelica.optimizations.MixinRendererLivingEntity"
, "angelica.MixinFMLClientHandler"
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
, "angelica.glsm.MixinSplashProgressCaching"
, "angelica.debug.MixinMinecraft_FPSCap"
, "angelica.ffp.MixinTessellator_CoreProfile"
, "angelica.glsm.MixinSplashProgressCaching"
, "angelica.gui.MixinGuiOptions"
, "angelica.optimizations.MixinRendererLivingEntity"
)
),
@@ -49,6 +50,13 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.vbo.MixinRenderGlobal")
),
ANGELICA_PANORAMA_BLUR(
new MixinBuilder("Replace main menu panorama with modern equivalent")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enablePanoramaBlurShader)
.addClientMixins("angelica.gui.MixinGuiMainMenu")
),
ANGELICA_FONT_RENDERER(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableFontRenderer)
@@ -221,7 +229,6 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderGlobal"
, "shaders.MixinRenderHorse"
, "shaders.MixinRenderItem"
, "shaders.MixinRenderManager"
, "shaders.MixinRenderNameTag"
, "shaders.MixinRenderPlayerArmor"
, "shaders.MixinTileEntityBeaconRenderer"
@@ -230,6 +237,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)
@@ -0,0 +1,346 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.uniform.GlUniformFloat2v;
import net.minecraft.client.Minecraft;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
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.GlUniformMatrix4f;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.util.function.Function;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
* Replaces the vanilla main menu panorama renderer with modern opengl. Significant reduction in draw calls.
*/
public class PanoramaRenderer {
private static final int FBO_SIZE = 256;
private static final int PANORAMA_GRID = 8;
private static final float BLUR_RADIUS = 3.0f;
private static final int VERTEX_STRIDE = 24;
private static final int VERTS_PER_FACE = PANORAMA_GRID * PANORAMA_GRID * 4;
private static final int INDICES_PER_FACE = PANORAMA_GRID * PANORAMA_GRID * 6;
private static PanoramaRenderer instance;
private int fboA, fboB, texA, texB, cubeVao, cubeVbo, cubeIbo, emptyVao;
private GlProgram<CubeUniforms> cubeProgram;
private GlProgram<BlurUniforms> blurProgram;
private GlProgram<BlitUniforms> blitProgram;
private boolean initialized;
private final Matrix4f mvpScratch = new Matrix4f();
private final Matrix4f projScratch = new Matrix4f();
private final Matrix4f baseMVScratch = new Matrix4f();
// Pre-computed face rotation matrices
private static final Matrix4f[] FACE_ROTATIONS = new Matrix4f[6];
static {
FACE_ROTATIONS[0] = new Matrix4f();
FACE_ROTATIONS[1] = new Matrix4f().rotateY((float) Math.toRadians(90.0));
FACE_ROTATIONS[2] = new Matrix4f().rotateY((float) Math.toRadians(180.0));
FACE_ROTATIONS[3] = new Matrix4f().rotateY((float) Math.toRadians(-90.0));
FACE_ROTATIONS[4] = new Matrix4f().rotateX((float) Math.toRadians(90.0));
FACE_ROTATIONS[5] = new Matrix4f().rotateX((float) Math.toRadians(-90.0));
}
public static PanoramaRenderer getInstance() {
if (instance == null) {
instance = new PanoramaRenderer();
}
return instance;
}
private void init() {
if (initialized) return;
initialized = true;
texA = createTexture();
texB = createTexture();
fboA = createFbo(texA);
fboB = createFbo(texB);
buildCubeGeometry();
emptyVao = GL30.glGenVertexArrays();
initShaders();
}
private void initShaders() {
cubeProgram = loadProgram("angelica:panorama_cube", "angelica:panorama_cube", "angelica:panorama_cube", CubeUniforms::new);
blitProgram = loadProgram("angelica:panorama_blit", "angelica:panorama_blur", "angelica:panorama_blit", BlitUniforms::new);
blurProgram = loadProgram("angelica:panorama_blur", "angelica:panorama_blur", "angelica:panorama_blur", BlurUniforms::new);
blurProgram.bind();
blurProgram.getInterface().radius.setFloat(BLUR_RADIUS);
blurProgram.unbind();
}
private static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, Function<ShaderBindingContext, T> factory) {
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertBase + ".vert", ShaderConstants.EMPTY);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", ShaderConstants.EMPTY);
try {
final GlProgram<T> program = GlProgram.builder(name).attachShader(vert).attachShader(frag).link(factory);
program.bind();
GL20.glUniform1i(GL20.glGetUniformLocation(program.handle(), "u_Texture"), 0);
program.unbind();
return program;
} finally {
vert.delete();
frag.delete();
}
}
private void buildCubeGeometry() {
final int grid = PANORAMA_GRID;
final int numPasses = grid * grid;
final FloatBuffer vbuf = ByteBuffer.allocateDirect(VERTS_PER_FACE * VERTEX_STRIDE).order(ByteOrder.nativeOrder()).asFloatBuffer();
for (int k = 0; k < numPasses; k++) {
final float tx = ((float) (k % grid) / grid - 0.5f) / 64.0f;
final float ty = ((float) (k / grid) / grid - 0.5f) / 64.0f;
final float alpha = 1.0f / (k + 1);
vbuf.put(-1.0f + tx).put(-1.0f + ty).put(1.0f).put(0.0f).put(0.0f).put(alpha);
vbuf.put(1.0f + tx).put(-1.0f + ty).put(1.0f).put(1.0f).put(0.0f).put(alpha);
vbuf.put(1.0f + tx).put(1.0f + ty).put(1.0f).put(1.0f).put(1.0f).put(alpha);
vbuf.put(-1.0f + tx).put(1.0f + ty).put(1.0f).put(0.0f).put(1.0f).put(alpha);
}
vbuf.flip();
final ShortBuffer ibuf = ByteBuffer.allocateDirect(INDICES_PER_FACE * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
for (int k = 0; k < numPasses; k++) {
final int base = k * 4;
ibuf.put((short) base).put((short) (base + 1)).put((short) (base + 2));
ibuf.put((short) base).put((short) (base + 2)).put((short) (base + 3));
}
ibuf.flip();
cubeVao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(cubeVao);
cubeVbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, cubeVbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vbuf, GL15.GL_STATIC_DRAW);
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, VERTEX_STRIDE, 0);
GL20.glEnableVertexAttribArray(1);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_STRIDE, 12);
GL20.glEnableVertexAttribArray(2);
GL20.glVertexAttribPointer(2, 1, GL11.GL_FLOAT, false, VERTEX_STRIDE, 20);
cubeIbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, cubeIbo);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ibuf, GL15.GL_STATIC_DRAW);
GL30.glBindVertexArray(0);
}
private static int createTexture() {
final int tex = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, FBO_SIZE, FBO_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return tex;
}
private static int createFbo(int colorTex) {
final int fbo = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTex, 0);
final int status = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Panorama FBO incomplete: 0x" + Integer.toHexString(status));
}
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
return fbo;
}
public void destroy() {
if (!initialized) return;
initialized = false;
if (fboA != 0) {
GL30.glDeleteFramebuffers(fboA);
fboA = 0;
}
if (fboB != 0) {
GL30.glDeleteFramebuffers(fboB);
fboB = 0;
}
if (texA != 0) {
GL11.glDeleteTextures(texA);
texA = 0;
}
if (texB != 0) {
GL11.glDeleteTextures(texB);
texB = 0;
}
if (cubeVao != 0) {
GL30.glDeleteVertexArrays(cubeVao);
cubeVao = 0;
}
if (cubeVbo != 0) {
GL15.glDeleteBuffers(cubeVbo);
cubeVbo = 0;
}
if (cubeIbo != 0) {
GL15.glDeleteBuffers(cubeIbo);
cubeIbo = 0;
}
if (emptyVao != 0) {
GL30.glDeleteVertexArrays(emptyVao);
emptyVao = 0;
}
if (cubeProgram != null) {
cubeProgram.delete();
cubeProgram = null;
}
if (blurProgram != null) {
blurProgram.delete();
blurProgram = null;
}
if (blitProgram != null) {
blitProgram.delete();
blitProgram = null;
}
}
public void renderSkybox(int panoramaTimer, float partialTicks, ResourceLocation[] panoramaPaths, Minecraft mc, int screenWidth, int screenHeight, float zLevel) {
init();
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.disableCull();
GLStateManager.disableScissorTest();
GLStateManager.disableDepthTest();
mc.getFramebuffer().unbindFramebuffer();
GL11.glViewport(0, 0, FBO_SIZE, FBO_SIZE);
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboA);
GL11.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
drawCubeFaces(panoramaTimer, partialTicks, panoramaPaths, mc);
GLStateManager.disableBlend();
blurProgram.bind();
GL30.glBindVertexArray(emptyVao);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final BlurUniforms bu = blurProgram.getInterface();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboB);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texA);
bu.direction.set(1.0f / FBO_SIZE, 0.0f);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboA);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texB);
bu.direction.set(0.0f, 1.0f / FBO_SIZE);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
mc.getFramebuffer().bindFramebuffer(true);
GL11.glViewport(0, 0, mc.displayWidth, mc.displayHeight);
blitProgram.bind();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texA);
final float f1 = screenWidth > screenHeight ? 120.0F / screenWidth : 120.0F / screenHeight;
blitProgram.getInterface().scaleV.setFloat(screenHeight * f1 / 256.0F);
blitProgram.getInterface().scaleU.setFloat(screenWidth * f1 / 256.0F);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GL30.glBindVertexArray(0);
blitProgram.unbind();
GLStateManager.enableDepthTest();
GLStateManager.enableCull();
GLStateManager.enableBlend();
}
private void drawCubeFaces(int panoramaTimer, float partialTicks, ResourceLocation[] panoramaPaths, Minecraft mc) {
final float pitch = MathHelper.sin(((float) panoramaTimer + partialTicks) / 400.0F) * 25.0F + 20.0F;
final float yaw = -((float) panoramaTimer + partialTicks) * 0.1F;
final Matrix4f base = baseMVScratch.identity().rotateX((float) Math.toRadians(180.0)).rotateZ((float) Math.toRadians(90.0))
.rotateX((float) Math.toRadians(pitch)).rotateY((float) Math.toRadians(yaw));
final Matrix4f proj = projScratch.setPerspective((float) Math.toRadians(120.0), 1.0f, 0.05f, 10.0f);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(false);
cubeProgram.bind();
GL30.glBindVertexArray(cubeVao);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final CubeUniforms cu = cubeProgram.getInterface();
for (int face = 0; face < 6; face++) {
proj.mul(base, mvpScratch).mul(FACE_ROTATIONS[face]);
cu.mvp.set(mvpScratch);
mc.getTextureManager().bindTexture(panoramaPaths[face]);
GL11.glDrawElements(GL11.GL_TRIANGLES, INDICES_PER_FACE, GL11.GL_UNSIGNED_SHORT, 0);
}
GL30.glBindVertexArray(0);
cubeProgram.unbind();
GLStateManager.glDepthMask(true);
}
private static class CubeUniforms {
final GlUniformMatrix4f mvp;
CubeUniforms(ShaderBindingContext ctx) {
mvp = ctx.bindUniform("u_MVP", GlUniformMatrix4f::new);
}
}
private static class BlurUniforms {
final GlUniformFloat2v direction;
final GlUniformFloat radius;
BlurUniforms(ShaderBindingContext ctx) {
direction = ctx.bindUniform("u_Direction", GlUniformFloat2v::new);
radius = ctx.bindUniform("u_Radius", GlUniformFloat::new);
}
}
private static class BlitUniforms {
final GlUniformFloat scaleV;
final GlUniformFloat scaleU;
BlitUniforms(ShaderBindingContext ctx) {
scaleV = ctx.bindUniform("u_ScaleV", GlUniformFloat::new);
scaleU = ctx.bindUniform("u_ScaleU", GlUniformFloat::new);
}
}
}
@@ -117,15 +117,6 @@ public class SodiumGameOptionPages {
}
}, (opts) -> opts.fullScreen)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName(I18n.format("options.angelica.sleepbeforeswap"))
.setTooltip(I18n.format("options.angelica.sleepbeforeswap.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> {
AngelicaConfig.sleepBeforeSwap = value;
}, opts -> AngelicaConfig.sleepBeforeSwap)
.setImpact(OptionImpact.VARIES)
.build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.vsync"))
.setTooltip(I18n.format("sodium.options.v_sync.tooltip"))
@@ -141,9 +141,6 @@ options.angelica.mobSpawnerRenderDistance.tooltip=Renderabstand für das rotiere
options.angelica.itemdisplaylistcount=Item Renderer Display List Cache Size
options.angelica.itemdisplaylistcount=Item-Renderer-Anzeigecache-Größe
options.angelica.itemdisplaylistcount.tooltip=Größte Anzahl an Anzeigelisten, die im Itemrenderer gecacht werden. Höhere Werte erhöhen Performance zukosten von VRAM.
options.angelica.sleepbeforeswap=Alternate FPS Limiter
options.angelica.sleepbeforeswap=Alternativer FPS-Begrenzer
options.angelica.sleepbeforeswap.tooltip=Aktiviert einen alternativen FPS-Begrenzer für stabilere Framezeiten, was zu geringfügig höherer Latenz führen kann. Wird nie mehr als einen Frame Latenz erzeugen, und hat bei hoher Framerate weniger Wirkung.
pack.iris.select.title=Auswählen
pack.iris.configure.title=Konfigurieren
label.iris.true=An
@@ -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
@@ -206,8 +208,6 @@ options.angelica.mobSpawnerRenderDistance=Mob spawner render distance
options.angelica.mobSpawnerRenderDistance.tooltip=Render distance for the spinning mob inside mod spawners
options.angelica.itemdisplaylistcount=Item Renderer Cache Limit
options.angelica.itemdisplaylistcount.tooltip=The upper limit of VBO's to cache in the optimized item renderer. Higher values will significantly increase performance, but may result in increased VRAM usage.
options.angelica.sleepbeforeswap=Alternate FPS Limiter
options.angelica.sleepbeforeswap.tooltip=Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.
options.angelica.threadedChunkBuilding=Threaded Chunk Building
options.angelica.threadedChunkBuilding.tooltip=If enabled, chunk meshing will be performed on multiple threads in parallel, significantly improving chunk build speed. Disable if you experience rendering issues with mods that have custom block renderers. Requires renderer reload to take effect.
@@ -164,8 +164,6 @@ options.angelica.mobSpawnerRenderDistance=刷怪笼渲染距离
options.angelica.mobSpawnerRenderDistance.tooltip=刷怪笼内旋转实体的渲染距离
options.angelica.itemdisplaylistcount=物品渲染器缓存限制
options.angelica.itemdisplaylistcount.tooltip=优化物品渲染器中的缓存数量上限。较高的值将显著提高性能,但可能导致显存占用增加
options.angelica.sleepbeforeswap=备用FPS限制器
options.angelica.sleepbeforeswap.tooltip=切换到备用FPS限制器,可以降低帧波动,但会略微增加延迟。最多只会造成一帧的延迟,且在较高帧率下影响较小。
pack.iris.select.title=选择
pack.iris.configure.title=配置
label.iris.true=开启
@@ -0,0 +1,15 @@
#version 330 core
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
uniform float u_ScaleV;
uniform float u_ScaleU;
out vec4 fragColor;
void main() {
float u = 0.5 + u_ScaleV * (2.0 * v_TexCoord.y - 1.0);
float v = 0.5 + u_ScaleU * (1.0 - 2.0 * v_TexCoord.x);
fragColor = texture(u_Texture, vec2(u, v));
}
@@ -0,0 +1,24 @@
#version 330 core
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
uniform vec2 u_Direction;
uniform float u_Radius;
out vec4 fragColor;
void main() {
float sigma = max(u_Radius * 0.5, 1.0);
float invTwoSigmaSq = 1.0 / (2.0 * sigma * sigma);
vec3 sum = vec3(0.0);
float weightSum = 0.0;
for (float i = -u_Radius; i <= u_Radius; i += 1.0) {
float w = exp(-i * i * invTwoSigmaSq);
sum += texture(u_Texture, v_TexCoord + u_Direction * i).rgb * w;
weightSum += w;
}
fragColor = vec4(sum / weightSum, 1.0);
}
@@ -0,0 +1,8 @@
#version 330 core
out vec2 v_TexCoord;
void main() {
v_TexCoord = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
gl_Position = vec4(v_TexCoord * 2.0 - 1.0, 0.0, 1.0);
}
@@ -0,0 +1,13 @@
#version 330 core
in vec2 v_TexCoord;
in float v_Alpha;
uniform sampler2D u_Texture;
out vec4 fragColor;
void main() {
vec4 color = texture(u_Texture, v_TexCoord);
fragColor = vec4(color.rgb, v_Alpha);
}
@@ -0,0 +1,16 @@
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
layout(location = 2) in float a_Alpha;
uniform mat4 u_MVP;
out vec2 v_TexCoord;
out float v_Alpha;
void main() {
gl_Position = u_MVP * vec4(a_Position, 1.0);
v_TexCoord = a_TexCoord;
v_Alpha = a_Alpha;
}
@@ -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
}
}
@@ -1,11 +1,9 @@
package com.gtnewhorizons.angelica.mixins.early.angelica;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.ffp.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.GameSettings;
import org.embeddedt.embeddium.impl.render.frame.RenderAheadManager;
@@ -34,7 +32,10 @@ public abstract class MixinMinecraft {
private static int max_texture_size;
@Unique
private long angelica$lastFrameTime = 0;
private static long angelica$lastFrameTime = 0;
@Unique
private static long angelica$fpsLimitOverhead = 0;
@Unique
private final RenderAheadManager celeritas$renderAheadManager = new RenderAheadManager();
@@ -62,34 +63,41 @@ public abstract class MixinMinecraft {
@Inject(
method = "func_147120_f",
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;update()V", shift = At.Shift.BEFORE, remap = false)
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;update()V", remap = false)
)
private void angelica$limitFPS(CallbackInfo ci) {
if (!AngelicaConfig.sleepBeforeSwap) return;
if (isFramerateLimitBelowMax()) {
final long target = angelica$lastFrameTime + (long) (1.0 / getLimitFramerate() * 1_000_000) * 1_000;
while (target - System.nanoTime() > 100) {
Thread.yield();
}
}
}
@Inject(
method = "func_147120_f",
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;update()V", shift = At.Shift.AFTER, remap = false)
)
private void angelica$trackFrametimes(CallbackInfo ci) {
if (AngelicaMod.proxy == null) return;
if (isFramerateLimitBelowMax() && !gameSettings.enableVsync) {
final long time = System.nanoTime();
final long lastWorkTime = time - angelica$lastFrameTime;
final long targetNanos = (long) (1.0 / getLimitFramerate() * 1_000_000_000L);
// Account for overhead, so the average FPS remains stable.
final long sleepNanos = targetNanos - lastWorkTime - angelica$fpsLimitOverhead;
if (sleepNanos > 0) {
try {
Thread.sleep(sleepNanos / 1_000_000, (int) sleepNanos % 1_000_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// Record overhead, capping it to prevent outsized spikes from affecting framerates for too long.
// In testing, spikes were followed by 1-frame dips, which I think is acceptable.
long overhead = System.nanoTime() - time - sleepNanos;
if (overhead < 0 || overhead > targetNanos / 2) overhead = 0;
angelica$fpsLimitOverhead = overhead;
}
final long time = System.nanoTime();
AngelicaMod.proxy.putFrametime(time - angelica$lastFrameTime);
angelica$lastFrameTime = time;
}
@WrapOperation(method = "runGameLoop", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;sync(I)V", remap = false))
private void angelica$noopFPSLimiter(int fps, Operation<Void> original) {
if (AngelicaConfig.sleepBeforeSwap) return;
original.call(fps);
@WrapWithCondition(method = "runGameLoop", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;sync(I)V", remap = false))
private boolean angelica$noopFPSLimiter(int fps) {
return false;
}
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiScreen;isShiftKeyDown()Z", shift = At.Shift.AFTER))
@@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
public class MixinTextureManager {
@Inject(method = "loadTexture", at = @At("RETURN"))
private void onTextureLoad(ResourceLocation resource, ITextureObject texture, CallbackInfoReturnable<Boolean> cir) {
if(texture != null && texture != TextureUtil.missingTexture) {
if(texture != null && texture != TextureUtil.missingTexture && !GLStateManager.isRecordingDisplayList()) {
final int curId = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture.getGlTextureId());
GLDebug.nameObject(GL11.GL_TEXTURE, texture.getGlTextureId(), resource.toString());
@@ -50,9 +50,9 @@ public abstract class MixinEntity implements IDynamicLightSource {
@Unique
private double angelica$prevZ;
@Unique
private LongOpenHashSet angelica$trackedLitChunkPos = new LongOpenHashSet();
private LongOpenHashSet angelica$trackedLitChunkPos = null;
@Unique
private LongOpenHashSet angelica$prevTrackedLitChunkPos = new LongOpenHashSet();
private LongOpenHashSet angelica$prevTrackedLitChunkPos = null;
@Override
@@ -117,6 +117,10 @@ public abstract class MixinEntity implements IDynamicLightSource {
this.angelica$prevZ = this.posZ;
this.angelica$lastLuminance = luminance;
if (this.angelica$prevTrackedLitChunkPos == null) {
this.angelica$prevTrackedLitChunkPos = new LongOpenHashSet();
this.angelica$trackedLitChunkPos = new LongOpenHashSet();
}
final LongOpenHashSet newPos = this.angelica$prevTrackedLitChunkPos;
newPos.clear();
@@ -161,7 +165,7 @@ public abstract class MixinEntity implements IDynamicLightSource {
@Override
public void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer) {
if (this.angelica$trackedLitChunkPos.isEmpty()) {
if (this.angelica$trackedLitChunkPos == null || this.angelica$trackedLitChunkPos.isEmpty()) {
angelica$scheduleRebuildAroundCurrentPosition(renderer);
} else {
var iter = this.angelica$trackedLitChunkPos.iterator();
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.gui;
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;
@Mixin(GuiMainMenu.class)
public abstract class MixinGuiMainMenu extends GuiScreen {
@Shadow private int panoramaTimer;
@Final @Shadow private static ResourceLocation[] titlePanoramaPaths;
/**
* @author Angelica
* @reason Replace panorama with modern equivalent
*/
@Overwrite
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,22 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiOptions;
import net.minecraft.client.gui.GuiScreen;
import org.spongepowered.asm.mixin.Mixin;
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.callback.CallbackInfo;
@Mixin(GuiOptions.class)
public abstract class MixinGuiOptions extends GuiScreen {
@Unique
private static final int SUPER_SECRET_SETTINGS_BUTTON_ID = 8675309;
@Inject(method = "initGui", at = @At("RETURN"))
private void angelica$disableSuperSecretSettings(CallbackInfo ci) {
this.buttonList.removeIf(b -> ((GuiButton) b).id == SUPER_SECRET_SETTINGS_BUTTON_ID);
}
}
@@ -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);
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
@@ -45,6 +46,7 @@ public class AngelicaExtension implements BeforeAllCallback, BeforeEachCallback,
setMainThread(Thread.currentThread());
GLStateManager.preInit();
VertexAttribState.init(0);
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete();
GLStateManager.BYPASS_CACHE = false;
@@ -63,13 +65,13 @@ public class AngelicaExtension implements BeforeAllCallback, BeforeEachCallback,
private static void setMainThread(Thread thread) {
try {
// Use Unsafe to set final static field - works on all Java versions
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
Field mainThreadField = GLStateManager.class.getDeclaredField("MainThread");
Object base = unsafe.staticFieldBase(mainThreadField);
long offset = unsafe.staticFieldOffset(mainThreadField);
final Field mainThreadField = GLStateManager.class.getDeclaredField("MainThread");
final Object base = unsafe.staticFieldBase(mainThreadField);
final long offset = unsafe.staticFieldOffset(mainThreadField);
unsafe.putObject(base, offset, thread);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Failed to set MainThread for tests", e);
@@ -104,19 +106,19 @@ public class AngelicaExtension implements BeforeAllCallback, BeforeEachCallback,
@Override
public void afterEach(ExtensionContext context) throws Exception {
int error = GL11.glGetError();
final int error = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, error,
() -> "GL Error: 0x" + Integer.toHexString(error));
int modelviewDepth = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
int projectionDepth = GL11.glGetInteger(GL11.GL_PROJECTION_STACK_DEPTH);
final int modelviewDepth = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
final int projectionDepth = GL11.glGetInteger(GL11.GL_PROJECTION_STACK_DEPTH);
assertEquals(EXPECTED_MODELVIEW_STACK_DEPTH, modelviewDepth,
"MODELVIEW stack depth mismatch - unbalanced glPushMatrix/glPopMatrix");
assertEquals(EXPECTED_PROJECTION_STACK_DEPTH, projectionDepth,
"PROJECTION stack depth mismatch - unbalanced glPushMatrix/glPopMatrix");
int matrixMode = GL11.glGetInteger(GL11.GL_MATRIX_MODE);
final int matrixMode = GL11.glGetInteger(GL11.GL_MATRIX_MODE);
assertEquals(GL11.GL_MODELVIEW, matrixMode,
() -> "Matrix mode not reset to MODELVIEW, was: " + GLDebug.getMatrixModeName(matrixMode));
}
@@ -8,7 +8,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
@@ -60,17 +59,17 @@ class FeedbackManagerTest {
// Upload 3 vertices to a VBO and draw
final int vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);
GLStateManager.glBindVertexArray(vao);
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GL20.glEnableVertexAttribArray(0);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
@@ -78,10 +77,10 @@ class FeedbackManagerTest {
assertEquals(-1, result, "Overflow should return -1");
// Cleanup
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GL30.glDeleteVertexArrays(vao);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
@@ -112,7 +111,7 @@ class FeedbackManagerTest {
// Upload 3 vertices forming a triangle
final int vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);
GLStateManager.glBindVertexArray(vao);
// NDC vertices: (0,0,0), (1,0,0), (0,1,0)
// With identity MVP and viewport 800x600:
@@ -124,10 +123,10 @@ class FeedbackManagerTest {
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GL20.glEnableVertexAttribArray(0);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
@@ -152,10 +151,10 @@ class FeedbackManagerTest {
assertEquals(600.0f, buf.get(7), 0.01f);
// Cleanup
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GL30.glDeleteVertexArrays(vao);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
@@ -170,7 +169,7 @@ class FeedbackManagerTest {
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);
GLStateManager.glBindVertexArray(vao);
// Single point at NDC origin (0,0,0)
final float[] verts = { 0f, 0f, 0f };
@@ -178,10 +177,10 @@ class FeedbackManagerTest {
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GL20.glEnableVertexAttribArray(0);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POINTS, 0, 1);
@@ -196,9 +195,9 @@ class FeedbackManagerTest {
assertEquals(0.5f, buf.get(3), 0.01f);
// Cleanup
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GL30.glDeleteVertexArrays(vao);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@@ -100,8 +100,8 @@ public class GLSM_ServerSideState_UnitTest {
backgroundError = t;
}
pendingCommand = null;
completeLatch.countDown();
}
completeLatch.countDown();
}
}
} catch (InterruptedException e) {