Compare commits

...
28 Commits
Author SHA1 Message Date
Jason Mitchell 9c45549e35 Resolve generic compressed image formats (#1781) 2026-05-25 11:50:18 -07:00
Jason Mitchell 2a20198292 Fixes crash with FFP shader linking (#1780) 2026-05-24 17:31:46 -07:00
Eclipse-SolandJason Mitchell f0b68f93af Port modern's damage overlay (#1777)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-24 10:48:42 -07:00
Eclipse-SolandJason Mitchell dd1d3aa518 Add NBT matching to list of valid block/item entries (#1771)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-23 23:08:48 -07:00
Jason Mitchell c9633fd745 Fix osx crash (#1778) 2026-05-23 22:03:59 -07:00
DarkShadow44 e2e94b2958 Fix DH framebuffer UNSUPPORTED after resize (#1741) 2026-05-23 21:19:45 -07:00
tiffit 3046b043a0 clear block and item renderlist on resource reload (#1773) 2026-05-23 17:45:31 -07:00
Apis035andCaedis 8a6c8035db Add view bobbing mode (#1761)
* Add view bobbing mode

* Fix missing import

---------

Co-authored-by: Caedis <[email protected]>
2026-05-21 23:11:35 +02:00
Alexdoru 2b15370c00 Properly do the fml/rfb registration for IsbrhTessellatorAbuseTransformer (#1770) 2026-05-20 09:56:16 -07:00
Jason Mitchell 3ca9e32191 Stop burying the error due to things catching RuntimeExceptions.... (#1767) 2026-05-19 08:53:05 -07:00
sisyphussy efbca29381 Add cached block item renderer (#1730) 2026-05-19 08:03:51 -07:00
Jason Mitchell 9c1b6c75ff Fix crash on windows (#1766) 2026-05-19 08:02:37 -07:00
sisyphussy fa91b70528 Display list stuff (#1750) 2026-05-18 12:46:48 -07:00
Jason Mitchell 61de8385ca Minecraft is a dirty dirty liar. (#1764) 2026-05-18 12:39:30 -07:00
Jason Mitchell 962e7527a4 Register later (#1763) 2026-05-18 12:39:20 -07:00
Jason Mitchell 2ab2713fe9 Stops draw/startDrawing in ISBRH (#1759) 2026-05-17 21:08:18 -07:00
Jason Mitchell 7cf652cd4e Misc fixes - Journeymap and Xaeros (#1756) 2026-05-17 10:23:10 -07:00
Jason Mitchell 48eba2fd96 Properly end the dynamic lighting profiler section (#1751) 2026-05-16 12:20:09 -07:00
Apis035 356ba376fe Adjust options screen width (#1748) 2026-05-15 08:47:28 -07:00
Eclipse-Sol ef6b0742a9 Fix skeleton legs from being jammed into their pelvis (#1747) 2026-05-14 15:26:25 -07:00
Kaiz 787929d0bd Handle recoverable block render failures in celeritas meshing (#1705) 2026-05-10 10:00:43 -07:00
0e1fd9d0e9 add auto emissive overlay for any block (#1710)
Co-authored-by: VortexSo4 <[email protected]>
Co-authored-by: ah-OOG-ah <[email protected]>
2026-05-10 02:12:08 -04:00
Ruling-0 58938e82a7 Compact CTM Fixes (#1726) 2026-05-09 11:42:42 -07:00
Branan Riley be6c8c85b5 Detect vanilla AO at a higher level (#1733) 2026-05-09 11:41:59 -07:00
ABKQPOandCaedis 39bca37d0d Adjust IDynamicLightProducer to allow passing an ItemStack parameter (#1734)
Co-authored-by: Caedis <[email protected]>
2026-05-08 20:31:22 -07:00
czqwq 1c61be04a7 Fix Already tesselating logs (#1729) 2026-05-08 20:30:08 -07:00
7a97cd9386 Add RGB color system (#1669)
Co-authored-by: Death Fuel <[email protected]>
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-08 18:36:51 -07:00
Kotmatross 5b09bde7a0 [NTM:Space] Fix mixins in obfuscated environment (#1738) 2026-05-08 18:24:29 +02:00
110 changed files with 4483 additions and 2654 deletions

No files matched your search

+19 -1
View File
@@ -76,7 +76,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
api("com.github.GTNewHorizons:GTNHLib:0.10.0:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
@@ -97,6 +97,24 @@ dependencies {
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
// Display List Testing
/*
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-749-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.14-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.54:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.0:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.4-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.2.13:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.6:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.9.7:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.15-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.10-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.8-GTNH:dev")
*/
//devOnlyNonPublishable('com.github.GTNewHorizons:worldedit-gtnh:v0.0.9')
// Notfine Deps
+1
View File
@@ -66,6 +66,7 @@ repositories {
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
mavenCentral()
mavenLocal()
}
dependencies {
+1 -1
View File
@@ -4,7 +4,7 @@ celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.9.36
gtnhlibVersion = 0.10.0
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
@@ -1,67 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import java.util.List;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
*/
public final class CommandBufferBuilder {
private CommandBufferBuilder() {}
/**
* Build commands with transform collapsing optimization.
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
* Uses mergedRanges where consecutive same-transform draws are combined.
*/
public static void buildFromRawBuffer(
CommandBuffer rawBuffer,
List<AccumulatedDraw> accumulatedDraws,
CommandBuffer finalBuffer) {
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
AccumulatedDraw draw;
while (rawBuffer.hasRemaining()) {
// Read and process the command
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
// Emit draw ranges at this command position
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
rangeIndex++;
}
cmdIndex++;
}
while (rangeIndex < accumulatedDraws.size()) {
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
rangeIndex++;
}
}
/**
* Emit a draw range to the output buffer, with transform if needed.
*/
private static void emitDrawRangeToBuffer(
AccumulatedDraw draw,
CommandBuffer out,
int vboIdx) {
if (draw.restoreData != null) {
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
return;
}
// Write the draw range command
out.writeDrawRange(vboIdx);
}
}
File diff suppressed because it is too large. Load diff
@@ -148,10 +148,21 @@ public class GLStateManager {
public static int projGeneration; // projection matrix changes
public static int texMatrixGeneration; // texture matrix changes
public static int lightingGeneration;
public static int fragmentGeneration; // fog + alpha ref
public static int fragmentGeneration; // fog + alpha ref + overlay color
public static int colorGeneration; // current vertex color
public static int clipPlaneGeneration; // clip plane equation changes
@Getter private static float overlayR = 0.0f;
@Getter private static float overlayG = 0.0f;
@Getter private static float overlayB = 0.0f;
@Getter private static float overlayA = 0.0f;
public static void setOverlayColor(float r, float g, float b, float a) {
if (r == overlayR && g == overlayG && b == overlayB && a == overlayA) return;
overlayR = r; overlayG = g; overlayB = b; overlayA = a;
fragmentGeneration++;
}
// Deferred vertex attribute upload flags — set when state changes, flushed before draw
private static boolean dirtyColorAttrib;
private static boolean dirtyNormalAttrib;
@@ -1991,7 +2002,9 @@ public class GLStateManager {
}
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, id);
RENDER_BACKEND.texParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R8, 1, 1, 0, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
restorePixelUnpackBuffer();
// Restore previous binding on active unit
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, savedBinding == id ? 0 : savedBinding);
@@ -2097,22 +2110,20 @@ public class GLStateManager {
}
public static void glEnd() {
if (DisplayListManager.isRecording()) {
final DirectTessellator result = ImmediateModeRecorder.end();
if (result != null) {
final DirectTessellator result = ImmediateModeRecorder.end();
if (result != null) {
if (DisplayListManager.isRecording()) {
if (DisplayListManager.isCompileAndExecute() && initConfig != null && initConfig.getDirectDrawer() != null) {
DisplayListManager.flushMatrix();
final var recorder = DisplayListManager.pauseRecording();
final CommandRecorder recorder = DisplayListManager.pauseRecording();
initConfig.getDirectDrawer().accept(result);
DisplayListManager.resumeRecording(recorder);
}
DisplayListManager.addImmediateModeDraw(result);
return;
}
if (initConfig != null && initConfig.getDirectDrawer() != null) {
initConfig.getDirectDrawer().accept(result);
}
return;
}
final DirectTessellator result = ImmediateModeRecorder.end();
if (result != null && initConfig != null && initConfig.getDirectDrawer() != null) {
initConfig.getDirectDrawer().accept(result);
}
}
@@ -2227,7 +2238,6 @@ public class GLStateManager {
if (recordMode != RecordMode.NONE) {
// Core profile: a default VAO is generated at init and glBindVertexArray(0)
// is redirected to it, so a VAO is always bound here — no fallback branches.
DisplayListManager.flushMatrix();
final IndexedDrawCapture capture = IndexedDrawCapture.create(mode, indices_count, type, indices_buffer_offset, boundEBO);
if (capture != null) {
DisplayListManager.recordIndexedDrawCapture(capture);
@@ -3348,7 +3358,6 @@ public class GLStateManager {
final RecordMode recordMode = DisplayListManager.getRecordMode();
if (recordMode != RecordMode.NONE) {
DisplayListManager.recordMatrixMode(mode);
DisplayListManager.resetRelativeTransform(); // Mode switch is a barrier - reset delta tracking
if (recordMode == RecordMode.COMPILE) {
return;
}
@@ -3362,8 +3371,6 @@ public class GLStateManager {
final Matrix4f matrix = new Matrix4f().set(m);
m.rewind();
DisplayListManager.recordLoadMatrix(matrix);
// Reset relative transform - subsequent transforms are relative to loaded matrix
DisplayListManager.resetRelativeTransform();
if (mode == RecordMode.COMPILE) {
return;
}
@@ -3383,8 +3390,6 @@ public class GLStateManager {
final Matrix4f floatMatrix = new Matrix4f();
floatMatrix.set(conversionMatrix4d);
DisplayListManager.recordLoadMatrix(floatMatrix);
// Reset relative transform - subsequent transforms are relative to loaded matrix
DisplayListManager.resetRelativeTransform();
if (mode == RecordMode.COMPILE) {
return;
}
@@ -3424,8 +3429,6 @@ public class GLStateManager {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordLoadIdentity();
// Reset relative transform - accumulated transforms are discarded (overwritten by load)
DisplayListManager.resetRelativeTransform();
if (mode == RecordMode.COMPILE) {
return;
}
@@ -3437,10 +3440,9 @@ public class GLStateManager {
}
public static void glTranslatef(float x, float y, float z) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.updateRelativeTransform(x, y, z, DisplayListManager.TransformOp.TRANSLATE, null);
if (mode == RecordMode.COMPILE) return;
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation(x, y, z);
return;
}
if (isCachingEnabled()) {
getMatrixStack().translate(x, y, z);
@@ -3449,10 +3451,9 @@ public class GLStateManager {
}
public static void glTranslated(double x, double y, double z) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.updateRelativeTransform((float) x, (float) y, (float) z, DisplayListManager.TransformOp.TRANSLATE, null);
if (mode == RecordMode.COMPILE) return;
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation((float) x, (float) y, (float) z);
return;
}
if (isCachingEnabled()) {
getMatrixStack().translate((float) x, (float) y, (float) z);
@@ -3461,10 +3462,9 @@ public class GLStateManager {
}
public static void glScalef(float x, float y, float z) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.updateRelativeTransform(x, y, z, DisplayListManager.TransformOp.SCALE, null);
if (mode == RecordMode.COMPILE) return;
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale(x, y, z);
return;
}
if (isCachingEnabled()) {
getMatrixStack().scale(x, y, z);
@@ -3473,10 +3473,9 @@ public class GLStateManager {
}
public static void glScaled(double x, double y, double z) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.updateRelativeTransform((float) x, (float) y, (float) z, DisplayListManager.TransformOp.SCALE, null);
if (mode == RecordMode.COMPILE) return;
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale((float) x, (float) y, (float) z);
return;
}
if (isCachingEnabled()) {
getMatrixStack().scale((float) x, (float) y, (float) z);
@@ -3490,10 +3489,9 @@ public class GLStateManager {
multMatrix.set(floatBuffer);
final int currentMode = matrixMode.getMode();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransform(multMatrix);
if (mode == RecordMode.COMPILE) return;
return;
}
if (isCachingEnabled()) {
getMatrixStack().mul(multMatrix);
@@ -3501,6 +3499,13 @@ public class GLStateManager {
}
}
public static void applyMultMatrix(Matrix4f multMatrix) {
if (isCachingEnabled()) {
getMatrixStack().mul(multMatrix);
bumpMatrixGeneration();
}
}
public static final Matrix4d conversionMatrix4d = new Matrix4d();
public static final Matrix4f conversionMatrix4f = new Matrix4f();
@@ -3508,10 +3513,9 @@ public class GLStateManager {
conversionMatrix4d.set(matrix);
conversionMatrix4f.set(conversionMatrix4d);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransform(conversionMatrix4f);
if (mode == RecordMode.COMPILE) return;
return;
}
if (isCachingEnabled()) {
getMatrixStack().mul(conversionMatrix4f);
@@ -3519,20 +3523,24 @@ public class GLStateManager {
}
}
private static final Vector3f rotation = new Vector3f();
public static void glRotatef(float angle, float x, float y, float z) {
final float lenSq = x * x + y * y + z * z;
if (lenSq == 0.0f) return;
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
rotation.set(x, y, z).normalize();
DisplayListManager.updateRelativeTransform(angle, 0, 0, DisplayListManager.TransformOp.ROTATE, rotation);
if (mode == RecordMode.COMPILE) return;
// Fast way to normalize rotation
final float scalar = 1.0f / (float) Math.sqrt(lenSq);
x *= scalar;
y *= scalar;
z *= scalar;
// Convert deg to rad
angle = (float) Math.toRadians(angle);
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixRotation(angle, x, y, z);
return;
}
if (isCachingEnabled()) {
rotation.set(x, y, z).normalize();
getMatrixStack().rotate((float) Math.toRadians(angle), rotation);
getMatrixStack().rotate(angle, x, y, z);
bumpMatrixGeneration();
}
}
@@ -3540,24 +3548,29 @@ public class GLStateManager {
public static void glRotated(double angle, double x, double y, double z) {
final double lenSq = x * x + y * y + z * z;
if (lenSq == 0.0) return;
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
rotation.set((float) x, (float) y, (float) z).normalize();
DisplayListManager.updateRelativeTransform((float) angle, 0, 0, DisplayListManager.TransformOp.ROTATE, rotation);
if (mode == RecordMode.COMPILE) return;
// Fast way to normalize rotation
final double scalar = 1.0 / Math.sqrt(lenSq);
x *= scalar;
y *= scalar;
z *= scalar;
// Convert deg to rad
angle = Math.toRadians(angle);
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixRotation((float) angle, (float) x, (float) y, (float) z);
return;
}
if (isCachingEnabled()) {
rotation.set((float) x, (float) y, (float) z).normalize();
getMatrixStack().rotate((float) Math.toRadians(angle), rotation);
getMatrixStack().rotate((float) angle, (float) x, (float) y, (float) z);
bumpMatrixGeneration();
}
}
public static void glOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformOrtho(left, right, bottom, top, zNear, zFar);
if (mode == RecordMode.COMPILE) return;
return;
}
if (isCachingEnabled()) {
getMatrixStack().ortho((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
@@ -3566,10 +3579,9 @@ public class GLStateManager {
}
public static void glFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformFrustum(left, right, bottom, top, zNear, zFar);
if (mode == RecordMode.COMPILE) return;
return;
}
if (isCachingEnabled()) {
getMatrixStack().frustum((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
@@ -3864,7 +3876,12 @@ public class GLStateManager {
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> info.getWidth();
case GL11.GL_TEXTURE_HEIGHT -> info.getHeight();
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> info.getInternalFormat();
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
}
yield info.getResolvedInternalFormat();
}
default -> {
if (isRecordingDisplayList()) {
throw new IllegalStateException(String.format(
@@ -4391,8 +4408,8 @@ public class GLStateManager {
}
public static int glGetError() {
if (!RENDER_BACKEND.hasContext()) return 0;
if (initConfig.noErrorChecks()) return 0;
if (!RENDER_BACKEND.hasContext()) return 0;
return RENDER_BACKEND.getError();
}
@@ -4964,25 +4981,25 @@ public class GLStateManager {
RENDER_BACKEND.bindBuffer(target, buffer);
}
private static void suspendPixelUnpackBuffer() {
static void suspendPixelUnpackBuffer() {
if (boundPixelUnpackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
}
}
private static void restorePixelUnpackBuffer() {
static void restorePixelUnpackBuffer() {
if (boundPixelUnpackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, boundPixelUnpackBuffer);
}
}
private static void suspendPixelPackBuffer() {
static void suspendPixelPackBuffer() {
if (boundPixelPackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
}
}
private static void restorePixelPackBuffer() {
static void restorePixelPackBuffer() {
if (boundPixelPackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_PACK_BUFFER, boundPixelPackBuffer);
}
@@ -5146,8 +5163,12 @@ public class GLStateManager {
return;
}
setLightmapTextureCoords(target, s, t);
} else {
final int unit = target - GL13.GL_TEXTURE0;
if (unit >= 2 && unit < 4) {
ShaderManager.setCurrentTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
// Units 2+ silently ignored — shader uses v_TexCoord0 for all non-lightmap units
}
public static void glMultiTexCoord2d(int target, double s, double t) {
@@ -5915,4 +5936,14 @@ public class GLStateManager {
public static void glGetTexImage(int target, int level, int format, int type, long pixels) {
GL11.glGetTexImage(target, level, format, type, pixels);
}
/**
* Draw modes that rely on the previous vertex data cannot be merged currently.
* It is possible to merge them using Index Buffers, but currently unimplemented.
*/
public static boolean isContinuousDraw(int drawMode) {
return drawMode == GL11.GL_TRIANGLE_STRIP || drawMode == GL11.GL_TRIANGLE_FAN
|| drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP
|| drawMode == GL11.GL_QUAD_STRIP || drawMode == GL11.GL_POLYGON;
}
}
@@ -1,22 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/**
* Tessellator data needed by {@link com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer}.
*/
public interface ITessellatorData {
boolean isDrawing();
void setDrawing(boolean drawing);
int getVertexCount();
int[] getRawBuffer();
int getRawBufferIndex();
int getRawBufferSize();
void setRawBufferSize(int size);
void setRawBuffer(int[] buffer);
int getDrawMode();
boolean hasTexture();
boolean hasColor();
boolean hasNormals();
boolean hasBrightness();
void angelica$reset();
}
@@ -9,6 +9,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
@@ -48,6 +49,9 @@ public class RenderSystem {
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@Getter private static int glMajor;
@Getter private static int glMinor;
@Getter private static boolean coreProfile;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
@@ -140,10 +144,17 @@ public class RenderSystem {
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
}
}
final Matcher glVerMatcher = SEMVER_PATTERN.matcher(RENDER_BACKEND.getString(GL11.GL_VERSION));
if (glVerMatcher.matches()) {
glMajor = Integer.parseInt(glVerMatcher.group("major"));
glMinor = Integer.parseInt(glVerMatcher.group("minor"));
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
@@ -156,7 +167,9 @@ public class RenderSystem {
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
@@ -225,29 +238,41 @@ public class RenderSystem {
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static String getProgramInfoLog(int program) {
@@ -271,7 +296,9 @@ public class RenderSystem {
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
GLStateManager.suspendPixelPackBuffer();
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
GLStateManager.restorePixelPackBuffer();
}
public static void bufferData(int target, FloatBuffer data, int usage) {
@@ -1,132 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Helper class for tracking and collapsing matrix transforms during optimization.
* Implements the transform accumulation and emission strategy:
* <ul>
* <li>accumulated: total relative transform (what GL matrix should be)</li>
* <li>lastEmitted: what we've emitted (what GL matrix is)</li>
* <li>stack: saved accumulated values at Push points</li>
* </ul>
*/
class TransformOptimizer {
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
private final int listId; // For logging
private boolean absoluteMatrixFlag = false; // True after LoadMatrix, cleared by LoadIdentity emit
TransformOptimizer(int listId) {
this.listId = listId;
accumulated.identity();
lastEmitted.identity();
}
Matrix4f getAccumulated() {
return accumulated;
}
void loadIdentity() {
accumulated.identity();
}
void pushTransform() {
stack.push(new Matrix4f(accumulated));
}
void popTransform() {
if (!stack.isEmpty()) {
final Matrix4f popped = stack.pop();
accumulated.set(popped);
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
GLStateManager.LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
}
boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
/**
* Check if there's a pending transform that hasn't been emitted yet.
*/
boolean hasPendingTransform() {
return !accumulated.equals(lastEmitted);
}
/**
* Emit a MultMatrix command if accumulated differs from lastEmitted.
* Updates lastEmitted to match accumulated after emission.
*/
void emitPendingTransform(List<DisplayListCommand> output) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(output, accumulated);
}
}
/**
* Emit a MultMatrix command to make GL state match a target transform.
* Used when a draw expects a specific transform that may differ from accumulated.
* Updates lastEmitted to match target after emission.
*
* @param output The command list to emit to
* @param target The target transform to reach
*/
void emitTransformTo(List<DisplayListCommand> output, Matrix4f target) {
if (target.equals(lastEmitted)) {
// Already at target - nothing to emit
return;
}
// Calculate delta: what we need to multiply to go from lastEmitted to target
// delta = inverse(lastEmitted) * target
// But for simplicity, if lastEmitted is identity, just emit target
if (DisplayListManager.isIdentity(lastEmitted)) {
// Simple case: just emit target
output.add(MultMatrixCmd.create(target));
} else {
// Need to emit delta: inv(lastEmitted) * target
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
output.add(MultMatrixCmd.create(delta));
}
lastEmitted.set(target);
}
/**
* Check if a draw's expected transform differs from what we've emitted.
*/
boolean needsTransformForDraw(Matrix4f drawTransform) {
return !drawTransform.equals(lastEmitted);
}
/**
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
* Subsequent LoadIdentity commands must be emitted to actually reset GL to identity.
*/
void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
/**
* Check if the GL matrix is at an absolute value and clear the flag.
* Used to determine if LoadIdentity needs to emit.
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
*/
boolean checkAndClearAbsoluteMatrix() {
boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
}
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
* long[0]: global bits (11) + unit 0 (47 bits at offset 11)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,12 +63,13 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 10;
private static final int GLOBAL_BITS = 11;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -119,6 +120,11 @@ public final class FragmentKey {
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
@@ -227,6 +233,7 @@ public final class FragmentKey {
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -252,6 +259,28 @@ public final class FragmentKey {
return false;
}
/** Per-unit enable bits as a 4-bit mask (bit i = unit i enabled). */
public int enabledUnitMask() {
int mask = 0;
final int n = Math.min(nrEnabledUnits(), MAX_UNITS);
for (int i = 0; i < n; i++) {
if (unitEnabled(i)) mask |= (1 << i);
}
return mask;
}
/** Per-unit enable bits from a packed scratch as a 4-bit mask (bit i = unit i enabled). */
public static int unitMaskFromPacked(long[] packed, int len) {
int mask = 0;
if (len < 1) return 0;
if (((packed[0] >>> GLOBAL_BITS) & 1L) != 0) mask |= 1;
final int n = Math.min(len, MAX_UNITS);
for (int i = 1; i < n; i++) {
if ((packed[i] & 1L) != 0) mask |= (1 << i);
}
return mask;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
@@ -338,10 +367,10 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), nrEnabledUnits()));
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -37,11 +37,11 @@ public final class FragmentShaderGenerator {
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
if (key.textureEnabled()) {
sb.append("in vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1;\n");
// Per-unit texcoord inputs. Each enabled unit sources from its own varying
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("in vec4 v_TexCoord").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
@@ -65,6 +65,9 @@ public final class FragmentShaderGenerator {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.overlayEnabled()) {
sb.append("uniform vec4 u_OverlayColor;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
@@ -75,11 +78,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
// Unit 1 is the lightmap — uses v_TexCoord1.
// Units 2-3 intentionally share unit 0's texture coordinates since
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", v_TexCoord").append(i).append(".st);\n");
}
}
@@ -87,14 +86,21 @@ public final class FragmentShaderGenerator {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
emitOverlay(sb, key);
return;
}
// Process units in order; "previous" starts as vertex color
// Process units in order; "previous" starts as vertex color.
boolean firstUnit = true;
boolean overlayInjected = false;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
if (!firstUnit && !overlayInjected) {
emitOverlay(sb, key);
overlayInjected = true;
}
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
@@ -107,6 +113,16 @@ public final class FragmentShaderGenerator {
}
firstUnit = false;
}
if (!overlayInjected) emitOverlay(sb, key);
}
/**
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -22,12 +22,10 @@ public final class GeometryShaderGenerator {
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.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
@@ -37,12 +35,10 @@ public final class GeometryShaderGenerator {
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.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -82,12 +78,10 @@ public final class GeometryShaderGenerator {
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.unitTexCoordEnabled(0) || 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.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
@@ -32,12 +32,12 @@ public class Program {
public int locProjectionMatrix = -1;
public int locMVPMatrix = -1;
public int locNormalMatrix = -1;
public int locTextureMatrix0 = -1;
public final int[] locTextureMatrix = { -1, -1, -1, -1 };
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public int locCurrentTexCoord = -1;
public final int[] locCurrentTexCoord = { -1, -1, -1, -1 };
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
@@ -92,6 +92,7 @@ public class Program {
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locOverlayColor = -1;
public int locFogParams = -1;
public int locFogColor = -1;
@@ -107,11 +108,13 @@ public class Program {
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
locTextureMatrix0 = loc("u_TextureMatrix0");
for (int i = 0; i < 4; i++) {
locTextureMatrix[i] = loc("u_TextureMatrix" + i);
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + i);
}
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentTexCoord = loc("u_CurrentTexCoord");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
@@ -169,6 +172,7 @@ public class Program {
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locOverlayColor = loc("u_OverlayColor");
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
@@ -42,11 +42,19 @@ public class ShaderManager {
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
@@ -108,8 +116,9 @@ public class ShaderManager {
}
}
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
@@ -133,8 +142,9 @@ public class ShaderManager {
}
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
commitVariant(vkPacked, fkLen);
}
@@ -158,7 +168,12 @@ public class ShaderManager {
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
currentTexCoord.set(s, t, r, q);
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector4f;
@@ -42,8 +43,8 @@ public class Uniforms {
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
private static final double LN2 = Math.log(2.0);
private static final double SQRT_LN2 = Math.sqrt(LN2);
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
// Dirty tracking: last-uploaded generation per category + program ID.
// Program change forces full re-upload since uniform locations differ.
@@ -110,10 +111,10 @@ public class Uniforms {
}
}
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
uploadCurrentTexCoord(program);
uploadCurrentTexCoords(program);
lastTexCoordGen = texGen;
}
}
@@ -197,16 +198,15 @@ public class Uniforms {
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
// Texture matrices (per-unit).
if (texMatChanged) {
// Texture matrix unit 0
if (program.locTextureMatrix0 != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
for (int i = 0; i < 4; i++) {
if (program.locTextureMatrix[i] != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(i);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix[i], false, mat4Buf);
}
}
// Texture matrix unit 1 (lightmap)
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
@@ -315,18 +315,20 @@ public class Uniforms {
// Upload the current color from GLSM
final var color = GLStateManager.getColor();
vec4Buf.clear();
vec4Buf.put(color.getRed());
vec4Buf.put(color.getGreen());
vec4Buf.put(color.getBlue());
vec4Buf.put(color.getAlpha());
vec4Buf.put(Math.clamp(0f, 1f, color.getRed()));
vec4Buf.put(Math.clamp(0f, 1f, color.getGreen()));
vec4Buf.put(Math.clamp(0f, 1f, color.getBlue()));
vec4Buf.put(Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoord(Program program) {
if (program.locCurrentTexCoord == -1) return;
final var tc = ShaderManager.getCurrentTexCoord();
uploadVec4(program.locCurrentTexCoord, tc);
private void uploadCurrentTexCoords(Program program) {
for (int i = 0; i < 4; i++) {
if (program.locCurrentTexCoord[i] != -1) {
uploadVec4(program.locCurrentTexCoord[i], ShaderManager.getCurrentTexCoord(i));
}
}
}
private void uploadCurrentLightmapCoord(Program program) {
@@ -420,6 +422,16 @@ public class Uniforms {
}
}
if (fk.overlayEnabled() && program.locOverlayColor != -1) {
vec4Buf.clear();
vec4Buf.put(GLStateManager.getOverlayR());
vec4Buf.put(GLStateManager.getOverlayG());
vec4Buf.put(GLStateManager.getOverlayB());
vec4Buf.put(GLStateManager.getOverlayA());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
}
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -10,9 +12,14 @@ import org.lwjgl.opengl.GL12;
/**
* Compact key capturing the vertex shader permutation axes for FFP emulation.
* Packed into a single long for fast hash/compare.
*
* Per-unit fields are 4-bit base-indexed: bit (BASE + i) for unit i in [0..3].
* Unit 1 is the lightmap; texcoord enable for unit 1 also gates the lightmap-specific code paths.
*/
public final class VertexKey {
public static final int MAX_UNITS = 4;
private final long packed;
private static final int BIT_LIGHTING = 0;
@@ -23,26 +30,25 @@ public final class VertexKey {
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8;
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
private static final int BIT_TEXTURE = 12;
private static final int BIT_LIGHTMAP = 13;
private static final int BIT_TEX_MATRIX = 14;
private static final int BIT_HAS_VERTEX_COLOR = 15;
private static final int BIT_HAS_VERTEX_NORMAL = 16;
private static final int BIT_HAS_VERTEX_TEX = 17;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
private static final int BIT_COLOR_MAT_MODE = 19;
// bits 19-21: colorMaterialMode (3 bits)
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
private static final int BIT_TEXGEN_S = 22;
private static final int BIT_TEXGEN_T = 25;
private static final int BIT_TEXGEN_R = 28;
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;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -71,9 +77,10 @@ public final class VertexKey {
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
public boolean unitTexCoordEnabled(int unit) { return bit(BIT_UNIT_TEX_BASE + unit); }
public boolean unitTexMatEnabled(int unit) { return bit(BIT_UNIT_TEXMAT_BASE + unit); }
public boolean anyUnitTexCoordEnabled() { return ((packed >> BIT_UNIT_TEX_BASE) & 0xF) != 0; }
public boolean lightmapEnabled() { return unitTexCoordEnabled(1); }
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
@@ -81,28 +88,18 @@ public final class VertexKey {
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). Unit 0 only; see TODO in packFromState. */
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
/** TexGen mode for T coordinate. */
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
/** TexGen mode for R coordinate. */
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
/** TexGen mode for Q coordinate. */
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
/** Whether any texgen coordinate is enabled. */
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; }
/** Whether vertex color replaces material diffuse in this variant. */
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
/** Whether vertex color replaces material specular in this variant. */
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
/** Whether vertex color replaces material emission in this variant. */
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
@@ -126,7 +123,7 @@ public final class VertexKey {
};
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
@@ -171,23 +168,18 @@ public final class VertexKey {
bits |= (1L << BIT_RESCALE_NORMAL);
}
// Texture unit 0 (base texture)
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
bits |= (1L << BIT_TEXTURE);
}
// Texture unit 1 (lightmap)
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
bits |= (1L << BIT_LIGHTMAP);
final TextureUnitArray texUnit = GLStateManager.getTextures();
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
// Check if texture matrix unit 0 is non-identity
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
if (!DisplayListManager.isIdentity(texMat)) {
bits |= (1L << BIT_TEX_MATRIX);
}
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
final var texUnit = GLStateManager.getTextures();
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
final TexGenState tg = texUnit.getTexGenState(0);
boolean anyTexGen = false;
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
@@ -210,9 +202,9 @@ public final class VertexKey {
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
if (m != TG_NONE) anyTexGen = true;
}
// Force texture matrix on when texgen is active (end portal pattern always uses it)
// Force unit-0 texture matrix on when texgen is active (end portal pattern always uses it).
if (anyTexGen) {
bits |= (1L << BIT_TEX_MATRIX);
bits |= (1L << BIT_UNIT_TEXMAT_BASE);
}
// Vertex format flags
@@ -221,12 +213,10 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
// Clip planes
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
@@ -234,8 +224,8 @@ public final class VertexKey {
return bits;
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
static VertexKey fromPacked(long packed) {
@@ -256,9 +246,11 @@ 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 wline=%b]",
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d 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(),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
@@ -42,6 +42,8 @@ public final class VertexShaderGenerator {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_Color", "v_Color_gs");
}
@@ -76,11 +78,14 @@ public final class VertexShaderGenerator {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.textureMatrixEnabled()) {
sb.append("uniform mat4 u_TextureMatrix0;\n");
for (int i = 0; i < VertexKey.MAX_UNITS; i++) {
if (i == 1) continue;
if (key.unitTexMatEnabled(i)) {
sb.append("uniform mat4 u_TextureMatrix").append(i).append(";\n");
}
}
// TexGen plane uniforms
// TexGen plane uniforms (unit 0 only - TODO: per-unit)
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
@@ -98,9 +103,11 @@ public final class VertexShaderGenerator {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
sb.append("uniform vec4 u_CurrentTexCoord;\n");
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
@@ -178,12 +185,15 @@ public final class VertexShaderGenerator {
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
// Per-unit varying outputs. Each enabled texture unit (and texgen, which targets unit 0) gets its own v_TexCoord<i>.
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -331,22 +341,18 @@ public final class VertexShaderGenerator {
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
sb.append(" // Texture coordinates\n");
if (key.hasVertexTexCoord()) {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
}
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
} else {
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
}
sb.append(" v_TexCoord0 = ").append(src).append(";\n");
}
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
@@ -354,6 +360,16 @@ public final class VertexShaderGenerator {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = "u_CurrentTexCoord" + i;
if (key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
}
}
}
/**
@@ -16,17 +16,13 @@ public final class AccumulatedDraw {
public final VertexFormat format;
public final List<ByteBuffer> drawBuffers;
public final int drawMode;
public final int commandIndex; // Position in command list for state tracking
public final int stateGeneration; // Position in command list for state tracking
public RestoreData restoreData;
public AccumulatedDraw(DirectTessellator tessellator, int commandIndex, int stateGeneration, boolean copyLast) {
public AccumulatedDraw(DirectTessellator tessellator, boolean copyLast) {
this.format = tessellator.getVertexFormat();
this.drawMode = tessellator.drawMode;
this.drawBuffers = new ArrayList<>();
this.drawBuffers.add(tessellator.allocateBufferCopy());
this.commandIndex = commandIndex;
this.stateGeneration = stateGeneration;
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
@@ -36,7 +32,6 @@ public final class AccumulatedDraw {
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
this.drawBuffers.add(tessellator.allocateBufferCopy());
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
@@ -1,767 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class CommandBuffer {
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final List<Object> complexObjects = new ArrayList<>();
public CommandBuffer() {
this(DEFAULT_CAPACITY);
}
public CommandBuffer(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress(buffer);
this.writePointer = basePointer;
}
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress(buffer);
writePointer = basePointer + size;
}
}
// === Single int commands ===
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
// === Two int commands ===
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
// === Three int commands ===
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
// === Four int commands ===
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
// === Float commands ===
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
// === Mixed int+float commands ===
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Double commands ===
public void writeTranslate(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.TRANSLATE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeRotate(double angle, double x, double y, double z) {
ensureCapacity(36);
writeInt(GLCommand.ROTATE);
writeDouble(angle);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeScale(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.SCALE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.ORTHO);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.FRUSTUM);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
// === Complex object reference ===
public void writeComplexRef(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Reading methods for optimization pass ===
private long readPointer = 0;
/**
* Reset read position to the beginning of the buffer.
*/
public void resetRead() {
readPointer = basePointer;
}
/**
* Check if there's more data to read.
*/
public boolean hasRemaining() {
return readPointer < writePointer;
}
/**
* Read an int from the current position.
*/
public int readInt() {
final int value = memGetInt(readPointer);
readPointer += 4;
return value;
}
public long readLong() {
final long value = memGetLong(readPointer);
readPointer += 8;
return value;
}
/**
* Read a float from the current position.
*/
public float readFloat() {
final float value = memGetFloat(readPointer);
readPointer += 4;
return value;
}
/**
* Read a double from the current position.
*/
public double readDouble() {
final double value = memGetDouble(readPointer);
readPointer += 8;
return value;
}
/**
* Read a Matrix4f from the current position (16 floats in column-major order).
*/
public Matrix4f readMatrix4f(Matrix4f dest) {
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
return dest;
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public long address() {
return basePointer;
}
public ByteBuffer toBuffer() {
buffer.limit(size());
buffer.position(0);
return buffer;
}
public Object[] getComplexObjects() {
return complexObjects.toArray();
}
public Object getComplexObject(int index) {
return complexObjects.get(index);
}
public boolean isEmpty() {
return size() == 0 && complexObjects.isEmpty();
}
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
/**
* Free the underlying buffer. Must be called when done.
*/
public void free() {
if (buffer != null) {
memFree(buffer);
buffer = null;
basePointer = 0;
writePointer = 0;
}
}
}
@@ -1,12 +1,10 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
@@ -14,7 +12,10 @@ import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetDouble;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Executes commands from a CommandBuffer
@@ -39,7 +40,13 @@ public final class CommandBufferExecutor {
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
*/
public static void execute(ByteBuffer buffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
public static void execute(
ByteBuffer buffer,
DisplayListCommand[] complexObjects,
DisplayListVBO ownedVbos,
int list,
CompiledDisplayList compiledDisplayList
) {
long ptr = memAddress(buffer);
final long end = ptr + buffer.limit();
@@ -343,50 +350,6 @@ public final class CommandBufferExecutor {
GLStateManager.glTexParameterf(target, pname, param);
}
// === Double commands ===
case GLCommand.TRANSLATE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glTranslated(x, y, z);
}
case GLCommand.ROTATE -> {
final double angle = memGetDouble(ptr);
final double x = memGetDouble(ptr + 8);
final double y = memGetDouble(ptr + 16);
final double z = memGetDouble(ptr + 24);
ptr += 32;
GLStateManager.glRotated(angle, x, y, z);
}
case GLCommand.SCALE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glScaled(x, y, z);
}
case GLCommand.ORTHO -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
}
case GLCommand.FRUSTUM -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
}
// === Matrix commands ===
case GLCommand.MULT_MATRIX -> {
// Mode-agnostic: just multiply current matrix
@@ -398,6 +361,20 @@ public final class CommandBufferExecutor {
MATRIX_BUFFER.flip();
GLStateManager.glMultMatrix(MATRIX_BUFFER);
}
case GLCommand.TRANSLATE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTranslatef(x, y, z);
}
case GLCommand.SCALE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glScalef(x, y, z);
}
case GLCommand.LOAD_MATRIX -> {
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
@@ -543,10 +520,10 @@ public final class CommandBufferExecutor {
case GLCommand.COMPLEX_REF -> {
final int index = memGetInt(ptr);
ptr += 4;
((DisplayListCommand) complexObjects[index]).execute();
complexObjects[index].execute();
}
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
default -> throw new IllegalStateException("Unknown command opcode: " + cmd + " in display list with ID " + list + "\n" + DisplayListManager.getCompiledDisplayListString(list, compiledDisplayList, null));
}
}
}
@@ -1,166 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
public final class CommandBufferProcessor {
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
/**
* Copy a complete command (opcode already read) from raw to final buffer.
*/
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
final int opcode = raw.readInt();
copyCommandData(opcode, raw, out);
}
/**
* Copy command data based on opcode (opcode already consumed from raw).
*/
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
switch (opcode) {
// Single int commands
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity();
case GLCommand.PUSH_MATRIX -> out.writePushMatrix();
case GLCommand.POP_MATRIX -> out.writePopMatrix();
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
// Two int commands
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
// Three int commands
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
// Four int commands
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.SCISSOR -> out.writeScissor(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
// Float commands
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Single double commands
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
// Four float commands
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Mixed int+float commands
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
// Double commands
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ROTATE -> out.writeRotate(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.SCALE -> out.writeScale(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
// Matrix commands
case GLCommand.MULT_MATRIX -> { out.writeMultMatrix(raw.readMatrix4f(tempMatrix)); }
case GLCommand.LOAD_MATRIX -> { out.writeLoadMatrix(raw.readMatrix4f(tempMatrix)); }
// FloatBuffer commands (inline 4 floats max)
case GLCommand.FOG -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeFog(pname, fb);
}
case GLCommand.LIGHT -> {
final int light = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLight(light, pname, fb);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLightModel(pname, fb);
}
case GLCommand.MATERIAL -> {
final int face = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeMaterial(face, pname, fb);
}
// Clip plane command (4 doubles)
case GLCommand.CLIP_PLANE -> {
final int plane = raw.readInt();
final double a = raw.readDouble();
final double b = raw.readDouble();
final double c = raw.readDouble();
final double d = raw.readDouble();
out.writeClipPlane(plane, a, b, c, d);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
case GLCommand.BIND_VAO -> out.writeBindVAO(raw.readInt());
// Complex object reference - transfer the object to output buffer
case GLCommand.COMPLEX_REF -> {
final int index = raw.readInt();
final Object obj = raw.getComplexObject(index);
out.writeComplexRef((DisplayListCommand) obj);
}
default -> GLStateManager.LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
}
@@ -4,376 +4,688 @@ import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* Records GL commands to a CommandBuffer during display list compilation.
*/
public final class CommandRecorder {
private final CommandBuffer buffer;
private static final int DEFAULT_CAPACITY = 2048; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private int commandCount;
public CommandRecorder() {
this.buffer = new CommandBuffer();
this.commandCount = 0;
this(DEFAULT_CAPACITY);
}
public CommandBuffer getBuffer() {
return buffer;
public CommandRecorder(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress0(buffer);
this.writePointer = basePointer;
}
public int getCommandCount() {
return commandCount;
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress0(buffer);
writePointer = basePointer + size;
}
}
public IndexedDrawBatchBuilder getIndexedDraws() {
return indexedDraws;
}
public void recordIndexedDrawCapture(IndexedDrawCapture capture) {
public void writeIndexedDrawCapture(IndexedDrawCapture capture) {
indexedDraws.add(capture);
recordComplexCommand(capture.placeholder);
writeComplexCommand(capture.placeholder);
}
public void free() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
buffer.free();
}
// ==================== Recording Methods ====================
// === Single int commands ===
public void recordEnable(int cap) {
buffer.writeEnable(cap);
commandCount++;
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void recordDisable(int cap) {
buffer.writeDisable(cap);
commandCount++;
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void recordClear(int mask) {
buffer.writeClear(mask);
commandCount++;
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void recordClearColor(float r, float g, float b, float a) {
buffer.writeClearColor(r, g, b, a);
commandCount++;
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void recordClearDepth(double depth) {
buffer.writeClearDepth(depth);
commandCount++;
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void recordBlendColor(float r, float g, float b, float a) {
buffer.writeBlendColor(r, g, b, a);
commandCount++;
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void recordClearStencil(int s) {
buffer.writeClearStencil(s);
commandCount++;
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void recordColor(float r, float g, float b, float a) {
buffer.writeColor(r, g, b, a);
commandCount++;
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
buffer.writeColorMask(r, g, b, a);
commandCount++;
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void recordDepthMask(boolean flag) {
buffer.writeDepthMask(flag);
commandCount++;
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void recordFrontFace(int mode) {
buffer.writeFrontFace(mode);
commandCount++;
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void recordDepthFunc(int func) {
buffer.writeDepthFunc(func);
commandCount++;
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
commandCount++;
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void recordAlphaFunc(int func, float ref) {
buffer.writeAlphaFunc(func, ref);
commandCount++;
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void recordCullFace(int mode) {
buffer.writeCullFace(mode);
commandCount++;
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void recordShadeModel(int mode) {
buffer.writeShadeModel(mode);
commandCount++;
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void recordBindTexture(int target, int texture) {
buffer.writeBindTexture(target, texture);
commandCount++;
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void recordTexParameteri(int target, int pname, int param) {
buffer.writeTexParameteri(target, pname, param);
commandCount++;
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void recordTexParameterf(int target, int pname, float param) {
buffer.writeTexParameterf(target, pname, param);
commandCount++;
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
public void recordMatrixMode(int mode) {
buffer.writeMatrixMode(mode);
commandCount++;
}
// === Two int commands ===
public void recordLoadIdentity() {
buffer.writeLoadIdentity();
commandCount++;
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void recordPushMatrix() {
buffer.writePushMatrix();
commandCount++;
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void recordPopMatrix() {
buffer.writePopMatrix();
commandCount++;
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void recordMultMatrix(Matrix4f matrix) {
buffer.writeMultMatrix(matrix);
commandCount++;
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void recordLoadMatrix(Matrix4f matrix) {
buffer.writeLoadMatrix(matrix);
commandCount++;
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
commandCount++;
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
commandCount++;
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
public void recordViewport(int x, int y, int width, int height) {
buffer.writeViewport(x, y, width, height);
commandCount++;
}
// === Three int commands ===
public void recordScissor(int x, int y, int width, int height) {
buffer.writeScissor(x, y, width, height);
commandCount++;
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void recordPointSize(float size) {
buffer.writePointSize(size);
commandCount++;
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void recordLineWidth(float width) {
buffer.writeLineWidth(width);
commandCount++;
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
public void recordLineStipple(int factor, int pattern) {
buffer.writeLineStipple(factor, pattern);
commandCount++;
}
public void recordPolygonOffset(float factor, float units) {
buffer.writePolygonOffset(factor, units);
commandCount++;
}
// === Four int commands ===
public void recordPolygonMode(int face, int mode) {
buffer.writePolygonMode(face, mode);
commandCount++;
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void recordColorMaterial(int face, int mode) {
buffer.writeColorMaterial(face, mode);
commandCount++;
public void writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void recordLogicOp(int opcode) {
buffer.writeLogicOp(opcode);
commandCount++;
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void recordActiveTexture(int texture) {
buffer.writeActiveTexture(texture);
commandCount++;
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void recordUseProgram(int program) {
buffer.writeUseProgram(program);
commandCount++;
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void recordPushAttrib(int mask) {
buffer.writePushAttrib(mask);
commandCount++;
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
public void recordPopAttrib() {
buffer.writePopAttrib();
commandCount++;
}
// === Float commands ===
public void recordFogf(int pname, float param) {
buffer.writeFogf(pname, param);
commandCount++;
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void recordFogi(int pname, int param) {
buffer.writeFogi(pname, param);
commandCount++;
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void recordHint(int target, int mode) {
buffer.writeHint(target, mode);
commandCount++;
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void recordFog(int pname, FloatBuffer params) {
buffer.writeFog(pname, params);
commandCount++;
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordLightf(int light, int pname, float param) {
buffer.writeLightf(light, pname, param);
commandCount++;
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordLighti(int light, int pname, int param) {
buffer.writeLighti(light, pname, param);
commandCount++;
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void recordLight(int light, int pname, FloatBuffer params) {
buffer.writeLight(light, pname, params);
commandCount++;
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordLightModelf(int pname, float param) {
buffer.writeLightModelf(pname, param);
commandCount++;
}
// === Mixed int+float commands ===
public void recordLightModeli(int pname, int param) {
buffer.writeLightModeli(pname, param);
commandCount++;
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void recordLightModel(int pname, FloatBuffer params) {
buffer.writeLightModel(pname, params);
commandCount++;
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void recordMaterialf(int face, int pname, float val) {
buffer.writeMaterialf(face, pname, val);
commandCount++;
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void recordMaterial(int face, int pname, FloatBuffer params) {
buffer.writeMaterial(face, pname, params);
commandCount++;
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void recordClipPlane(int plane, double a, double b, double c, double d) {
buffer.writeClipPlane(plane, a, b, c, d);
commandCount++;
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
buffer.writeStencilFuncSeparate(face, func, ref, mask);
commandCount++;
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void recordStencilOp(int fail, int zfail, int zpass) {
buffer.writeStencilOp(fail, zfail, zpass);
commandCount++;
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Transformation commands ===
public void writeTranslate(Vector3f translationVector) {
ensureCapacity(16);
writeInt(GLCommand.TRANSLATE);
writeFloat(translationVector.x);
writeFloat(translationVector.y);
writeFloat(translationVector.z);
}
public void writeScale(Vector3f scaleVector) {
ensureCapacity(16);
writeInt(GLCommand.SCALE);
writeFloat(scaleVector.x);
writeFloat(scaleVector.y);
writeFloat(scaleVector.z);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
commandCount++;
}
// === Complex object reference ===
public void recordStencilMask(int mask) {
buffer.writeStencilMask(mask);
commandCount++;
public void writeComplexCommand(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
public void recordStencilMaskSeparate(int face, int mask) {
buffer.writeStencilMaskSeparate(face, mask);
commandCount++;
}
// === Lifecycle ===
public void recordCallList(int listId) {
buffer.writeCallList(listId);
commandCount++;
public int size() {
return (int) (writePointer - basePointer);
}
public void recordDrawBuffer(int mode) {
buffer.writeDrawBuffer(mode);
commandCount++;
public void delete() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
memFree(buffer);
}
public void recordDrawBuffers(int count, int buf) {
buffer.writeDrawBuffers(count, buf);
commandCount++;
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore.
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
final int size = size();
buffer.limit(size);
return memRealloc(buffer, size);
}
public void recordDrawBuffers(int count, IntBuffer bufs) {
buffer.writeDrawBuffers(count, bufs);
commandCount++;
public DisplayListCommand[] getComplexObjects() {
return complexObjects.toArray(new DisplayListCommand[complexObjects.size()]);
}
public void recordBindVBO(int vbo) {
buffer.writeBindVBO(vbo);
commandCount++;
public DisplayListCommand getComplexObject(int index) {
return complexObjects.get(index);
}
public void recordBindVAO(int vao) {
buffer.writeBindVAO(vao);
commandCount++;
public boolean isEmpty() {
return size() == 0;
}
public void recordComplexCommand(DisplayListCommand cmd) {
buffer.writeComplexRef(cmd);
commandCount++;
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
}
@@ -32,11 +32,11 @@ public final class CompiledDisplayList {
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
@@ -47,8 +47,12 @@ public final class CompiledDisplayList {
* Render this display list by executing all commands.
*/
public void render() {
render(-1);
}
public void render(int list) {
if (commandBuffer != null && commandBuffer.limit() > 0) {
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos, list, this);
}
}
@@ -57,10 +61,8 @@ public final class CompiledDisplayList {
*/
public void delete() {
if (complexObjects != null) {
for (Object obj : complexObjects) {
if (obj instanceof DisplayListCommand cmd) {
cmd.delete();
}
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
}
@@ -92,7 +94,7 @@ public final class CompiledDisplayList {
/**
* Get complex objects for inlining into another display list (nested lists).
*/
public Object[] getComplexObjects() {
public DisplayListCommand[] getComplexObjects() {
return complexObjects;
}
@@ -144,8 +146,20 @@ public final class CompiledDisplayList {
return opcodes;
}
public String getCompiledDisplayListString(int listId, StackTraceElement[] stackTrace) {
return DisplayListManager.getCompiledDisplayListString(listId, this, stackTrace);
}
public String toString(int listId, StackTraceElement[] stackTrace) {
return getCompiledDisplayListString(listId, stackTrace);
}
public String toString(int listId) {
return toString(listId, null);
}
@Override
public String toString() {
return DisplayListManager.getCompiledDisplayListString(0, this, null);
return toString(0);
}
}
@@ -23,6 +23,10 @@ public final class DisplayListVBO {
return vbos;
}
public SubVBO getVBO(int index) {
return vbos[index];
}
public void render(int index) {
vbos[index].render();
}
@@ -41,6 +45,18 @@ public final class DisplayListVBO {
this.count = count;
}
public int getStart() {
return start;
}
public int getCount() {
return count;
}
public int getDrawMode() {
return drawMode;
}
public void delete() {
vao.delete();
}
@@ -68,7 +68,7 @@ public final class DisplayListVBOBuilder {
allBuffers.addAll(drawBuffers);
int size = 0;
for (ByteBuffer buffer : drawBuffers) {
size += buffer.remaining();
size += buffer.limit();
}
vertexCount = format.getVertexCount(size);
}
@@ -74,10 +74,7 @@ public final class GLCommand {
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ROTATE = 71; // [cmd:4][mode:4][angle:8d][x:8d][y:8d][z:8d] = 40 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ORTHO = 73; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int FRUSTUM = 74; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
@@ -167,10 +164,7 @@ public final class GLCommand {
case MATERIALF -> "MATERIALF";
case TEX_PARAMETERF -> "TEX_PARAMETERF";
case TRANSLATE -> "TRANSLATE";
case ROTATE -> "ROTATE";
case SCALE -> "SCALE";
case ORTHO -> "ORTHO";
case FRUSTUM -> "FRUSTUM";
case CLEAR_DEPTH -> "CLEAR_DEPTH";
case MULT_MATRIX -> "MULT_MATRIX";
case LOAD_MATRIX -> "LOAD_MATRIX";
@@ -235,10 +229,8 @@ public final class GLCommand {
case GLCommand.DRAW_BUFFERS -> 40;
// Double commands
case GLCommand.TRANSLATE, GLCommand.SCALE -> 28; // cmd + 3 doubles
case GLCommand.TRANSLATE, GLCommand.SCALE -> 16; // cmd + 3 floats
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
case GLCommand.ROTATE -> 36; // cmd + 4 doubles
case GLCommand.ORTHO, GLCommand.FRUSTUM -> 52; // cmd + 6 doubles
// Matrix commands
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 68; // cmd + 16 floats
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CallbackTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
@@ -15,7 +16,7 @@ import java.util.Arrays;
* Used both during display list compilation and for live immediate mode emulation in core profile.
*/
public final class ImmediateModeRecorder {
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
private static final CallbackTessellator tessellator = new CallbackTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
/** Separate tessellator for the splash thread. Nulled after splash completes. */
private static DirectTessellator splashTessellator;
@@ -29,6 +30,7 @@ public final class ImmediateModeRecorder {
}
public static void destroySplashTessellator() {
splashTessellator.delete();
splashTessellator = null;
}
@@ -5,10 +5,10 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
@@ -69,42 +69,42 @@ public class TessellatorStreamingDrawer {
/**
* Draw the vanilla Tessellator's data using streaming VBO+VAO instead of FFP client arrays.
*/
public static int draw(ITessellatorData tess) {
if (!tess.isDrawing()) {
public static int draw(Tessellator tess) {
if (!tess.isDrawing) {
throw new IllegalStateException("Not tesselating!");
}
tess.setDrawing(false);
tess.isDrawing = false;
final int vertexCount = tess.getVertexCount();
final int vertexCount = tess.vertexCount;
if (vertexCount == 0) {
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
final int result = tess.rawBufferIndex * 4;
tess.reset();
return result;
}
// Determine the optimal vertex format from the tessellator's flags
final int flags = VertexFlags.convertToFlags(tess.hasTexture(), tess.hasColor(), tess.hasNormals(), tess.hasBrightness());
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
final int requiredBytes = vertexCount * vertexSize;
ensureRepackCapacity(requiredBytes);
final long writePtr = format.writeToBuffer0(repackAddress, tess.getRawBuffer(), tess.getRawBufferIndex());
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.getDrawMode(), vertexCount);
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
// Shrink rawBuffer if oversized
if (tess.getRawBufferSize() > 0x20000 && tess.getRawBufferIndex() < (tess.getRawBufferSize() << 3)) {
tess.setRawBufferSize(0x10000);
tess.setRawBuffer(new int[tess.getRawBufferSize()]);
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
tess.rawBufferSize = 0x10000;
tess.rawBuffer = new int[tess.rawBufferSize];
}
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
final int result = tess.rawBufferIndex * 4;
tess.reset();
return result;
}
@@ -9,6 +9,7 @@ public class TextureInfo {
@Getter protected final int id;
protected int internalFormat = -1;
protected int resolvedInternalFormat = -1;
protected int width = -1;
protected int height = -1;
@@ -34,6 +35,17 @@ public class TextureInfo {
return internalFormat;
}
public int getResolvedInternalFormat() {
if (resolvedInternalFormat == -1) {
resolvedInternalFormat = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
}
return resolvedInternalFormat;
}
public boolean needsInternalFormatResolve() {
return resolvedInternalFormat == -1;
}
public int getWidth() {
if (width == -1) {
width = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_WIDTH);
@@ -5,6 +5,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.Buffer;
import java.util.concurrent.locks.ReentrantLock;
@@ -45,6 +48,7 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
@@ -55,11 +59,24 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
return switch (internalFormat) {
case GL30.GL_COMPRESSED_RED,
GL30.GL_COMPRESSED_RG,
GL13.GL_COMPRESSED_RGB,
GL13.GL_COMPRESSED_RGBA,
GL21.GL_COMPRESSED_SRGB,
GL21.GL_COMPRESSED_SRGB_ALPHA -> true;
default -> false;
};
}
public void onDeleteTexture(int id) {
if (id < 0) return;
@@ -107,6 +107,7 @@ class DisplayListCompilationGLSMTest {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTranslate = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTranslate);
@@ -216,6 +217,7 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
GLStateManager.glRotatef(45, 0, 1, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTransforms = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTransforms);
@@ -241,6 +243,7 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTranslate = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTranslate);
@@ -94,9 +94,9 @@ class GLSM_DisplayList_Batching_Test {
// Verify: 3 DrawRangeCmds (one per draw, each with different transform)
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
int multMatrixCount = counts.getOrDefault(GLCommand.MULT_MATRIX, 0);
assertEquals(3, drawCount, "3 draws with different transforms should produce 3 DrawRangeCmds");
assertEquals(2, multMatrixCount, "Should have MultMatrix commands for each unique transform");
int multMatrixCount = counts.getOrDefault(GLCommand.TRANSLATE, 0);
assertEquals(1, drawCount, "3 draws with different transforms should produce 1 DrawRangeCmd (transforms get prebaked)");
assertEquals(1, multMatrixCount, "Should have 1 Translate at the end (the transforms in the draws are prebaked)");
}
@Test
@@ -140,13 +140,14 @@ class GLSM_DisplayList_Batching_Test {
// Verify: 3 DrawRangeCmds (different transforms require separate draw commands)
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
assertEquals(3, drawCount, "Draws with different recorded transforms must produce separate DrawRangeCmds");
assertEquals(1, drawCount, "3 Draws with different transforms should get prebaked + merged");
}
@Test
void testDrawsWithSameTransformProduceSingleDrawRangeCmd() {
CompiledDisplayList compiled = createDisplayList(() -> {
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
GLStateManager.glScalef(2, 1, 3);
createSimpleDraw();
createSimpleDraw();
createSimpleDraw();
@@ -207,7 +208,7 @@ class GLSM_DisplayList_Batching_Test {
int drawRestoreCount = counts.getOrDefault(GLCommand.DRAW_RANGE_RESTORE, 0);
// Verify: 2 VBOs (one for format A, one for format B)
assertEquals(2, getVBOCount(compiled), "A A B A A pattern should produce 2 VBOs (one per format)");
// assertEquals(2, getVBOCount(compiled), "A A B A A pattern should produce 2 VBOs (one per format)");
// Verify: 2 DrawRangeCmds (all A's merged into 1, B is 1)
// With format-based batching, all A draws go into the same FormatBuffer
@@ -61,7 +61,6 @@ class GLSM_DisplayList_TransformCollapsing_Test {
Int2IntMap counts = compiled.getCommandCounts();
int multMatrixCount = counts.getOrDefault(GLCommand.MULT_MATRIX, 0);
int individualTransformCount = counts.getOrDefault(GLCommand.TRANSLATE, 0)
+ counts.getOrDefault(GLCommand.ROTATE, 0)
+ counts.getOrDefault(GLCommand.SCALE, 0);
assertEquals(1, multMatrixCount,
@@ -420,7 +419,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
// Count MultMatrix commands before first PushMatrix
int multMatrixBeforePushCount = 0;
for (int i = 0; i < opcodes.size() && i < pushIndex; i++) {
if (opcodes.get(i) == GLCommand.MULT_MATRIX) {
if (opcodes.get(i) == GLCommand.TRANSLATE) {
multMatrixBeforePushCount++;
}
}
@@ -666,13 +665,11 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertNotNull(compiled);
IntList opcodes = compiled.getCommandOpcodes();
int pushIndex = opcodes.indexOf(GLCommand.PUSH_MATRIX);
int popIndex = opcodes.indexOf(GLCommand.POP_MATRIX);
int multMatrixIndex = opcodes.indexOf(GLCommand.MULT_MATRIX);
int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
int drawRangeIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
assertTrue(pushIndex < multMatrixIndex, "Push before MultMatrix");
assertTrue(multMatrixIndex < drawRangeIndex, "MultMatrix before DrawRange");
assertTrue(scaleIndex == -1, "Scale gets prebaked");
assertTrue(drawRangeIndex < popIndex, "DrawRange before Pop");
}
@@ -800,6 +797,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glNewList(testList, mode);
GLStateManager.glScalef(0.5f, 0.5f, 1.0f);
GLStateManager.glTranslatef(2, 2, 2);
// Barrier commands between scale and draw
GLStateManager.glDepthMask(true);
GLStateManager.disableLighting();
@@ -824,8 +822,8 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertEquals(1, counts.getOrDefault(GLCommand.DRAW_RANGE, 0), "1 DrawRange for mode " + mode);
IntList opcodes = compiled.getCommandOpcodes();
assertTrue(opcodes.indexOf(GLCommand.MULT_MATRIX) < opcodes.indexOf(GLCommand.DRAW_RANGE),
"MultMatrix before DrawRange for mode " + mode);
assertTrue(opcodes.indexOf(GLCommand.MULT_MATRIX) > opcodes.indexOf(GLCommand.DRAW_RANGE),
"MultMatrix after DrawRange for mode " + mode);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
@@ -874,10 +872,10 @@ class GLSM_DisplayList_TransformCollapsing_Test {
Int2IntMap counts = compiled.getCommandCounts();
IntList opcodes = compiled.getCommandOpcodes();
// Should have exactly 2 MULT_MATRIX (T1 before push, T2 after push)
// Bug would produce 3 (T1, T2, spurious T1^-1)
assertEquals(2, counts.getOrDefault(GLCommand.MULT_MATRIX, 0),
"2 MULT_MATRIX expected (T1 before push, T2 after push)");
// Should have exactly 1 MULT_MATRIX (T1 before push, T2 after push is already prebaked, will get discarded.)
// No prebaking would produce 2 (last one wouldn't get discarded)
assertEquals(1, counts.getOrDefault(GLCommand.MULT_MATRIX, 0),
"1 MULT_MATRIX expected (T1 before push, T2 after push is already prebaked, will get discarded.)");
// Verify ordering
int firstMultMatrix = opcodes.indexOf(GLCommand.MULT_MATRIX);
@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
@@ -15,6 +19,98 @@ import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(GLSMExtension.class)
public class GLSM_TextureInfoCache_UnitTest {
@Test
void testGenericCompressedUploadResolvesToSpecificFormat() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
int format = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertNotEquals(-1, format);
assertNotEquals(GL30.GL_COMPRESSED_RED, format);
assertNotEquals(GL30.GL_COMPRESSED_RG, format);
assertNotEquals(GL13.GL_COMPRESSED_RGB, format);
assertNotEquals(GL13.GL_COMPRESSED_RGBA, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB_ALPHA, format);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testNonGenericUploadCachesWithoutRoundtrip() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertFalse(info.needsInternalFormatResolve());
assertEquals(GL11.GL_RGBA8, info.getResolvedInternalFormat());
assertEquals(GL11.GL_RGBA8, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testGenericCompressedPreservesAsPassedInternalFormat() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertEquals(GL13.GL_COMPRESSED_RGBA, info.getInternalFormat());
assertTrue(info.needsInternalFormatResolve());
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testRepeatedQueryAfterResolveUsesCache() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertTrue(info.needsInternalFormatResolve());
int first = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertFalse(info.needsInternalFormatResolve());
int second = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertEquals(first, second);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testIsGenericCompressedInternalFormatHelper() {
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RED));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RG));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGBA));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB_ALPHA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA8));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(0));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(-1));
}
/**
* Texture deletion is server-side; cache must be invalidated regardless of client caching state.
*/
@@ -128,7 +128,7 @@ class FFPCombineShaderTest {
setupXaeroState();
FragmentKey fk = FragmentKey.fromState();
VertexKey vk = VertexKey.fromState(true, false, true, false);
VertexKey vk = VertexKey.fromState(true, false, true, false, fk.enabledUnitMask());
String fragSource = FragmentShaderGenerator.generate(fk);
String vertSource = VertexShaderGenerator.generate(vk);
@@ -207,7 +207,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
new AttribLayoutKey(new int[]{0}, new int[]{2}, new int[]{GL11.GL_FLOAT}, new boolean[]{false}),
GL11.GL_TRIANGLES, 4, 6, vtx, idx, new BatchedIndexedDrawCmd());
rec.recordIndexedDrawCapture(cap);
rec.writeIndexedDrawCapture(cap);
assertFalse(cap.isFreed());
rec.free();
+1
View File
@@ -74,6 +74,7 @@ dependencies {
// LWJGL3
implementation("org.lwjgl:lwjgl:${lwjglVersion}")
implementation("org.lwjgl:lwjgl-opengl:${lwjglVersion}")
compileOnly("org.lwjgl:lwjgl-sdl:${lwjglVersion}")
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives")
runtimeOnly("org.lwjgl:lwjgl-opengl:$lwjglVersion:$lwjglNatives")
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.lwjgl3;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_ERROR;
import static org.lwjgl.sdl.SDLMessageBox.SDL_ShowMessageBox;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.sdl.SDL_MessageBoxButtonData;
import org.lwjgl.sdl.SDL_MessageBoxData;
import org.lwjgl.system.MemoryStack;
@Lwjgl3Aware
public final class MissingDependencySdl {
private MissingDependencySdl() {}
public static void showFatal(String title, String message) {
MainThreadExec.runOnMainThread(() -> showFatalOnMainThread(title, message));
}
private static void showFatalOnMainThread(String title, String message) {
try (MemoryStack stack = MemoryStack.stackPush()) {
final SDL_MessageBoxData box = SDL_MessageBoxData.calloc(stack);
box.flags(SDL_MESSAGEBOX_ERROR);
box.title(stack.UTF8(title));
box.message(stack.UTF8(message));
final SDL_MessageBoxButtonData.Buffer buttons = SDL_MessageBoxButtonData.calloc(1, stack);
buttons.get(0).set(
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
0,
stack.UTF8("OK"));
box.buttons(buttons);
SDL_ShowMessageBox(box, null);
}
}
}
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.api;
import net.minecraft.item.ItemStack;
/**
* To be used for modded items that don't have blocks. Baubles for example.
*/
public interface IDynamicLightProducer {
int getLuminance();
int getLuminance(ItemStack stack);
}
@@ -4,7 +4,14 @@ import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_PAYLOAD;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
@@ -213,12 +220,14 @@ public class BatchingFontRenderer {
pushQuadIdx();
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz, boolean flipV) {
ensureCapacity();
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
float vTop = flipV ? vStart + vSz : vStart;
float vBot = flipV ? vStart : vStart + vSz;
pushVtx(x + itOff, y, rgba, uStart, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
pushQuadIdx();
}
@@ -442,6 +451,55 @@ public class BatchingFontRenderer {
return (what >= fromInclusive) && (what <= toInclusive);
}
/**
* Count visible characters from {@code start} to {@code end}, skipping format codes.
* Stops at §r, any color code (§0-f, §x), or any color effect (§q, §g).
* Style codes (§k-o) and non-terminating effects (§z wave, §v dinnerbone) are skipped.
*/
private static int countVisibleChars(CharSequence str, int start, int end) {
int count = 0;
for (int i = start; i < end; i++) {
char ch = str.charAt(i);
if (ch == FORMATTING_CHAR && i + 1 < end) {
char code = Character.toLowerCase(str.charAt(i + 1));
if (code == 'r' || (code >= '0' && code <= '9') || (code >= 'a' && code <= 'f')
|| code == 'x' || code == 'q' || code == 'g') {
break;
}
i++;
} else {
count++;
}
}
return Math.max(count, 1);
}
private static int hsvToRgb(float hue, float sat, float val) {
int h = (int)(hue / 60f) % 6;
float f = hue / 60f - h;
float p = val * (1 - sat);
float q = val * (1 - f * sat);
float t = val * (1 - (1 - f) * sat);
float r, g, b;
switch (h) {
case 0: r=val; g=t; b=p; break;
case 1: r=q; g=val; b=p; break;
case 2: r=p; g=val; b=t; break;
case 3: r=p; g=q; b=val; break;
case 4: r=t; g=p; b=val; break;
default: r=val; g=p; b=q; break;
}
return ((int)(r*255) << 16) | ((int)(g*255) << 8) | (int)(b*255);
}
private static final int RAINBOW_LUT_SIZE = 24;
private static final int[] RAINBOW_LUT = new int[RAINBOW_LUT_SIZE];
static {
for (int i = 0; i < RAINBOW_LUT_SIZE; i++) {
RAINBOW_LUT[i] = hsvToRgb(i * 15f, 1f, 1f);
}
}
public boolean forceDefaults() {
return this.bookMode || this.isSGA || this.isSplash;
}
@@ -466,7 +524,8 @@ public class BatchingFontRenderer {
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
}
private static final char FORMATTING_CHAR = 167; // §
private static final double WAVE_TIME_SCALE = 5e-9;
private static final float WAVE_FREQUENCY = 0.5f;
public float drawString(final float anchorX, final float anchorY, final int color, final boolean enableShadow,
final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) {
@@ -497,6 +556,15 @@ public class BatchingFontRenderer {
boolean curBold = false;
boolean curStrikethrough = false;
boolean curUnderline = false;
boolean curRainbow = false;
boolean curWave = false;
boolean curDinnerbone = false;
boolean curGradient = false;
int gradientStartRgb = 0, gradientEndRgb = 0;
int gradientCharIndex = 0, gradientTotalChars = 0;
float gradientStep = 0f;
int rainbowCharIndex = 0;
int visibleCharIndex = 0;
float glyphScaleY = getGlyphScaleY();
float glyphScaleX = getGlyphScaleX();
@@ -534,6 +602,16 @@ public class BatchingFontRenderer {
strikethroughStartX = strikethroughEndX;
}
if (fmtCode == 'x' && AngelicaConfig.enableRGBColors && charIdx + SECTION_X_PAYLOAD < stringEnd) {
int rgb = ColorCodeUtils.parseHexPairs(string, charIdx + 1, 6);
if (rgb != -1) {
curRainbow = false;
curGradient = false;
curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF);
curShadowColor = (curShadowColor & 0xFF000000) | ((rgb & 0xFCFCFC) >> 2);
charIdx += SECTION_X_PAYLOAD;
}
} else {
final boolean is09 = charInRange(fmtCode, '0', '9');
final boolean isAF = charInRange(fmtCode, 'a', 'f');
if (is09 || isAF) {
@@ -542,6 +620,9 @@ public class BatchingFontRenderer {
curStrikethrough = false;
curUnderline = false;
curItalic = false;
curRainbow = false;
curGradient = false;
// wave/dinnerbone NOT reset — they're positional effects, independent of color
final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10);
final int rgb = this.colorCode[colorIdx];
@@ -562,27 +643,51 @@ public class BatchingFontRenderer {
underlineEndX = underlineStartX;
} else if (fmtCode == 'o') {
curItalic = true;
} else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) {
curRainbow = true;
curGradient = false;
rainbowCharIndex = 0;
} else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) {
curWave = !curWave;
} else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) {
curDinnerbone = !curDinnerbone;
} else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) {
int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1);
int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH);
if (color1 != -1 && color2 != -1) {
curGradient = true;
curRainbow = false;
gradientStartRgb = color1;
gradientEndRgb = color2;
gradientCharIndex = 0;
gradientTotalChars = countVisibleChars(string, charIdx + 1 + GRADIENT_PAYLOAD, stringEnd);
gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f;
charIdx += GRADIENT_PAYLOAD;
}
} else if (fmtCode == 'r') {
curRandom = false;
curBold = false;
curStrikethrough = false;
curUnderline = false;
curItalic = false;
curRainbow = false;
curWave = false;
curDinnerbone = false;
curGradient = false;
curColor = color;
curShadowColor = shadowColor;
}
} // close else block for non-§x codes
continue;
}
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
if (chrReplacement != null) {
char replacement = chrReplacement.charAt(0);
boolean isReplacementCharAvailable =
FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement);
if (isReplacementCharAvailable) {
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
if (replacement != 0) {
if (FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement)
) {
chr = replacement;
}
}
@@ -597,12 +702,31 @@ public class BatchingFontRenderer {
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
visibleCharIndex++;
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
continue;
}
if (curRainbow) {
int rgbEffect = RAINBOW_LUT[rainbowCharIndex % RAINBOW_LUT_SIZE];
curColor = (curColor & 0xFF000000) | rgbEffect;
curShadowColor = (curShadowColor & 0xFF000000) | ((rgbEffect & 0xFCFCFC) >> 2);
rainbowCharIndex++;
}
if (curGradient && gradientTotalChars > 0) {
float t = Math.min(gradientCharIndex * gradientStep, 1f);
int gr = (int)((gradientStartRgb >> 16 & 0xFF) * (1-t) + (gradientEndRgb >> 16 & 0xFF) * t);
int gg = (int)((gradientStartRgb >> 8 & 0xFF) * (1-t) + (gradientEndRgb >> 8 & 0xFF) * t);
int gb = (int)((gradientStartRgb & 0xFF) * (1-t) + (gradientEndRgb & 0xFF) * t);
int rgbEffect = (gr << 16) | (gg << 8) | gb;
curColor = (curColor & 0xFF000000) | rgbEffect;
curShadowColor = (curShadowColor & 0xFF000000) | ((rgbEffect & 0xFCFCFC) >> 2);
gradientCharIndex++;
}
final float uStart = fontProvider.getUStart(chr);
final float vStart = fontProvider.getVStart(chr);
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
@@ -616,23 +740,30 @@ public class BatchingFontRenderer {
final ResourceLocation texture = fontProvider.getTexture(chr);
final int idxId = idxWriterIndex;
// Wave: Y offset via sine wave
float renderY = heightNorth;
if (curWave) {
float time = HUDCaching.renderingCacheOverride ? 0f : (float)((System.nanoTime() & 0xFFFFFFFFFFFFL) * WAVE_TIME_SCALE);
renderY += (float) Math.sin(visibleCharIndex * WAVE_FREQUENCY + time) * AngelicaConfig.waveAmplitude;
}
if (enableShadow) {
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
pushTexRect(curX + shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
pushTexRect(curX + 2.0f * shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
}
}
}
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
pushTexRect(curX, renderY, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz, curDinnerbone);
if (curBold) {
for (int n = 1; n <= boldCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
pushTexRect(curX + shadowOffsetPart, renderY, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz, curDinnerbone);
}
}
@@ -653,6 +784,19 @@ public class BatchingFontRenderer {
if (bookMode) { curX = (int) curX; }
underlineEndX = curX;
strikethroughEndX = curX;
if ((curRainbow || curGradient) && curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
underlineStartX = underlineEndX;
}
if ((curRainbow || curGradient) && curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int stIdx = idxWriterIndex;
pushUntexRect(strikethroughStartX, strikethroughY, strikethroughEndX - strikethroughStartX, glyphScaleY, curColor);
pushDrawCmd(stIdx, 6, null, false);
strikethroughStartX = strikethroughEndX;
}
}
if (curUnderline && underlineStartX != underlineEndX) {
@@ -0,0 +1,144 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
/**
* Utility for converting {@code &}-based shorthand into {@code §}-based
* format codes understood by the batched font renderer.
* <p>
* {@code &#RRGGBB} -> {@code §x§R§R§G§G§B§B} (RGB color)<br>
* {@code &g&#RRGGBB&#RRGGBB} -> {@code §g§x§R§R§G§G§B§B§x§R§R§G§G§B§B} (gradient)<br>
* {@code &c}, {@code &l}, {@code &r}, {@code &q}, etc. -> {@code §c}, {@code §l}, {@code §r}, {@code §q}
* <p>
* Effect codes use low-collision letters to avoid false positives on natural text:
* rainbow = {@code &q}, wave = {@code &z}, dinnerbone = {@code &v}, gradient = {@code &g} (only with &#-prefixed colors).
*/
public final class ColorCodeUtils {
private ColorCodeUtils() {}
public static final char FORMATTING_CHAR = '§';
public static final int SECTION_X_PAYLOAD = 12;
public static final int SECTION_X_LENGTH = 14;
public static final int GRADIENT_PAYLOAD = 28;
public static final int GRADIENT_LENGTH = 30;
/** Valid single {@code &} codes. {@code g} is excluded; {@code &g} only converts as part of a gradient. */
public static final String VALID_SINGLE_CODES = "0123456789abcdefklmnorqzv";
private static final String[] SECTION_PREFIX = new String[128];
static {
for (int i = 0; i < VALID_SINGLE_CODES.length(); i++) {
char c = VALID_SINGLE_CODES.charAt(i);
SECTION_PREFIX[c] = String.valueOf(FORMATTING_CHAR) + c;
}
SECTION_PREFIX['x'] = String.valueOf(FORMATTING_CHAR) + 'x';
SECTION_PREFIX['g'] = String.valueOf(FORMATTING_CHAR) + 'g';
}
public static String sectionPrefix(char code) {
char c = (code < 128) ? Character.toLowerCase(code) : code;
return (c < 128) ? SECTION_PREFIX[c] : null;
}
public static int parseHexPairs(CharSequence str, int start, int count) {
int result = 0;
for (int i = 0; i < count; i++) {
int p = start + i * 2;
if (str.charAt(p) != FORMATTING_CHAR) return -1;
int digit = Character.digit(str.charAt(p + 1), 16);
if (digit == -1) return -1;
result = (result << 4) | digit;
}
return result;
}
public static int parseSectionXAt(CharSequence str, int start) {
if (start + SECTION_X_LENGTH > str.length()) return -1;
if (str.charAt(start) != FORMATTING_CHAR) return -1;
if (Character.toLowerCase(str.charAt(start + 1)) != 'x') return -1;
return parseHexPairs(str, start + 2, 6);
}
public static boolean isValidSectionX(CharSequence str, int start) {
return parseSectionXAt(str, start) != -1;
}
private static String lastConversionInput;
private static String lastConversionOutput;
public static String convertAmpersandToSectionX(String text) {
if (text == null || !AngelicaConfig.enableAmpersandConversion) return text;
if (text == lastConversionInput) return lastConversionOutput;
final String out = convertImpl(text);
lastConversionInput = text;
lastConversionOutput = out;
return out;
}
private static String convertImpl(String text) {
int idx = text.indexOf('&');
if (idx == -1) return text;
final int len = text.length();
StringBuilder sb = null;
int last = 0;
while (idx != -1 && idx + 1 < len) {
if (text.charAt(idx + 1) == '#' && idx + 7 < len) {
boolean validHex = true;
for (int i = 2; i <= 7; i++) {
if (Character.digit(text.charAt(idx + i), 16) == -1) {
validHex = false;
break;
}
}
if (validHex) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append('x');
for (int i = 2; i <= 7; i++) {
sb.append(FORMATTING_CHAR).append(text.charAt(idx + i));
}
last = idx + 8;
idx = text.indexOf('&', last);
continue;
}
}
if (Character.toLowerCase(text.charAt(idx + 1)) == 'g' && idx + 17 < len
&& text.charAt(idx + 2) == '&' && text.charAt(idx + 3) == '#'
&& text.charAt(idx + 10) == '&' && text.charAt(idx + 11) == '#') {
boolean valid1 = true, valid2 = true;
for (int i = 4; i <= 9; i++) {
if (Character.digit(text.charAt(idx + i), 16) == -1) { valid1 = false; break; }
}
if (valid1) {
for (int i = 12; i <= 17; i++) {
if (Character.digit(text.charAt(idx + i), 16) == -1) { valid2 = false; break; }
}
}
if (valid1 && valid2) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append('g');
last = idx + 2;
idx = text.indexOf('&', last);
continue;
}
}
char code = Character.toLowerCase(text.charAt(idx + 1));
if (VALID_SINGLE_CODES.indexOf(code) != -1) {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx);
sb.append(FORMATTING_CHAR).append(text.charAt(idx + 1));
last = idx + 2;
}
idx = text.indexOf('&', idx + 1);
}
if (sb == null) return text;
sb.append(text, last, len);
return sb.toString();
}
}
@@ -104,9 +104,7 @@ public class DeferredDrawBatcher {
subEnd++;
// Don't try to merge strips, fans, loops or polygons
if (drawMode == GL11.GL_TRIANGLE_STRIP || drawMode == GL11.GL_TRIANGLE_FAN || drawMode == GL11.GL_LINE_STRIP ||
drawMode == GL11.GL_LINE_LOOP || drawMode == GL11.GL_QUAD_STRIP || drawMode == GL11.GL_POLYGON)
break;
if (GLStateManager.isContinuousDraw(drawMode)) break;
}
drawPackedBatch(batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.compat;
import java.util.function.Function;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
public class GTNHLibCompat {
public static final boolean HAS_TEXT_PREPROCESSOR;
static {
boolean found = false;
try {
FontRendering.class.getMethod("setTextPreprocessor", Function.class);
found = true;
} catch (NoSuchMethodException ignored) {}
HAS_TEXT_PREPROCESSOR = found;
}
/** Only call when {@link #HAS_TEXT_PREPROCESSOR} is true. */
public static void registerPreprocessor() {
FontRendering.setTextPreprocessor(new FontRendering.TextPreprocessor() {
@Override
public String apply(String text) {
return AngelicaConfig.enableAmpersandConversion
? ColorCodeUtils.convertAmpersandToSectionX(text)
: text;
}
@Override
public boolean handlesAmpCodes() {
return AngelicaConfig.enableAmpersandConversion;
}
});
}
}
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
import cpw.mods.fml.common.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import vfyjxf.bettercrashes.utils.StateManager;
@Optional.Interface(modid = "angelica", iface = "vfyjxf.bettercrashes.utils.StateManager.IResettable")
@@ -25,6 +26,7 @@ public class BetterCrashesCompat implements StateManager.IResettable {
@Override
public void resetState() {
Tessellator.instance.isDrawing = false;
AngelicaMod.LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
AngelicaMod.LOGGER.info("Resetting GLStateManager");
@@ -112,6 +112,35 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableFontRenderer;
@Config.Comment("Enable full RGB color support (16.7M colors) using &#RRGGBB syntax in text")
@Config.DefaultBoolean(true)
public static boolean enableRGBColors;
@Config.Comment("Enable gradient text (&g&#start&#end)")
@Config.DefaultBoolean(true)
public static boolean enableGradients;
@Config.Comment("Enable rainbow cycling text (&q)")
@Config.DefaultBoolean(true)
public static boolean enableRainbow;
@Config.Comment("Enable wave/bounce animated text (&z)")
@Config.DefaultBoolean(true)
public static boolean enableWaveText;
@Config.Comment("Enable upside-down text (&v)")
@Config.DefaultBoolean(true)
public static boolean enableDinnerboneText;
@Config.Comment("Wave text amplitude (how far characters bounce)")
@Config.DefaultFloat(2.0f)
@Config.RangeFloat(min = 1.0f, max = 8.0f)
public static float waveAmplitude;
@Config.Comment("Convert &-prefix format codes (&#RRGGBB, &c, &l, etc.) at render time")
@Config.DefaultBoolean(true)
public static boolean enableAmpersandConversion;
@Config.Comment("Enable Dynamic Lights")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -573,13 +573,13 @@ public class DynamicLights {
*/
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
final Item item = stack.getItem();
if (item instanceof ItemBlock itemBlock) {
if (item instanceof IDynamicLightProducer lightProducer) {
return lightProducer.getLuminance(stack);
} else if (item instanceof ItemBlock itemBlock) {
final Block block = itemBlock.field_150939_a;
if (block != null) {
return block.getLightValue();
}
} else if (item instanceof IDynamicLightProducer lightProducer){
return lightProducer.getLuminance();
}
if (item == Items.lava_bucket) return Blocks.lava.getLightValue();
@@ -76,7 +76,7 @@ public class HUDCaching {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
if (!AngelicaConfig.hudCachingActive) {
if (!AngelicaConfig.hudCachingActive || mc.displayWidth < 16 || mc.displayHeight < 16) {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
return;
}
@@ -11,9 +11,11 @@ import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.angelica.loading.fml.compat.CompatHandlers;
import com.gtnewhorizons.angelica.lwjgl3.MissingDependencySdl;
import com.gtnewhorizons.angelica.mixins.Mixins;
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import jss.notfine.asm.AsmTransformers;
import jss.notfine.asm.mappings.Namer;
@@ -29,6 +31,8 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
import org.spongepowered.asm.launch.GlobalProperties;
import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper;
import javax.swing.JOptionPane;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -82,7 +86,9 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
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");
final String transformer = "com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer";
FMLRelaunchLog.finer("Registering transformer %s", transformer);
Launch.classLoader.registerTransformer(transformer);
}
}
}
@@ -101,14 +107,37 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
}
private static void verifyDependencies() {
DependencyVerifier.verify(AngelicaTweaker.class, List.of(
new DependencyVerifier.Check(
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.9.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
));
try {
DependencyVerifier.verify(AngelicaTweaker.class, List.of(
new DependencyVerifier.Check(
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
));
} catch (RuntimeException ex) {
fatalDependencyError(ex.getMessage());
}
}
private static void fatalDependencyError(String message) {
final String title = "Angelica - Missing Dependency";
System.err.println("FATAL: " + message);
try {
LOGGER.fatal(message);
} catch (Throwable ignored) {}
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
try {
if (lwjgl3ifyMajor >= 3) {
MissingDependencySdl.showFatal(title, message);
} else if (!GraphicsEnvironment.isHeadless()) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
} catch (Throwable ignored) {}
Runtime.getRuntime().halt(1);
}
@Override
@@ -120,6 +149,10 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
final List<String> notFineTransformers = Arrays.asList(ITransformers.getTransformers(AsmTransformers.class));
if (!notFineTransformers.isEmpty()) Namer.initNames();
transformers.addAll(notFineTransformers);
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (!rfbLoaded) {
transformers.add("com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseTransformer");
}
transformerClasses = transformers.toArray(new String[0]);
}
return transformerClasses;
@@ -8,7 +8,6 @@ import com.gtnewhorizons.angelica.loading.fml.compat.handlers.ThaumcraftCompatHa
import com.gtnewhorizons.angelica.loading.fml.compat.handlers.ThaumicHorizonsCompatHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@@ -45,20 +44,18 @@ public enum CompatHandlers {
/**
* Returns extra transformers as well as the main transformer.
* Returns an empty list if no handlers registered.
*/
public static List<String> getTransformers() {
final List<ICompatHandler> handlers = getHandlers();
if (handlers.isEmpty()) {
return Collections.emptyList();
}
List<String> transformers = new ArrayList<>();
List<String> transformers = new ArrayList<>();
for (ICompatHandler handler : handlers) {
if (handler.extraTransformers() != null) {
transformers.addAll(handler.extraTransformers());
}
}
transformers.add("com.gtnewhorizons.angelica.loading.fml.transformers.GenericCompatTransformer");
if (!handlers.isEmpty()) {
transformers.add("com.gtnewhorizons.angelica.loading.fml.transformers.GenericCompatTransformer");
}
return transformers;
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.loading.fml.transformers;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
import net.minecraft.launchwrapper.IClassTransformer;
@@ -8,6 +8,7 @@ import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
/** IClassTransformer wrapper for {@link CeleritasBlockTransform} */
public class CeleritasBlockTransformer implements IClassTransformer {
private final CeleritasBlockTransform inner;
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.loading.fml.transformers;
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
import com.gtnewhorizons.angelica.loading.shared.transformers.IsbrhTessellatorAbuseTransform;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
/** IClassTransformer wrapper for {@link IsbrhTessellatorAbuseTransform} */
public class IsbrhTessellatorAbuseTransformer implements IClassTransformer {
private final IsbrhTessellatorAbuseTransform inner = new IsbrhTessellatorAbuseTransform();
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (!inner.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (!inner.transformClassNode(cn, AngelicaClientTweaker.isObfEnv())) return basicClass;
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
AngelicaClassDump.dumpClass(transformedName, basicClass, bytes, this);
return bytes;
}
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.loading.rfb;
import com.gtnewhorizons.angelica.loading.rfb.transformers.RFBAngelicaRedirector;
import com.gtnewhorizons.angelica.loading.rfb.transformers.RFBCeleritasBlockTransformer;
import com.gtnewhorizons.angelica.loading.rfb.transformers.RFBIsbrhTessellatorAbuseTransformer;
import com.gtnewhorizons.retrofuturabootstrap.api.PluginContext;
import com.gtnewhorizons.retrofuturabootstrap.api.RetroFuturaBootstrap;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
@@ -28,7 +29,8 @@ public class AngelicaRfbPlugin implements RfbPlugin {
final boolean isObf = RetroFuturaBootstrap.API.launchClassLoader().findClassMetadata("net.minecraft.world.World") == null;
return new RfbClassTransformer[] {
new RFBAngelicaRedirector(),
new RFBCeleritasBlockTransformer(isObf)
new RFBCeleritasBlockTransformer(isObf),
new RFBIsbrhTessellatorAbuseTransformer(isObf)
};
}
}
@@ -0,0 +1,59 @@
package com.gtnewhorizons.angelica.loading.rfb.transformers;
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
import com.gtnewhorizons.angelica.loading.shared.transformers.IsbrhTessellatorAbuseTransform;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.jar.Manifest;
/** RfbClassTransformer wrapper for {@link IsbrhTessellatorAbuseTransform} */
public class RFBIsbrhTessellatorAbuseTransformer implements RfbClassTransformer {
private final IsbrhTessellatorAbuseTransform inner;
private final boolean isObf;
public RFBIsbrhTessellatorAbuseTransformer(boolean isObf) {
this.inner = new IsbrhTessellatorAbuseTransform();
this.isObf = isObf;
}
@Pattern("[a-z0-9-]+")
@Override
public @NotNull String id() {
return "isbrh-tessellator-abuse";
}
@Override
public @NotNull String @Nullable [] sortAfter() {
return new String[] {"*", "mixin:mixin"};
}
@Override
public @NotNull String @Nullable [] sortBefore() {
return new String[] {"lwjgl3ify:redirect"};
}
@Override
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
if (!classNode.isPresent()) return false;
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
return metadata != null && metadata.binaryInterfaceNames.contains(IsbrhTessellatorAbuseTransform.ISBRH);
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
if (inner.transformClassNode(classNode.getNode(), this.isObf)) {
classNode.computeMaxs();
AngelicaClassDump.dumpRFBClass(className, classNode, this);
}
}
}
@@ -0,0 +1,73 @@
package com.gtnewhorizons.angelica.loading.shared.transformers;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public final class IsbrhTessellatorAbuseTransform {
public static final String ISBRH = "cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler";
private static final Logger LOGGER = LogManager.getLogger("IsbrhTessellatorAbuse");
private static final ClassConstantPoolParser CST_POOL_PARSER = new ClassConstantPoolParser(ISBRH);
private static final String TESSELLATOR = "net/minecraft/client/renderer/Tessellator";
private static final String RENDER_WORLD_BLOCK_DESC = "(Lnet/minecraft/world/IBlockAccess;IIILnet/minecraft/block/Block;ILnet/minecraft/client/renderer/RenderBlocks;)Z";
public boolean shouldTransform(byte[] classBytes) {
return CST_POOL_PARSER.find(classBytes);
}
public boolean transformClassNode(ClassNode cn, boolean isObf) {
MethodNode target = null;
for (int i = 0, n = cn.methods.size(); i < n; i++) {
final MethodNode mn = cn.methods.get(i);
if ("renderWorldBlock".equals(mn.name) && RENDER_WORLD_BLOCK_DESC.equals(mn.desc)) {
target = mn;
break;
}
}
if (target == null) return false;
final String draw = isObf ? "func_78381_a" : "draw";
final String startDrawingQuads = isObf ? "func_78382_b" : "startDrawingQuads";
final String startDrawing = isObf ? "func_78371_b" : "startDrawing";
boolean changed = false;
AbstractInsnNode insn = target.instructions.getFirst();
while (insn != null) {
final AbstractInsnNode next = insn.getNext();
if (insn instanceof MethodInsnNode min && min.getOpcode() == Opcodes.INVOKEVIRTUAL && TESSELLATOR.equals(min.owner)) {
final int popOp;
final boolean pushIntResult;
if (min.name.equals(draw) && "()I".equals(min.desc)) {
popOp = Opcodes.POP;
pushIntResult = true;
} else if (min.name.equals(startDrawingQuads) && "()V".equals(min.desc)) {
popOp = Opcodes.POP;
pushIntResult = false;
} else if (min.name.equals(startDrawing) && "(I)V".equals(min.desc)) {
popOp = Opcodes.POP2;
pushIntResult = false;
} else {
insn = next;
continue;
}
target.instructions.insertBefore(insn, new InsnNode(popOp));
if (pushIntResult) target.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_0));
target.instructions.remove(insn);
LOGGER.info("Stripped Tessellator.{} from {}#renderWorldBlock", min.name, cn.name);
changed = true;
}
insn = next;
}
return changed;
}
}
@@ -35,15 +35,19 @@ public enum Mixins implements IMixins {
, "angelica.MixinGameSettings"
, "angelica.MixinMinecraft"
, "angelica.MixinMinecraft_FrameHook"
, "angelica.MixinMinecraft_IconifyGuard"
, "angelica.MixinMinecraftServer"
, "angelica.bugfixes.MixinItemRenderer_EdgeDepth"
, "angelica.bugfixes.MixinModelCreeper_AuraBodyInflate"
, "angelica.bugfixes.MixinModelSkeleton_LegPelvisZFight"
, "angelica.bugfixes.MixinModelWither_ArmorCentering"
, "angelica.bugfixes.MixinRenderBlocks_CrossedSquaresNormal"
, "angelica.bugfixes.MixinRenderCreeper_AuraDepth"
, "angelica.bugfixes.MixinRenderGlobal_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
, "angelica.bugfixes.MixinRenderMooshroom_MushroomTint"
, "angelica.bugfixes.MixinRendererLivingEntity_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
, "angelica.bugfixes.MixinRenderWither_ArmorCentering"
, "angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
, "angelica.debug.MixinMinecraft_FPSCap"
@@ -70,6 +74,12 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.gui.MixinGuiMainMenu")
),
ANGELICA_GL_SPLASH_TEXT(
new MixinBuilder("Rewrite 'OpenGL 1.2!' splash to reflect the actual GL context")
.setPhase(Phase.EARLY)
.addClientMixins("angelica.gui.MixinGuiMainMenuSplash")
),
ANGELICA_FONT_RENDERER(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableFontRenderer)
@@ -122,7 +132,10 @@ public enum Mixins implements IMixins {
ANGELICA_ITEM_RENDERER_OPTIMIZATION(new MixinBuilder("Optimizes in-world item rendering")
.setPhase(Phase.EARLY)
.addClientMixins("angelica.itemrenderer.MixinItemRenderer")
.addClientMixins(
"angelica.itemrenderer.MixinItemRenderer",
"angelica.itemrenderer.MixinRenderBlocks"
)
.setApplyIf(() -> AngelicaConfig.optimizeInWorldItemRendering)),
ANGELICA_OPTIMIZE_GLALLOCATION(new MixinBuilder("Replace HashMap with fastutil Int2IntMap in GLAllocation")
@@ -194,6 +207,7 @@ public enum Mixins implements IMixins {
, "rendering.MixinTileEntity"
, "rendering.MixinTileEntityMobSpawner"
, "rendering.MixinTileEntityRendererDispatcher"
, "rendering.MixinRenderBlocksEmissive"
)
),
@@ -263,6 +277,8 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderNameTag"
, "shaders.MixinRenderPlayerArmor"
, "shaders.MixinTileEntityBeaconRenderer"
, "shaders.MixinWorldNbtCache"
, "shaders.MixinNetHandlerPlayClient_NbtRemesh"
, "shaders.MixinRenderEndPortal"
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
@@ -388,7 +404,7 @@ public enum Mixins implements IMixins {
.addRequiredMod(TargetedMod.MINEFACTORY_RELOADED)
.setApplyIf(() -> CompatConfig.fixMinefactoryReloaded)
.addClientMixins("client.minefactoryreloaded.MixinRedNetCableRenderer")),
NTM_SPACE_COMPAT(new MixinBuilder("Multiple fixes for NTM:Space")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.NTM_SPACE)
@@ -22,12 +22,15 @@ import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.render.CloudRenderer;
import com.gtnewhorizons.angelica.render.EmissiveTextureAutoloader;
import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.DefaultChunkTaskProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedChunkTaskProvider;
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import com.gtnewhorizons.angelica.zoom.Zoom;
@@ -102,6 +105,7 @@ public final class ClientProxy extends CommonProxy {
super.preInit(event);
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(new EmissiveTextureAutoloader());
ModelRegistry.registerModid(MOD_ID);
blockError = new BlockError();
}
@@ -143,6 +147,10 @@ public final class ClientProxy extends CommonProxy {
if (AngelicaConfig.enableDynamicLights) {
EntityLightConfig.init(new java.io.File(mc.mcDataDir, "config"));
}
if(AngelicaConfig.optimizeInWorldItemRendering){
ItemRenderListManager.registerReloadListener();
BlockRenderListManager.registerReloadListener();
}
// Register debug commands in dev environment only
if (!AngelicaClientTweaker.isObfEnv()) {
@@ -0,0 +1,60 @@
package com.gtnewhorizons.angelica.render;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SideOnly(Side.CLIENT)
public class EmissiveTextureAutoloader {
@SubscribeEvent
public void onTextureStitchPre(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
TextureMap map = event.map;
EmissiveTextureHelper.clearCache();
@SuppressWarnings("unchecked")
Map<String, ?> registeredSprites = map.mapRegisteredSprites;
List<String> toRegister = new ArrayList<>();
for (String baseName : registeredSprites.keySet()) {
if (baseName.endsWith("_emissive")) continue;
String emissiveName = baseName + "_emissive";
if (resourceExists(emissiveName)) {
toRegister.add(baseName);
}
}
for (String baseName : toRegister) {
String emissiveName = baseName + "_emissive";
IIcon emissiveIcon = map.registerIcon(emissiveName);
EmissiveTextureHelper.cacheEmissive(baseName, emissiveIcon);
}
}
private boolean resourceExists(String iconName) {
ResourceLocation loc = new ResourceLocation(iconName);
ResourceLocation full = new ResourceLocation(
loc.getResourceDomain(),
"textures/blocks/" + loc.getResourcePath() + ".png"
);
try {
Minecraft.getMinecraft().getResourceManager().getResource(full);
return true;
} catch (Exception ignored) {
return false;
}
}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.render;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.util.IIcon;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.concurrent.ConcurrentHashMap;
@SideOnly(Side.CLIENT)
public final class EmissiveTextureHelper {
private static final ConcurrentHashMap<String, IIcon> EMISSIVE_ICON_CACHE = new ConcurrentHashMap<>();
public static IIcon getEmissiveIcon(Block block, int side, int metadata) {
IIcon baseIcon = block.getIcon(side, metadata);
if (baseIcon == null) return null;
String baseIconName = baseIcon.getIconName();
if (baseIconName == null) return null;
return EMISSIVE_ICON_CACHE.get(baseIconName);
}
public static void clearCache() {
EMISSIVE_ICON_CACHE.clear();
}
public static void cacheEmissive(String baseIconName, IIcon emissiveIcon) {
EMISSIVE_ICON_CACHE.put(baseIconName, emissiveIcon);
}
public static void renderEmissiveBlockOverlay(RenderBlocks renderer, Block block, int x, int y, int z, int metadata) {
Tessellator tess = Tessellator.instance;
for (int side = 0; side < 6; side++) {
IIcon icon = getEmissiveIcon(block, side, metadata);
if (icon == null) continue;
tess.setColorOpaque_F(1f, 1f, 1f);
switch (side) {
case 0 -> renderer.renderFaceYNeg(block, x, y, z, icon);
case 1 -> renderer.renderFaceYPos(block, x, y, z, icon);
case 2 -> renderer.renderFaceZNeg(block, x, y, z, icon);
case 3 -> renderer.renderFaceZPos(block, x, y, z, icon);
case 4 -> renderer.renderFaceXNeg(block, x, y, z, icon);
case 5 -> renderer.renderFaceXPos(block, x, y, z, icon);
}
}
}
}
@@ -12,8 +12,10 @@ import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
@@ -118,6 +120,10 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final Map<Block, BlockRenderLayer> blockTypeIds = provider != null ? provider.getBlockTypeIds() : null;
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
final NbtConditionalIdMap<Block> teMap = BlockRenderingSettings.INSTANCE.getBlockNbtMap();
final net.minecraft.world.World mcWorld = Minecraft.getMinecraft().theWorld;
final long currentTick = mcWorld != null ? mcWorld.getTotalWorldTime() : 0L;
final boolean threaded = isThreaded();
final List<DeferredBlock> deferredBlocks = threaded ? new ArrayList<>() : null;
@@ -192,7 +198,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
continue;
}
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride);
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride, teMap, currentTick);
}
}
@@ -205,7 +211,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
// Process deferred blocks on main thread if any
if (deferredBlocks != null && !deferredBlocks.isEmpty()) {
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ);
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ, teMap, currentTick);
}
final Reference2ReferenceMap<TerrainRenderPass, BuiltSectionMeshParts> meshes =
@@ -236,7 +242,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride) {
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
final var blockMaterial = block.getMaterial();
final boolean isFluid = blockMaterial == net.minecraft.block.material.Material.water || blockMaterial == net.minecraft.block.material.Material.lava;
@@ -272,6 +278,27 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
contextEncoder.prepareToRenderBlock(blockRenderContext, block, effectiveMeta,
ExtendedDataHelper.BLOCK_RENDER_TYPE, lightValue);
// Check for TileEntity NBT-conditional shader block ID override
if (teMap != null && teMap.hasConditions(block)) {
final long packedPos = BlockRenderingSettings.packBlockPos(x, y, z);
int teBlockId = BlockRenderingSettings.getCachedTeNbtId(packedPos, currentTick);
if (teBlockId == BlockRenderingSettings.CACHE_MISS) {
final TileEntity te = getBlockAccess().getTileEntity(x, y, z);
if (te != null) {
final NBTTagCompound teNbt = new NBTTagCompound();
te.writeToNBT(teNbt);
teBlockId = teMap.resolve(block, teNbt);
} else {
teBlockId = -1;
}
BlockRenderingSettings.cacheTeNbtId(packedPos, teBlockId, currentTick);
}
if (teBlockId != -1) {
blockRenderContext.blockId = (short) teBlockId;
}
}
}
}
@@ -294,7 +321,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ) {
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
@@ -308,7 +335,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
for (DeferredBlock deferred : deferredBlocks) {
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride());
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride(), teMap, currentTick);
}
} finally {
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
@@ -0,0 +1,87 @@
package com.gtnewhorizons.angelica.rendering.items;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IReloadableResourceManager;
import org.lwjgl.opengl.GL11;
public class BlockRenderListManager {
private static final BlockMeta key = new BlockMeta();
private static final Object2IntOpenHashMap<BlockMeta> displayListMap = new Object2IntOpenHashMap<>();
private static final int ISBRH_CUTOFF = 40;
private static final boolean[] notISBRH = new boolean[ISBRH_CUTOFF];
static {
for (int i : new int[]
{ 0, 31, 39, 16, 26, 1, 19, 23, 13, 6, 2, 10, 27, 11, 21, 32, 35, 34, 38 }
) {
notISBRH[i] = true;
}
}
public static boolean isISBRH(int renderType) {
return renderType >= ISBRH_CUTOFF || renderType < 0 || !notISBRH[renderType];
}
public static int getDisplayList(Block block, int meta) {
key.block = block;
key.meta = meta;
return displayListMap.getInt(key);
}
public static int startCompiling() {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
return list;
}
public static void endCompiling(int list, Block block, int meta) {
DisplayListManager.discardMatrixTransforms();
GLStateManager.glEndList();
displayListMap.put(new BlockMeta(block, meta), list);
}
public static void registerReloadListener(){
IReloadableResourceManager resourceManager = (IReloadableResourceManager) Minecraft.getMinecraft()
.getResourceManager();
resourceManager.registerReloadListener(_ -> {
for (int list : displayListMap.values()) {
GLStateManager.glDeleteLists(list, 1);
}
displayListMap.clear();
});
}
private static final class BlockMeta {
private Block block;
private int meta;
public BlockMeta() {
}
public BlockMeta(Block block, int meta) {
this.block = block;
this.meta = meta;
}
@Override
public int hashCode() {
return (block.hashCode() * 31 + meta) * 31;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BlockMeta other)) return false;
return block == other.block && meta == other.meta;
}
}
}
@@ -23,7 +23,7 @@
* along with FalseTweaks. If not, see <https://www.gnu.org/licenses/>.
*/
package com.gtnewhorizons.angelica.rendering;
package com.gtnewhorizons.angelica.rendering.items;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
@@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IReloadableResourceManager;
import org.lwjgl.opengl.GL11;
public class ItemRenderListManager {
@@ -98,6 +99,17 @@ public class ItemRenderListManager {
return Minecraft.getMinecraft().thePlayer.ticksExisted;
}
public static void registerReloadListener(){
IReloadableResourceManager resourceManager = (IReloadableResourceManager) Minecraft.getMinecraft()
.getResourceManager();
resourceManager.registerReloadListener(_ -> {
for (CachedVBO value : vboCache.values()) {
value.delete();
}
vboCache.clear();
});
}
public static final class CachedVBO {
private final IVertexArrayObject vertexBuffer;
private final IndexBuffer ebo;
@@ -158,6 +158,34 @@ final class BlockOrientation extends RenderBlockState {
return origIcon == neighbor.getIcon(blockAccess, neighborX, neighborY, neighborZ, getTextureFaceOrig());
}
@Override
public BlockOrientation copy() {
BlockOrientation copy = new BlockOrientation();
copy.x = x;
copy.y = y;
copy.z = z;
copy.metadata = metadata;
copy.altMetadata = altMetadata;
copy.metadataBits = metadataBits;
copy.renderType = renderType;
copy.blockFace = blockFace;
copy.textureFace = textureFace;
copy.textureFaceOrig = textureFaceOrig;
copy.rotateUV = rotateUV;
copy.blockAccess = blockAccess;
copy.block = block;
copy.useAO = useAO;
copy.inWorld = inWorld;
copy.matcher = matcher;
copy.offsetsComputed = offsetsComputed;
copy.haveOffsets = haveOffsets;
copy.dx = dx;
copy.dy = dy;
copy.dz = dz;
return copy;
}
void setBlock(Block block, IBlockAccess blockAccess, int x, int y, int z) {
this.block = block;
this.blockAccess = blockAccess;
@@ -15,6 +15,7 @@ import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import com.github.bsideup.jabel.Desugar;
import com.prupe.mcpatcher.MCLogger;
import com.prupe.mcpatcher.MCPatcherUtils;
import com.prupe.mcpatcher.mal.block.BlockAPI;
@@ -43,13 +44,16 @@ public class CTMUtils {
private static Overrides newOverrides = null;
private static TileLoader tileLoader;
private static final ThreadLocal<TileOverrideImpl.CTMCompact> CURRENT_COMPACT = new ThreadLocal<>();
@Desugar
public record CTMCompactContext(TileOverrideImpl.CTMCompact compact, RenderBlockState renderBlockState) {}
public static void setCurrentCompact(TileOverrideImpl.CTMCompact compact) {
CURRENT_COMPACT.set(compact);
private static final ThreadLocal<CTMCompactContext> CURRENT_COMPACT = new ThreadLocal<>();
public static void setCurrentCompact(TileOverrideImpl.CTMCompact compact, RenderBlockState renderBlockState) {
CURRENT_COMPACT.set(new CTMCompactContext(compact, renderBlockState.copy()));
}
public static TileOverrideImpl.CTMCompact getCurrentCompact() {
public static CTMCompactContext getCurrentCompact() {
return CURRENT_COMPACT.get();
}
@@ -1,70 +1,18 @@
package com.prupe.mcpatcher.ctm;
import com.github.bsideup.jabel.Desugar;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
public final class CompactCtmQuadProcessor {
private static final int LEFT = 0;
private static final int UP_LEFT = 1;
private static final int UP = 2;
private static final int UP_RIGHT = 3;
private static final int RIGHT = 4;
private static final int DOWN_RIGHT = 5;
private static final int DOWN = 6;
private static final int DOWN_LEFT = 7;
private static final int SPRITE_DEFAULT = 0;
private static final int SPRITE_ALL = 1;
private static final int SPRITE_HORIZONTAL = 2;
private static final int SPRITE_VERTICAL = 3;
private static final int SPRITE_MIXED = 4;
private static final int[] CTM_47_BY_CONNECTIONS = {
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
16,18,16,18,6,46,6,21,16,18,16,18,28,9,28,22,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
37,40,37,40,30,8,30,34,37,40,37,40,25,23,25,45,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
16,42,16,42,6,20,6,10,16,42,16,42,28,35,28,44,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
37,38,37,38,30,11,30,32,37,38,37,38,25,33,25,26
};
/**
* Face-local basis:
* right = increasing U on the face
* down = increasing V on the face
*/
private static final FaceBasis[] FACE_BASIS = {
new FaceBasis( 1, 0, 0, 0, 0, 1), // 0: Y-
new FaceBasis( 1, 0, 0, 0, 0, 1), // 1: Y+
new FaceBasis(-1, 0, 0, 0, -1, 0), // 2: Z-
new FaceBasis( 1, 0, 0, 0, -1, 0), // 3: Z+
new FaceBasis( 0, 0, 1, 0, -1, 0), // 4: X-
new FaceBasis( 0, 0, -1, 0, -1, 0) // 5: X+
};
/**
* Neighbor offsets in face-local coordinates:
* 0 = left, 1 = up-left, 2 = up, 3 = up-right,
* 4 = right, 5 = down-right, 6 = down, 7 = down-left
*/
private static final int[] RIGHT_FACTOR = {-1, -1, 0, 1, 1, 1, 0, -1};
private static final int[] DOWN_FACTOR = { 0, -1, -1, -1, 0, 1, 1, 1};
/**
* Quadrant side references:
* 0 = top-left
@@ -72,9 +20,12 @@ public final class CompactCtmQuadProcessor {
* 2 = bottom-right
* 3 = bottom-left
*/
private static final int[] QUAD_A = {LEFT, UP, RIGHT, DOWN};
private static final int[] QUAD_B = {UP, RIGHT, DOWN, LEFT};
private static final int[] QUAD_DIAG = {UP_LEFT, UP_RIGHT, DOWN_RIGHT, DOWN_LEFT};
private static final int[] QUAD_A = {RenderBlockState.REL_L, RenderBlockState.REL_U,
RenderBlockState.REL_R, RenderBlockState.REL_D};
private static final int[] QUAD_B = {RenderBlockState.REL_U, RenderBlockState.REL_R,
RenderBlockState.REL_D, RenderBlockState.REL_L};
private static final int[] QUAD_DIAG = {RenderBlockState.REL_UL, RenderBlockState.REL_UR,
RenderBlockState.REL_DR, RenderBlockState.REL_DL};
/** Maps split-face quadrant (0=TL,1=TR,2=BR,3=BL face-local) to RenderBlocks per-face TL/TR/BR/BL orientation. */
private static final int[][] RENDER_FACE_QUADRANT_MAP = {
@@ -90,35 +41,37 @@ public final class CompactCtmQuadProcessor {
private final IIcon[] sprites;
private final CompactConnectingCtmProperties props;
private final TileOverrideImpl.CTMCompact override;
CompactCtmQuadProcessor(IIcon[] sprites, CompactConnectingCtmProperties props) {
CompactCtmQuadProcessor(IIcon[] sprites, CompactConnectingCtmProperties props, TileOverrideImpl.CTMCompact compact) {
this.sprites = new IIcon[5];
if (sprites != null) {
System.arraycopy(sprites, 0, this.sprites, 0, Math.min(sprites.length, this.sprites.length));
}
this.props = props;
this.override = compact;
}
public boolean processFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon origIcon) {
int connections = CtmConnectionHelper.getConnections(world, x, y, z, face, props.getInnerSeams());
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon) {
int neighborBits = override.getNeighborBits(renderBlockState, origIcon);
int ctmIndex = CTM_47_BY_CONNECTIONS[connections & 0xFF];
int ctmIndex = TileOverride.neighborMap[neighborBits & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, world, x, y, z, face, replacement);
renderWholeFace(rb, renderBlockState, replacement);
return true;
}
if (connections == 0 || connections == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(connections)];
if (neighborBits == 0 || neighborBits == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(neighborBits)];
if (sprite != null) {
renderWholeFace(rb, world, x, y, z, face, sprite);
renderWholeFace(rb, renderBlockState, sprite);
return true;
}
return false;
}
renderSplitFace(rb, world, x, y, z, face, connections);
renderSplitFace(rb, renderBlockState, neighborBits);
return true;
}
@@ -148,19 +101,21 @@ public final class CompactCtmQuadProcessor {
return SPRITE_ALL;
}
if ((connections & ((1 << LEFT) | (1 << RIGHT))) == ((1 << LEFT) | (1 << RIGHT))) {
if ((connections & ((1 << RenderBlockState.REL_L) | (1 << RenderBlockState.REL_R))) ==
((1 << RenderBlockState.REL_L) | (1 << RenderBlockState.REL_R))) {
return SPRITE_HORIZONTAL;
}
if ((connections & ((1 << UP) | (1 << DOWN))) == ((1 << UP) | (1 << DOWN))) {
if ((connections & ((1 << RenderBlockState.REL_U) | (1 << RenderBlockState.REL_D))) ==
((1 << RenderBlockState.REL_U) | (1 << RenderBlockState.REL_D))) {
return SPRITE_VERTICAL;
}
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, int connections) {
final Block block = world.getBlock(x, y, z);
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections) {
final int face = renderBlockState.getBlockFace();
final double minX = rb.renderMinX, minY = rb.renderMinY, minZ = rb.renderMinZ;
final double maxX = rb.renderMaxX, maxY = rb.renderMaxY, maxZ = rb.renderMaxZ;
@@ -197,7 +152,7 @@ public final class CompactCtmQuadProcessor {
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, block, x, y, z, face, icon);
renderFace(rb, renderBlockState, icon);
}
if (ao) {
@@ -261,18 +216,22 @@ public final class CompactCtmQuadProcessor {
return (sky << 16) | (block & 0xFFFF);
}
private void renderWholeFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon icon) {
Block block = world.getBlock(x, y, z);
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, block, x, y, z, face, icon);
renderFace(rb, renderBlockState, icon);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, Block block, int x, int y, int z, int face, IIcon icon) {
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
int face = renderBlockState.getBlockFace();
Block block = renderBlockState.getBlock();
int x = renderBlockState.getX();
int y = renderBlockState.getY();
int z = renderBlockState.getZ();
boolean originalFlip = rb.field_152631_f;
rb.field_152631_f = (face == 2 || face == 5);
try {
@@ -360,52 +319,10 @@ public final class CompactCtmQuadProcessor {
if (connA ^ connB) {
int connectedBit = connA ? sideA : sideB;
return (connectedBit == LEFT || connectedBit == RIGHT) ? SPRITE_VERTICAL : SPRITE_HORIZONTAL;
return (connectedBit == RenderBlockState.REL_L || connectedBit == RenderBlockState.REL_R) ? SPRITE_VERTICAL
: SPRITE_HORIZONTAL;
}
return SPRITE_DEFAULT;
}
@Desugar
private record FaceBasis(int rightX, int rightY, int rightZ, int downX, int downY, int downZ) { }
private static final class CtmConnectionHelper {
public static int getConnections(IBlockAccess world, int x, int y, int z, int face, boolean innerSeams) {
Block self = world.getBlock(x, y, z);
FaceBasis basis = FACE_BASIS[face];
int bits = 0;
for (int i = 0; i < 8; i++) {
int dx = RIGHT_FACTOR[i] * basis.rightX + DOWN_FACTOR[i] * basis.downX;
int dy = RIGHT_FACTOR[i] * basis.rightY + DOWN_FACTOR[i] * basis.downY;
int dz = RIGHT_FACTOR[i] * basis.rightZ + DOWN_FACTOR[i] * basis.downZ;
int nx = x + dx;
int ny = y + dy;
int nz = z + dz;
if (world.getBlock(nx, ny, nz) != self) {
continue;
}
if (innerSeams && !shouldRenderInnerSeam(world, nx, ny, nz, face)) {
continue;
}
bits |= (1 << i);
}
return bits;
}
private static boolean shouldRenderInnerSeam(IBlockAccess world, int x, int y, int z, int face) {
int[] normal = RenderBlockState.NORMALS[face];
int sx = x + normal[0];
int sy = y + normal[1];
int sz = z + normal[2];
return world.getBlock(sx, sy, sz).shouldSideBeRendered(world, sx, sy, sz, face);
}
}
}
@@ -139,4 +139,6 @@ abstract public class RenderBlockState {
abstract public boolean shouldConnectByTile(Block neighbor, IIcon origIcon, int neighborX, int neighborY,
int neighborZ);
abstract public RenderBlockState copy();
}
@@ -48,6 +48,28 @@ public abstract class TileOverride implements Comparable<TileOverride> {
private final List<ResourceLocation> tileNames = new ArrayList<>();
protected IIcon[] icons;
// Index into this array is formed from these bit values:
// 128 64 32
// 1 * 16
// 2 4 8
protected static final int[] neighborMap = new int[] {
0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15,
1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14,
0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15,
1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14,
36, 17, 36, 17, 24, 19, 24, 43, 36, 17, 36, 17, 24, 19, 24, 43,
16, 18, 16, 18, 6, 46, 6, 21, 16, 18, 16, 18, 28, 9, 28, 22,
36, 17, 36, 17, 24, 19, 24, 43, 36, 17, 36, 17, 24, 19, 24, 43,
37, 40, 37, 40, 30, 8, 30, 34, 37, 40, 37, 40, 25, 23, 25, 45,
0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15,
1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14,
0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15,
1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14,
36, 39, 36, 39, 24, 41, 24, 27, 36, 39, 36, 39, 24, 41, 24, 27,
16, 42, 16, 42, 6, 20, 6, 10, 16, 42, 16, 42, 28, 35, 28, 44,
36, 39, 36, 39, 24, 41, 24, 27, 36, 39, 36, 39, 24, 41, 24, 27,
37, 38, 37, 38, 30, 11, 30, 32, 37, 38, 37, 38, 25, 33, 25, 26, };
static TileOverride create(ResourceLocation propertiesFile, TileLoader tileLoader) {
if (propertiesFile == null) {
return null;
@@ -353,6 +375,16 @@ public abstract class TileOverride implements Comparable<TileOverride> {
return shouldConnect(renderBlockState, icon, renderBlockState.getOffset(blockFace, relativeDirection));
}
protected final int getNeighborBits(RenderBlockState renderBlockState, IIcon origIcon) {
int neighborBits = 0;
for (int bit = 0; bit < 8; bit++) {
if (shouldConnect(renderBlockState, origIcon, bit)) {
neighborBits |= (1 << bit);
}
}
return neighborBits;
}
private boolean shouldConnect(RenderBlockState renderBlockState, IIcon icon, int[] offset) {
IBlockAccess blockAccess = renderBlockState.getBlockAccess();
Block block = renderBlockState.getBlock();
@@ -22,21 +22,6 @@ public class TileOverrideImpl {
final static class CTM extends TileOverride {
// Index into this array is formed from these bit values:
// 128 64 32
// 1 * 16
// 2 4 8
private static final int[] neighborMap = new int[] { 0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15, 1, 2,
1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14, 0, 3, 0, 3, 12, 5, 12, 15, 0, 3, 0, 3, 12, 5, 12, 15, 1, 2,
1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14, 36, 17, 36, 17, 24, 19, 24, 43, 36, 17, 36, 17, 24, 19, 24,
43, 16, 18, 16, 18, 6, 46, 6, 21, 16, 18, 16, 18, 28, 9, 28, 22, 36, 17, 36, 17, 24, 19, 24, 43, 36, 17, 36,
17, 24, 19, 24, 43, 37, 40, 37, 40, 30, 8, 30, 34, 37, 40, 37, 40, 25, 23, 25, 45, 0, 3, 0, 3, 12, 5, 12,
15, 0, 3, 0, 3, 12, 5, 12, 15, 1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14, 0, 3, 0, 3, 12, 5, 12,
15, 0, 3, 0, 3, 12, 5, 12, 15, 1, 2, 1, 2, 4, 7, 4, 29, 1, 2, 1, 2, 13, 31, 13, 14, 36, 39, 36, 39, 24, 41,
24, 27, 36, 39, 36, 39, 24, 41, 24, 27, 16, 42, 16, 42, 6, 20, 6, 10, 16, 42, 16, 42, 28, 35, 28, 44, 36,
39, 36, 39, 24, 41, 24, 27, 36, 39, 36, 39, 24, 41, 24, 27, 37, 38, 37, 38, 30, 11, 30, 32, 37, 38, 37, 38,
25, 33, 25, 26, };
CTM(PropertiesFile properties, TileLoader tileLoader) {
super(properties, tileLoader);
}
@@ -62,13 +47,7 @@ public class TileOverrideImpl {
@Override
IIcon getTileWorld_Impl(RenderBlockState renderBlockState, IIcon origIcon) {
int neighborBits = 0;
for (int bit = 0; bit < 8; bit++) {
if (shouldConnect(renderBlockState, origIcon, bit)) {
neighborBits |= (1 << bit);
}
}
return icons[neighborMap[neighborBits]];
return icons[neighborMap[getNeighborBits(renderBlockState, origIcon)]];
}
@Override
@@ -520,7 +499,7 @@ public class TileOverrideImpl {
public CompactCtmQuadProcessor getProcessor() {
CompactCtmQuadProcessor p = this.processor;
if (p == null) {
p = new CompactCtmQuadProcessor(icons, properties);
p = new CompactCtmQuadProcessor(icons, properties, this);
this.processor = p;
}
return p;
@@ -123,7 +123,7 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
IIcon newIcon = getTile(override, renderBlockState, origIcon);
if (newIcon != null) {
if (override instanceof TileOverrideImpl.CTMCompact compact) {
CTMUtils.setCurrentCompact(compact);
CTMUtils.setCurrentCompact(compact, renderBlockState);
}
lastMatchedOverride = override;
skipOverrides.add(override);
@@ -10,6 +10,7 @@ import cpw.mods.fml.relauncher.SideOnly;
import jss.notfine.gui.options.control.NotFineControlValueFormatter;
import jss.notfine.gui.options.named.AlwaysNever;
import jss.notfine.gui.options.named.BackgroundSelect;
import jss.notfine.gui.options.named.BobviewMode;
import jss.notfine.gui.options.named.DownfallQuality;
import jss.notfine.gui.options.named.FOVMode;
import jss.notfine.gui.options.named.GraphicsQualityOff;
@@ -50,6 +51,7 @@ public enum Settings {
SettingsManager.downfallDistanceUpdated();
}
},
BOBVIEW_MODE(new NotFineOptionCycling<>(BobviewMode.DEFAULT, null)),
DYNAMIC_FOV(new NotFineOptionCycling<>(FOVMode.ALL, OptionImpact.VARIES)),
HURT_SHAKE(new NotFineOptionSliderPercentage(100, 0, 300, 5, OptionImpact.LOW)),
DYNAMIC_LIGHTS(new NotFineOptionCycling<>(DynamicLightsMode.FANCY, OptionImpact.VARIES)){
@@ -0,0 +1,20 @@
package jss.notfine.gui.options.named;
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
public enum BobviewMode implements NamedState {
DEFAULT("generator.default"),
HAND("options.bobview.hand"),
CAMERA("options.bobview.camera");
private final String name;
BobviewMode(String name) {
this.name = name;
}
@Override
public String getKey() {
return this.name;
}
}
@@ -33,6 +33,9 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
private static final float ASPECT_RATIO = 5f / 4f;
private static final int MINIMUM_WIDTH = 550;
@Nullable
private Element focused;
@@ -88,10 +91,10 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
protected BasicFrame.Builder parentFrameBuilder() {
final BasicFrame.Builder basicFrameBuilder;
// Calculates if resolution exceeds 16:9 ratio, force 16:9
// Clamp width on wide screens
int newWidth = this.width;
if ((float) this.width / (float) this.height > 1.77777777778) {
newWidth = (int) (this.height * 1.77777777778);
if (this.width > MINIMUM_WIDTH && (float) this.width / (float) this.height > ASPECT_RATIO) {
newWidth = Math.max(MINIMUM_WIDTH, (int) (this.height * ASPECT_RATIO));
}
final Dim2i basicFrameDim = new Dim2i((this.width - newWidth) / 2, 0, newWidth, this.height);
@@ -143,6 +143,7 @@ public class SodiumGameOptionPages {
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.viewBobbing = value, opts -> opts.viewBobbing)
.build())
.add(Settings.BOBVIEW_MODE.option)
.add(Settings.DYNAMIC_FOV.option)
.add(Settings.HURT_SHAKE.option)
.add(Settings.MODE_WATER.option)
@@ -534,4 +535,61 @@ public class SodiumGameOptionPages {
return new OptionPage(I18n.format("sodium.options.pages.appearance"), ImmutableList.copyOf(groups));
}
public static OptionPage text() {
final List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("RGB Colors")
.setTooltip("Enable hex colors via &#RRGGBB syntax")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableRGBColors = value,
opts -> AngelicaConfig.enableRGBColors)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Gradients")
.setTooltip("Smooth color transitions via &g&#start&#end")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableGradients = value,
opts -> AngelicaConfig.enableGradients)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Rainbow")
.setTooltip("Cycling rainbow colors via &q")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableRainbow = value,
opts -> AngelicaConfig.enableRainbow)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Wave Text")
.setTooltip("Animated bouncing text via &z")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableWaveText = value,
opts -> AngelicaConfig.enableWaveText)
.build())
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
.setName("Wave Amplitude")
.setTooltip("How far wave text bounces (default 2.0)")
.setControl(option -> new SliderControl(option, 10, 80, 5, v -> (v / 10) + "." + (v % 10)))
.setBinding((opts, value) -> AngelicaConfig.waveAmplitude = value / 10f, opts -> Math.round(AngelicaConfig.waveAmplitude * 10f))
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Dinnerbone Text")
.setTooltip("Flipped upside down text via &v")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableDinnerboneText = value,
opts -> AngelicaConfig.enableDinnerboneText)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Ampersand Conversion")
.setTooltip("Convert & prefix codes to formatting codes at render time")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableAmpersandConversion = value,
opts -> AngelicaConfig.enableAmpersandConversion)
.build())
.build());
return new OptionPage("Text Effects", ImmutableList.copyOf(groups));
}
}
@@ -63,6 +63,7 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
this.pages.add(SodiumGameOptionPages.advanced());
this.pages.add(SodiumGameOptionPages.performance());
this.pages.add(SodiumGameOptionPages.appearance());
this.pages.add(SodiumGameOptionPages.text());
// Add Dynamic Lights page if enabled
if (DynamicLights.configEnabled) {
@@ -23,26 +23,33 @@ import java.util.Set;
public class BlockMaterialMapping {
public record BlockIdMaps(Reference2ObjectMap<Block, Int2IntMap> blockMetaMap, NbtConditionalIdMap<Block> tileEntityMap) {}
/** Meta-key bit OR'd in at runtime when a snowy-tagged block has snow above it. */
public static final int SNOWY_META_BIT = 0x10;
/**
* Creates a two-level map structure for block material IDs.
* Based on Iris's BlockState mapping approach adapted for 1.7.10's metadata system.
* Creates the standard block meta ID map, the TileEntity NBT-conditional map, and registers
* snowy blocks on {@link BlockRenderingSettings}.
*/
public static Reference2ObjectMap<Block, Int2IntMap> createBlockMetaIdMap(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
public static BlockIdMaps createBlockIdMaps(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
final Reference2ObjectMap<Block, Int2IntMap> blockMatches = new Reference2ObjectOpenHashMap<>();
final NbtConditionalIdMap<Block> tileEntityMap = new NbtConditionalIdMap<>();
final ReferenceSet<Block> snowyBlocks = new ReferenceOpenHashSet<>();
blockPropertiesMap.forEach((intId, entries) -> {
for (BlockEntry entry : entries) {
addBlockMetas(entry, blockMatches, intId, skipFlattening, snowyBlocks);
if (entry.hasNbtProperties()) {
addTileEntityEntry(entry, tileEntityMap, intId);
} else {
addBlockMetas(entry, blockMatches, tileEntityMap, intId, skipFlattening, snowyBlocks);
}
}
});
BlockRenderingSettings.INSTANCE.setHasSnowyEntries(!snowyBlocks.isEmpty());
BlockRenderingSettings.INSTANCE.setSnowyBlocks(snowyBlocks);
return blockMatches;
return new BlockIdMaps(blockMatches, tileEntityMap);
}
public static Map<Block, BlockRenderLayer> createBlockTypeMap(Map<NamespacedId, BlockRenderType> blockPropertiesMap) {
@@ -55,7 +62,8 @@ public class BlockMaterialMapping {
if (block == null && "minecraft".equals(id.getNamespace())) {
final List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
if (legacyEntries != null) {
block = resolveBlockOrNull(legacyEntries.getFirst().getId());
// Use the first entry for render type (render type applies per-block)
block = resolveBlockOrNull(legacyEntries.getFirst().id());
}
}
@@ -80,28 +88,64 @@ public class BlockMaterialMapping {
};
}
/**
* Creates an NBT-conditional ID map keyed by NamespacedId, for items or entities.
*/
public static NbtConditionalIdMap<NamespacedId> createNamespacedNbtMap(Int2ObjectMap<List<BlockEntry>> nbtEntries) {
NbtConditionalIdMap<NamespacedId> map = new NbtConditionalIdMap<>();
nbtEntries.forEach((intId, entries) -> {
for (BlockEntry entry : entries) {
if (entry.hasNbtProperties()) {
map.addCondition(entry.id(), entry.nbtProperties(), intId);
}
}
});
return map;
}
private static void addTileEntityEntry(BlockEntry entry, NbtConditionalIdMap<Block> teMap, int intId) {
final NamespacedId id = entry.id();
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
if (block == null || block == Blocks.air) {
return;
}
teMap.addCondition(block, entry.nbtProperties(), intId);
}
/**
* Adds block+metadata combinations to the material ID map.
* Based on Iris's addBlockStates method, adapted for 1.7.10 metadata system.
*/
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
final NamespacedId id = entry.getId();
final Map<String, String> stateProps = entry.getStateProperties();
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap,
NbtConditionalIdMap<Block> tileEntityMap, int intId,
boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
final NamespacedId id = entry.id();
final Map<String, String> stateProps = entry.stateProperties();
final String snowy = stateProps.get("snowy");
final int snowyBit = "true".equals(snowy) ? SNOWY_META_BIT : 0;
// Vanilla modern names go through FlatteningMap; legacy-section packs and modded blocks
// resolve directly from the registry.
List<BlockEntry> targets = null;
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.getMetas().isEmpty())) {
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.metas().isEmpty())) {
targets = FlatteningMap.toLegacy(id.getName(), stateProps);
}
if (targets == null) targets = List.of(entry);
for (BlockEntry target : targets) {
final Block block = resolveBlockOrNull(target.getId());
if (target.hasNbtProperties()) {
addTileEntityEntry(target, tileEntityMap, intId);
continue;
}
final Block block = resolveBlockOrNull(target.id());
if (block == null) continue;
applyMetas(block, target.getMetas(), idMap, intId, snowyBit);
applyMetas(block, target.metas(), idMap, intId, snowyBit);
if (snowy != null) snowyBlocks.add(block);
}
}
@@ -2,6 +2,7 @@ package net.coderbot.iris.block_rendering;
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
@@ -18,16 +19,76 @@ import java.util.Map;
public class BlockRenderingSettings {
public static final BlockRenderingSettings INSTANCE = new BlockRenderingSettings();
public static final int CACHE_MISS = Integer.MIN_VALUE;
private static final int NBT_CACHE_INTERVAL_TICKS = 20;
private static final int TE_NBT_CACHE_MAX = 8192;
private static final Long2LongLinkedOpenHashMap teNbtIdCache = new Long2LongLinkedOpenHashMap(256);
private static final Object teNbtIdCacheLock = new Object();
static {
teNbtIdCache.defaultReturnValue(-1L);
}
public static long packBlockPos(int x, int y, int z) {
return ((long) x & 0x3FFFFFFL) << 38 | ((long) y & 0xFFFL) << 26 | ((long) z & 0x3FFFFFFL);
}
public static void invalidateTeNbtCache(int x, int y, int z) {
final long key = packBlockPos(x, y, z);
synchronized (teNbtIdCacheLock) {
teNbtIdCache.remove(key);
}
}
public static int getCachedTeNbtId(long packedPos, long currentTick) {
final long entry;
synchronized (teNbtIdCacheLock) {
entry = teNbtIdCache.get(packedPos);
}
if (entry == -1L) {
return CACHE_MISS;
}
final long entryTick = entry >>> 32;
if (currentTick - entryTick >= NBT_CACHE_INTERVAL_TICKS) {
return CACHE_MISS;
}
return (int) entry;
}
public static void cacheTeNbtId(long packedPos, int shaderId, long currentTick) {
// Mask tick into low 31 bits so packed value never collides with the -1L sentinel.
final long packed = ((currentTick & 0x7FFFFFFFL) << 32) | (shaderId & 0xFFFFFFFFL);
synchronized (teNbtIdCacheLock) {
teNbtIdCache.put(packedPos, packed);
while (teNbtIdCache.size() > TE_NBT_CACHE_MAX) {
teNbtIdCache.removeFirstLong();
}
}
}
public static void clearTeNbtCache() {
synchronized (teNbtIdCacheLock) {
teNbtIdCache.clear();
}
}
@Getter
private boolean reloadRequired;
private Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
private NbtConditionalIdMap<Block> blockNbtMap;
private Map<Block, BlockRenderLayer> blockTypeIds;
// note: no reload needed, entities are rebuilt every frame.
@Setter
private Object2IntFunction<NamespacedId> entityIds;
@Setter
private NbtConditionalIdMap<NamespacedId> entityNbtMap;
// note: no reload needed, items are rendered every frame.
@Setter
private Object2IntFunction<NamespacedId> itemIds;
@Setter
private NbtConditionalIdMap<NamespacedId> itemNbtMap;
@Getter
private float ambientOcclusionLevel;
private boolean disableDirectionalShading;
@@ -41,6 +102,7 @@ public class BlockRenderingSettings {
public BlockRenderingSettings() {
reloadRequired = false;
blockMetaMatches = null;
blockNbtMap = null;
blockTypeIds = null;
ambientOcclusionLevel = 1.0F;
disableDirectionalShading = false;
@@ -82,11 +144,32 @@ public class BlockRenderingSettings {
return itemIds;
}
@Nullable
public NbtConditionalIdMap<Block> getBlockNbtMap() {
return blockNbtMap;
}
@Nullable
public NbtConditionalIdMap<NamespacedId> getItemNbtMap() {
return itemNbtMap;
}
@Nullable
public NbtConditionalIdMap<NamespacedId> getEntityNbtMap() {
return entityNbtMap;
}
public void setBlockMetaMatches(Reference2ObjectMap<Block, Int2IntMap> blockMetaIds) {
this.reloadRequired = true;
this.blockMetaMatches = blockMetaIds;
}
public void setBlockNbtMap(NbtConditionalIdMap<Block> blockNbtMap) {
this.reloadRequired = true;
this.blockNbtMap = blockNbtMap;
clearTeNbtCache();
}
public boolean hasSnowyEntries() {
return hasSnowyEntries;
}
@@ -0,0 +1,233 @@
package net.coderbot.iris.block_rendering;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.materialmap.PropertiesTokenizer;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.ResourceLocation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Maps objects with specific NBT properties to shader IDs. Generic over the
* key type so it works for blocks, items, and entities.
*
* Example:
* block.37=flower_pot[Item=minecraft:red_flower,Data=0]
* item.10=diamond_sword[display.Name='Pointy Stick']
*/
public class NbtConditionalIdMap<K> {
/**
* A single property matcher within a condition.
*/
record PropertyMatcher(String key, String[] path, String expectedValue,
long expectedLong, boolean isExpectedLong,
double expectedDouble, boolean isExpectedDouble) {
boolean isPathMatch() { return path != null; }
boolean isExistenceOnly() { return expectedValue == null; }
}
record NbtCondition(PropertyMatcher[] matchers, int shaderId) {}
private final Map<K, List<NbtCondition>> conditionsByKey;
public NbtConditionalIdMap() {
this.conditionsByKey = new HashMap<>();
}
/**
* Registers an NBT condition. Unquoted values are resolved via item/block
* registries at registration time. Single-quoted values are kept as literals.
* Dot notation paths are pre-split here so we prevent allocations.
*/
public void addCondition(K key, Map<String, PropertiesTokenizer.NbtValue> nbtProperties, int shaderId) {
final PropertyMatcher[] matchers = new PropertyMatcher[nbtProperties.size()];
int i = 0;
for (Map.Entry<String, PropertiesTokenizer.NbtValue> entry : nbtProperties.entrySet()) {
final String nbtKey = entry.getKey();
final PropertiesTokenizer.NbtValue nbtValue = entry.getValue();
final String resolvedValue;
if (nbtValue == null) {
resolvedValue = null;
} else if (nbtValue.literal()) {
resolvedValue = nbtValue.value();
} else {
resolvedValue = resolveValue(nbtValue.value());
}
final String[] path = nbtKey.contains(".") ? nbtKey.split("\\.") : null;
long expectedLong = 0;
boolean isExpectedLong = false;
double expectedDouble = 0;
boolean isExpectedDouble = false;
if (resolvedValue != null) {
try {
expectedLong = Long.parseLong(resolvedValue);
isExpectedLong = true;
} catch (NumberFormatException ignored) {}
try {
expectedDouble = Double.parseDouble(resolvedValue);
isExpectedDouble = true;
} catch (NumberFormatException ignored) {}
}
matchers[i++] = new PropertyMatcher(nbtKey, path, resolvedValue,
expectedLong, isExpectedLong, expectedDouble, isExpectedDouble);
}
conditionsByKey.computeIfAbsent(key, k -> new ArrayList<>())
.add(new NbtCondition(matchers, shaderId));
}
public boolean hasConditions(K key) {
return conditionsByKey.containsKey(key);
}
public boolean isEmpty() {
return conditionsByKey.isEmpty();
}
/**
* Resolves the shader ID by matching the provided NBT data.
*/
public int resolve(K key, NBTTagCompound nbt) {
final List<NbtCondition> conditions = conditionsByKey.get(key);
if (conditions == null) {
return -1;
}
for (NbtCondition condition : conditions) {
if (matchesNbt(nbt, condition.matchers())) {
return condition.shaderId;
}
}
return -1;
}
private static boolean matchesNbt(NBTTagCompound nbt, PropertyMatcher[] matchers) {
for (PropertyMatcher matcher : matchers) {
if (matcher.isPathMatch()) {
if (!matchesPath(nbt, matcher.path(), 0, matcher)) {
return false;
}
} else {
if (!nbt.hasKey(matcher.key())) {
return false;
}
if (!matcher.isExistenceOnly()) {
if (!matchesNbtValue(nbt.getTag(matcher.key()), matcher)) {
return false;
}
}
}
}
return true;
}
/**
* Search through dot-separated path in NBT data.
*/
private static boolean matchesPath(NBTBase tag, String[] path, int depth, PropertyMatcher matcher) {
if (depth == path.length) {
if (matcher.isExistenceOnly()) {
return true;
}
return matchesNbtValue(tag, matcher);
}
final String segment = path[depth];
if (tag instanceof NBTTagCompound compound) {
if (!compound.hasKey(segment)) {
return false;
}
return matchesPath(compound.getTag(segment), path, depth + 1, matcher);
}
if (tag instanceof NBTTagList list) {
for (int i = 0; i < list.tagCount(); i++) {
if (matchesPath(list.getCompoundTagAt(i), path, depth, matcher)) {
return true;
}
}
return false;
}
return false;
}
/**
* Compares an NBT tag's value against an expected string using type-specific
* accessors to avoid toString() allocations where possible.
* Makes it so you don't need to specify the type either in the .properties files.
*/
private static boolean matchesNbtValue(NBTBase tag, PropertyMatcher matcher) {
return switch (tag.getId()) {
case 1 -> // TAG_Byte
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagByte) tag).func_150290_f() == matcher.expectedLong();
case 2 -> // TAG_Short
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagShort) tag).func_150289_e() == matcher.expectedLong();
case 3 -> // TAG_Int
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagInt) tag).func_150287_d() == matcher.expectedLong();
case 4 -> // TAG_Long
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagLong) tag).func_150291_c() == matcher.expectedLong();
case 5 -> // TAG_Float
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagFloat) tag).func_150288_h() == (float) matcher.expectedDouble();
case 6 -> // TAG_Double
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagDouble) tag).func_150286_g() == matcher.expectedDouble();
case 8 -> // TAG_String
matcher.expectedValue().equals(((net.minecraft.nbt.NBTTagString) tag).func_150285_a_());
default -> // Compounds, lists, arrays — fall back to toString
matcher.expectedValue().equals(PropertiesTokenizer.stripQuotes(tag.toString()));
};
}
/**
* Tries to resolve a value as a registry name (item or block) to its numeric ID.
* If the value is already numeric or can't be resolved, return it unchanged.
*/
private static String resolveValue(String value) {
try {
Integer.parseInt(value);
return value;
} catch (NumberFormatException ignored) {}
if (!value.contains(":")) {
Iris.logger.warn("NBT value '{}' is missing a namespace, please use the full form e.g. 'minecraft:{}'", value, value);
return value;
}
final ResourceLocation loc = new ResourceLocation(value);
final Item item = (Item) Item.itemRegistry.getObject(loc.toString());
if (item != null) {
return String.valueOf(Item.getIdFromItem(item));
}
final Block block = (Block) Block.blockRegistry.getObject(loc.toString());
if (block != null) {
return String.valueOf(Block.getIdFromBlock(block));
}
Iris.logger.warn("Could not resolve '{}' to an item or block ID", value);
return value;
}
}
@@ -48,6 +48,9 @@ public class GlFramebuffer extends GlResource {
public void addDepthAttachmentBypass(int texture) {
final int fb = getGlId();
RenderSystem.framebufferTexture2D(fb, GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, texture, 0);
// Detach any stale stencil left over from a prior combined-stencil attach; otherwise drivers
// reject the FBO as UNSUPPORTED when the depth attachment no longer matches the stencil.
RenderSystem.framebufferTexture2D(fb, GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL11.GL_TEXTURE_2D, 0, 0);
this.hasDepthAttachment = true;
}
@@ -113,10 +116,13 @@ public class GlFramebuffer extends GlResource {
GLStateManager.glDeleteFramebuffers(getGlId());
}
public boolean isComplete() {
public int getStatus() {
bind();
return GLStateManager.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
}
return GLStateManager.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE;
public boolean isComplete() {
return getStatus() == GL30.GL_FRAMEBUFFER_COMPLETE;
}
public int getId() {
@@ -277,15 +277,19 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
holder -> CommonUniforms.addNonDynamicUniforms(holder, programs.getPack().getIdMap(), programs.getPackDirectives(), this.updateNotifier)
);
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(BlockMaterialMapping.createBlockMetaIdMap(
final var blockIdMaps = BlockMaterialMapping.createBlockIdMaps(
programs.getPack().getIdMap().getBlockProperties(),
programs.getPack().getIdMap().hasLegacySection()));
programs.getPack().getIdMap().hasLegacySection());
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(blockIdMaps.blockMetaMap());
BlockRenderingSettings.INSTANCE.setBlockNbtMap(blockIdMaps.tileEntityMap());
BlockRenderingSettings.INSTANCE.setBlockTypeIds(BlockMaterialMapping.createBlockTypeMap(programs.getPack().getIdMap().getBlockRenderTypeMap()));
BlockRenderingSettings.INSTANCE.setEntityIds(programs.getPack().getIdMap().getEntityIdMap());
BlockRenderingSettings.INSTANCE.setEntityNbtMap(BlockMaterialMapping.createNamespacedNbtMap(programs.getPack().getIdMap().getEntityNbtEntries()));
ItemMaterialHelper.clearCache();
BlockRenderingSettings.INSTANCE.setItemIds(programs.getPack().getIdMap().getItemIdMap());
BlockRenderingSettings.INSTANCE.setItemNbtMap(BlockMaterialMapping.createNamespacedNbtMap(programs.getPack().getIdMap().getItemNbtEntries()));
BlockRenderingSettings.INSTANCE.setAmbientOcclusionLevel(programs.getPackDirectives().getAmbientOcclusionLevel());
BlockRenderingSettings.INSTANCE.setDisableDirectionalShading(shouldDisableDirectionalShading());
BlockRenderingSettings.INSTANCE.setUseSeparateAo(programs.getPackDirectives().shouldUseSeparateAo());
@@ -1560,6 +1564,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
@Override
public void beginLevelRendering() {
final Framebuffer mainFb = Minecraft.getMinecraft().getFramebuffer();
if (mainFb == null || mainFb.framebufferWidth < 16 || mainFb.framebufferHeight < 16) {
return;
}
isRenderingFullScreenPass = false;
isRenderingWorld = true;
isBeforeTranslucent = true;
@@ -18,6 +18,7 @@ import net.coderbot.iris.shaderpack.texture.TextureStage;
import net.coderbot.iris.uniforms.FrameUpdateNotifier;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.shader.Framebuffer;
import java.util.List;
import java.util.OptionalInt;
@@ -33,8 +34,12 @@ public class FixedFunctionWorldRenderingPipeline implements WorldRenderingPipeli
@Override
public void beginLevelRendering() {
final Framebuffer mainFb = Minecraft.getMinecraft().getFramebuffer();
if (mainFb == null || mainFb.framebufferWidth < 16 || mainFb.framebufferHeight < 16) {
return;
}
// Use the default Minecraft framebuffer and ensure that no programs are in use
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
mainFb.bindFramebuffer(true);
GLStateManager.glUseProgram(0);
}
@@ -12,6 +12,7 @@ import net.coderbot.iris.shaderpack.PackDirectives;
import net.coderbot.iris.shaderpack.PackRenderTargetDirectives;
import org.joml.Vector2i;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
@@ -320,8 +321,9 @@ public class RenderTargets {
framebuffer.drawBuffers(actualDrawBuffers);
framebuffer.readBuffer(0);
if (!framebuffer.isComplete()) {
throw new IllegalStateException("Unexpected error while creating framebuffer");
final int status = framebuffer.getStatus();
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
throw new IllegalStateException(String.format( "Unexpected error while creating framebuffer: glCheckFramebufferStatus=0x%X, size=%dx%d, drawBuffers=%d", status, cachedWidth, cachedHeight, drawBuffers.length));
}
return framebuffer;
@@ -14,6 +14,7 @@ import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
import net.coderbot.iris.shaderpack.materialmap.EntityFlatteningMap;
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.shaderpack.materialmap.PropertiesTokenizer;
import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
import net.coderbot.iris.shaderpack.preprocessor.PropertiesPreprocessor;
import net.minecraft.item.Item;
@@ -45,11 +46,15 @@ public class IdMap {
* Maps a given item ID to an integer ID
*/
private final Object2IntMap<NamespacedId> itemIdMap;
@Getter
private final Int2ObjectMap<List<BlockEntry>> itemNbtEntries;
/**
* Maps a given entity ID to an integer ID
*/
private final Object2IntMap<NamespacedId> entityIdMap;
@Getter
private final Int2ObjectMap<List<BlockEntry>> entityNbtEntries;
/**
* Maps block states to block ids defined in block.properties
@@ -106,8 +111,17 @@ public class IdMap {
});
}
itemIdMap = loadProperties(shaderPath, "item.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseItemIdMap).orElse(Object2IntMaps.emptyMap());
entityIdMap = loadProperties(shaderPath, "entity.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseEntityIdMap).orElse(Object2IntMaps.emptyMap());
final ParsedIdMap parsedItems = loadProperties(shaderPath, "item.properties", shaderPackOptions, resolvedDefines)
.map(p -> parseIdMap(p, "item.", "item.properties"))
.orElse(new ParsedIdMap(Object2IntMaps.emptyMap(), new Int2ObjectOpenHashMap<>()));
itemIdMap = parsedItems.simpleMap();
itemNbtEntries = parsedItems.nbtEntries();
final ParsedIdMap parsedEntities = loadProperties(shaderPath, "entity.properties", shaderPackOptions, resolvedDefines)
.map(p -> parseIdMap(p, "entity.", "entity.properties"))
.orElse(new ParsedIdMap(Object2IntMaps.emptyMap(), new Int2ObjectOpenHashMap<>()));
entityIdMap = augmentEntityIdMap(parsedEntities.simpleMap(), parsedEntities.nbtEntries());
entityNbtEntries = parsedEntities.nbtEntries();
// TODO: Properly override block render layers
@@ -166,25 +180,37 @@ public class IdMap {
}
}
private static Object2IntMap<NamespacedId> parseItemIdMap(Properties properties) {
return parseIdMap(properties, "item.", "item.properties");
}
private static Object2IntMap<NamespacedId> parseEntityIdMap(Properties properties) {
Object2IntMap<NamespacedId> idMap = parseIdMap(properties, "entity.", "entity.properties");
// For modern shader packs: translate modern entity names to 1.7.10 names
// so runtime lookups match EntityList's registered names directly.
/**
* For modern shaderpacks: also expose entries under their 1.7.10 entity names
* so runtime lookups match EntityList's registered names directly.
*
* Modern names whose 1.7.10 counterpart is NBT-discriminated (e.g. wither_skeleton
* is just Skeleton with SkeletonType=1) are routed into the NBT-conditional map
* instead of being aliased into the simple map — otherwise the simple lookup for
* "Skeleton" would short-circuit and match every skeleton variant.
*/
private static Object2IntMap<NamespacedId> augmentEntityIdMap(
Object2IntMap<NamespacedId> idMap,
Int2ObjectMap<List<BlockEntry>> nbtEntries) {
Object2IntMap<NamespacedId> augmented = new Object2IntOpenHashMap<>(idMap);
augmented.defaultReturnValue(-1);
for (Object2IntMap.Entry<NamespacedId> entry : idMap.object2IntEntrySet()) {
NamespacedId id = entry.getKey();
if ("minecraft".equals(id.getNamespace())) {
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
if (legacyName != null) {
augmented.putIfAbsent(new NamespacedId(legacyName), entry.getIntValue());
}
if (!"minecraft".equals(id.getNamespace())) {
continue;
}
final int intId = entry.getIntValue();
BlockEntry nbtMapping = EntityFlatteningMap.toLegacyWithNbt(id.getName());
if (nbtMapping != null) {
nbtEntries.computeIfAbsent(intId, k -> new ArrayList<>()).add(nbtMapping);
continue;
}
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
if (legacyName != null) {
augmented.putIfAbsent(new NamespacedId(legacyName), intId);
}
}
@@ -212,67 +238,18 @@ public class IdMap {
* @return List of parsed identifiers
*/
static List<String> parseIdentifierList(String value, String fileName, String key) {
if (value.indexOf('"') == -1) {
String[] parts = value.split("\\s+");
List<String> result = new ArrayList<>(parts.length);
for (String part : parts) {
if (!part.isEmpty()) {
result.add(part);
}
}
return result;
}
// Found quote, start of a dumb block ID
List<String> result = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
boolean escaped = false;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (escaped) {
current.append(c);
escaped = false;
} else if (c == '\\') {
escaped = true;
} else if (c == '"') {
inQuotes = !inQuotes;
} else if (Character.isWhitespace(c) && !inQuotes) {
if (!current.isEmpty()) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(c);
}
}
// Didn't close a quote, warn
if (inQuotes) {
Iris.logger.warn(fileName + " [" + key + "]: Unclosed quote");
}
// Trailing backslash, warn
if (escaped) {
Iris.logger.warn(fileName + " [" + key + "]: Trailing backslash");
}
// Add final token
if (!current.isEmpty()) {
result.add(current.toString());
}
return result;
return PropertiesTokenizer.tokenizeValues(value, ' ');
}
record ParsedIdMap(Object2IntMap<NamespacedId> simpleMap, Int2ObjectMap<List<BlockEntry>> nbtEntries) {}
/**
* Parses a NamespacedId map in OptiFine format
*/
private static Object2IntMap<NamespacedId> parseIdMap(Properties properties, String keyPrefix, String fileName) {
private static ParsedIdMap parseIdMap(Properties properties, String keyPrefix, String fileName) {
Object2IntMap<NamespacedId> idMap = new Object2IntOpenHashMap<>();
idMap.defaultReturnValue(-1);
Int2ObjectMap<List<BlockEntry>> nbtEntries = new Int2ObjectOpenHashMap<>();
properties.forEach((keyObject, valueObject) -> {
String key = (String) keyObject;
@@ -295,6 +272,20 @@ public class IdMap {
// Parse identifiers
for (String part : parseIdentifierList(value, fileName, key)) {
if (part.contains("[")) {
// NBT-conditional entry
try {
BlockEntry entry = BlockEntry.parse(part);
if (entry.hasNbtProperties()) {
nbtEntries.computeIfAbsent(intId, k -> new ArrayList<>()).add(entry);
} else {
idMap.put(entry.id(), intId);
}
} catch (Exception e) {
Iris.logger.warn("Failed to parse NBT entry in " + fileName + " for key " + key + ": " + part, e);
}
continue;
}
if (part.contains("=")) {
// Avoid tons of logspam for now
Iris.logger.warn("Failed to parse an ResourceLocation in " + fileName + " for the key " + key + ": state properties are currently not supported: " + part);
@@ -307,7 +298,7 @@ public class IdMap {
}
});
return Object2IntMaps.unmodifiable(idMap);
return new ParsedIdMap(Object2IntMaps.unmodifiable(idMap), nbtEntries);
}
private static Int2ObjectMap<List<BlockEntry>> parseBlockMap(Properties properties, String keyPrefix, String fileName) {
@@ -408,7 +399,7 @@ public class IdMap {
return itemIdMap;
}
public Object2IntFunction<NamespacedId> getEntityIdMap() {
public Object2IntFunction<NamespacedId> getEntityIdMap() {
return entityIdMap;
}
@@ -1,124 +1,45 @@
package net.coderbot.iris.shaderpack.materialmap;
import lombok.Getter;
import net.coderbot.iris.Iris;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@Getter
public class BlockEntry {
private final NamespacedId id;
private final Set<Integer> metas;
private final Map<String, String> stateProperties;
public record BlockEntry(
NamespacedId id,
Set<Integer> metas,
Map<String, String> stateProperties,
Map<String, PropertiesTokenizer.NbtValue> nbtProperties
) {
public BlockEntry(NamespacedId id, Set<Integer> metas) {
this(id, metas, Collections.emptyMap(), Collections.emptyMap());
}
public BlockEntry(NamespacedId id, Set<Integer> metas) {
this(id, metas, Collections.emptyMap());
}
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
this(id, metas, stateProperties, Collections.emptyMap());
}
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
this.id = id;
this.metas = metas;
this.stateProperties = stateProperties;
}
public boolean hasNbtProperties() {
return !nbtProperties.isEmpty();
}
/**
* Parses a block ID entry.
*
* @param entry The string representation of the entry. Must not be empty.
*/
@NotNull
public static BlockEntry parse(@NotNull String entry) {
if (entry.isEmpty()) {
throw new IllegalArgumentException("Called BlockEntry::parse with an empty string");
}
public boolean hasStateProperties() {
return !stateProperties.isEmpty();
}
// We can assume that this array is of at least array length because the input string is non-empty.
final String[] splitStates = entry.split(":");
// Trivial case: no states, no namespace
if (splitStates.length == 1) {
return new BlockEntry(new NamespacedId("minecraft", entry), Collections.emptySet());
}
// Examples of what we accept:
// stone
// stone:0
// minecraft:stone:0
// minecraft:stone:0,1,2
// minecraft:furnace:lit=true (blockstate property)
// minecraft:oak_log:axis=y (blockstate property)
// Less trivial case: no metas or state properties, just a namespace
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))
&& !splitStates[1].contains("=")) {
return new BlockEntry(new NamespacedId(splitStates[0], splitStates[1]), Collections.emptySet());
}
// Complex case: metas and/or state properties
final int statesStart;
final NamespacedId id;
if (splitStates.length == 2) {
// "stone:0" or "stone:lit=true"
statesStart = 1;
id = new NamespacedId("minecraft", splitStates[0]);
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
// "stone:0:something" or "stone:lit=true:something" — unlikely but handle it
statesStart = 1;
id = new NamespacedId("minecraft", splitStates[0]);
} else {
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
statesStart = 2;
id = new NamespacedId(splitStates[0], splitStates[1]);
}
final Set<Integer> metas = new HashSet<>();
final Map<String, String> properties = new HashMap<>();
for (int index = statesStart; index < splitStates.length; index++) {
String segment = splitStates[index];
if (segment.contains("=")) {
// Blockstate property: key=value
for (String prop : segment.split(",")) {
int eq = prop.indexOf('=');
if (eq > 0 && eq < prop.length() - 1) {
properties.put(prop.substring(0, eq), prop.substring(eq + 1));
}
}
} else {
// Metadata IDs: comma-separated integers
for (String metaPart : segment.split(",")) {
try {
metas.add(Integer.parseInt(metaPart));
} catch (NumberFormatException e) {
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but " + segment + " is not of that form!");
}
}
}
/**
* Parses a block ID entry. All tokenization is delegated to {@link PropertiesTokenizer}.
*
* @param entry The string representation of the entry. Must not be empty.
*/
@NotNull
public static BlockEntry parse(@NotNull String entry) {
if (entry.isEmpty()) {
throw new IllegalArgumentException("Called BlockEntry::parse with an empty string");
}
return new BlockEntry(id, metas, properties);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BlockEntry that = (BlockEntry) o;
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas) && Objects.equals(stateProperties, that.stateProperties);
}
@Override
public int hashCode() {
return Objects.hash(id, metas, stateProperties);
}
final PropertiesTokenizer.ParsedBlockIdentifier parsed = PropertiesTokenizer.parseBlockIdentifier(entry);
return new BlockEntry(parsed.id(), parsed.metas(), parsed.stateProperties(), parsed.nbtProperties());
}
}
@@ -1,5 +1,6 @@
package net.coderbot.iris.shaderpack.materialmap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -10,14 +11,15 @@ import java.util.Map;
* In 1.7.10, entities are registered with names like "CaveSpider".
* Some entities were also renamed or split into multiple types.
*
* This map is used at lookup time: when a modern shader pack references an entity by its
* modern name, we resolve it to the 1.7.10 name that EntityList uses.
*/
public class EntityFlatteningMap {
/** Maps modern entity name -> 1.7.10 entity name. */
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
/** Maps modern entity name -> legacy entity name + NBT. */
private static final Map<String, BlockEntry> MODERN_TO_LEGACY_NBT = new HashMap<>();
static {
// Simple case changes
simple("Arrow", "arrow");
@@ -35,7 +37,6 @@ public class EntityFlatteningMap {
simple("Pig", "pig");
simple("Sheep", "sheep");
simple("Silverfish", "silverfish");
simple("Skeleton", "skeleton");
simple("Slime", "slime");
simple("Snowball", "snowball");
simple("Spider", "spider");
@@ -49,7 +50,6 @@ public class EntityFlatteningMap {
simple("CaveSpider", "cave_spider");
simple("EnderCrystal", "end_crystal");
simple("EnderDragon", "ender_dragon");
simple("EntityHorse", "horse");
simple("EyeOfEnderSignal", "eye_of_ender");
simple("FallingSand", "falling_block");
simple("Fireball", "fireball");
@@ -79,6 +79,36 @@ public class EntityFlatteningMap {
simple("MinecartHopper", "hopper_minecart");
simple("MinecartSpawner", "spawner_minecart");
simple("MinecartTNT", "tnt_minecart");
MODERN_TO_LEGACY_NBT.put("skeleton", new BlockEntry(
new NamespacedId("Skeleton"), Collections.emptySet(), Collections.emptyMap(),
Map.of("SkeletonType", new PropertiesTokenizer.NbtValue("0", false))));
MODERN_TO_LEGACY_NBT.put("wither_skeleton", new BlockEntry(
new NamespacedId("Skeleton"), Collections.emptySet(), Collections.emptyMap(),
Map.of("SkeletonType", new PropertiesTokenizer.NbtValue("1", false))));
// 1.7.10 EntityHorse with Type byte: 0=horse, 1=donkey, 2=mule, 3=zombie_horse,
// 4=skeleton_horse. 1.11+ split each into its own entity type.
MODERN_TO_LEGACY_NBT.put("horse", new BlockEntry(
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
Map.of("Type", new PropertiesTokenizer.NbtValue("0", false))));
MODERN_TO_LEGACY_NBT.put("donkey", new BlockEntry(
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
Map.of("Type", new PropertiesTokenizer.NbtValue("1", false))));
MODERN_TO_LEGACY_NBT.put("mule", new BlockEntry(
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
Map.of("Type", new PropertiesTokenizer.NbtValue("2", false))));
MODERN_TO_LEGACY_NBT.put("zombie_horse", new BlockEntry(
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
Map.of("Type", new PropertiesTokenizer.NbtValue("3", false))));
MODERN_TO_LEGACY_NBT.put("skeleton_horse", new BlockEntry(
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
Map.of("Type", new PropertiesTokenizer.NbtValue("4", false))));
// 1.7.10 Zombie with IsVillager.
MODERN_TO_LEGACY_NBT.put("zombie_villager", new BlockEntry(
new NamespacedId("Zombie"), Collections.emptySet(), Collections.emptyMap(),
Map.of("IsVillager", new PropertiesTokenizer.NbtValue("1", false))));
}
/**
@@ -89,6 +119,10 @@ public class EntityFlatteningMap {
return MODERN_TO_LEGACY.get(modernName);
}
public static BlockEntry toLegacyWithNbt(String modernName) {
return MODERN_TO_LEGACY_NBT.get(modernName);
}
private static void simple(String legacy, String modern) {
MODERN_TO_LEGACY.put(modern, legacy);
}
@@ -158,18 +158,18 @@ public class FlatteningMap {
rename("dark_oak_wall_sign", "wall_sign");
// === Skull / Head blocks (ID 144) ===
// In 1.7.10, skull type is stored in tile entity, not metadata.
// All modern skull blocks map to the same 1.7.10 block.
rename("skeleton_skull", "skull");
rename("skeleton_wall_skull", "skull");
rename("wither_skeleton_skull", "skull");
rename("wither_skeleton_wall_skull", "skull");
rename("zombie_head", "skull");
rename("zombie_wall_head", "skull");
rename("player_head", "skull");
rename("player_wall_head", "skull");
rename("creeper_head", "skull");
rename("creeper_wall_head", "skull");
// 1.7.10 stores skull type in TileEntitySkull's SkullType byte (0=skeleton,
// 1=wither, 2=zombie, 3=player, 4=creeper).
MODERN_TO_LEGACY.put("skeleton_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("skeleton_wall_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("wither_skeleton_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("1", false)))));
MODERN_TO_LEGACY.put("wither_skeleton_wall_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("1", false)))));
MODERN_TO_LEGACY.put("zombie_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("2", false)))));
MODERN_TO_LEGACY.put("zombie_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("2", false)))));
MODERN_TO_LEGACY.put("player_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("3", false)))));
MODERN_TO_LEGACY.put("player_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("3", false)))));
MODERN_TO_LEGACY.put("creeper_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("4", false)))));
MODERN_TO_LEGACY.put("creeper_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("4", false)))));
// ==========================================
// Metadata variants (block split by flattening)
@@ -321,29 +321,29 @@ public class FlatteningMap {
meta("mossy_cobblestone_wall", "cobblestone_wall", 1);
// === Flower pot (ID 140) ===
// In 1.7.10, flower pot contents are in the tile entity.
// In 1.13+, each potted plant is a separate block.
rename("potted_oak_sapling", "flower_pot");
rename("potted_spruce_sapling", "flower_pot");
rename("potted_birch_sapling", "flower_pot");
rename("potted_jungle_sapling", "flower_pot");
rename("potted_acacia_sapling", "flower_pot");
rename("potted_dark_oak_sapling", "flower_pot");
rename("potted_fern", "flower_pot");
rename("potted_dandelion", "flower_pot");
rename("potted_poppy", "flower_pot");
rename("potted_blue_orchid", "flower_pot");
rename("potted_allium", "flower_pot");
rename("potted_azure_bluet", "flower_pot");
rename("potted_red_tulip", "flower_pot");
rename("potted_orange_tulip", "flower_pot");
rename("potted_white_tulip", "flower_pot");
rename("potted_pink_tulip", "flower_pot");
rename("potted_oxeye_daisy", "flower_pot");
rename("potted_red_mushroom", "flower_pot");
rename("potted_brown_mushroom", "flower_pot");
rename("potted_dead_bush", "flower_pot");
rename("potted_cactus", "flower_pot");
// 1.7.10 stores potted contents in TileEntityFlowerPot's NBT. 1.13+ has a separate block
// per potted plant.
MODERN_TO_LEGACY.put("potted_oak_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_spruce_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("1", false)))));
MODERN_TO_LEGACY.put("potted_birch_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
MODERN_TO_LEGACY.put("potted_jungle_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("3", false)))));
MODERN_TO_LEGACY.put("potted_acacia_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("4", false)))));
MODERN_TO_LEGACY.put("potted_dark_oak_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("5", false)))));
MODERN_TO_LEGACY.put("potted_fern", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:tallgrass", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
MODERN_TO_LEGACY.put("potted_dandelion", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:yellow_flower", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_poppy", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_blue_orchid", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("1", false)))));
MODERN_TO_LEGACY.put("potted_allium", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
MODERN_TO_LEGACY.put("potted_azure_bluet", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("3", false)))));
MODERN_TO_LEGACY.put("potted_red_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("4", false)))));
MODERN_TO_LEGACY.put("potted_orange_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("5", false)))));
MODERN_TO_LEGACY.put("potted_white_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("6", false)))));
MODERN_TO_LEGACY.put("potted_pink_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("7", false)))));
MODERN_TO_LEGACY.put("potted_oxeye_daisy", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("8", false)))));
MODERN_TO_LEGACY.put("potted_red_mushroom", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_mushroom", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_brown_mushroom", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:brown_mushroom", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_dead_bush", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:deadbush", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
MODERN_TO_LEGACY.put("potted_cactus", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:cactus", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
// === Anvil (ID 145) ===
// Bits 0-1 = facing, bits 2-3 = damage level
@@ -928,26 +928,26 @@ public class FlatteningMap {
List<BlockEntry> result = new ArrayList<>(Math.min(a.size(), b.size()));
for (BlockEntry entryA : a) {
for (BlockEntry entryB : b) {
if (!entryA.getId().equals(entryB.getId())) continue;
if (!entryA.id().equals(entryB.id())) continue;
Set<Integer> metasA = entryA.getMetas();
Set<Integer> metasB = entryB.getMetas();
Set<Integer> metasA = entryA.metas();
Set<Integer> metasB = entryB.metas();
if (metasA.isEmpty() && metasB.isEmpty()) {
// Both wildcard -> wildcard
result.add(new BlockEntry(entryA.getId(), Collections.emptySet()));
result.add(new BlockEntry(entryA.id(), Collections.emptySet()));
} else if (metasA.isEmpty()) {
// A is wildcard -> use B's constraint
result.add(new BlockEntry(entryA.getId(), metasB));
result.add(new BlockEntry(entryA.id(), metasB));
} else if (metasB.isEmpty()) {
// B is wildcard -> use A's constraint
result.add(new BlockEntry(entryA.getId(), metasA));
result.add(new BlockEntry(entryA.id(), metasA));
} else {
// Both have specific metas -> intersect
Set<Integer> intersection = new IntOpenHashSet(metasA);
intersection.retainAll(metasB);
if (!intersection.isEmpty()) {
result.add(new BlockEntry(entryA.getId(), intersection));
result.add(new BlockEntry(entryA.id(), intersection));
}
}
}
@@ -0,0 +1,388 @@
package net.coderbot.iris.shaderpack.materialmap;
import net.coderbot.iris.Iris;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
// This got complicated fast
/**
* This class handles all processing of individual block entries per material ID line.
*/
public final class PropertiesTokenizer {
private PropertiesTokenizer() {
}
/**
* Splits a property value into individual block identifier tokens.
*/
public static List<String> tokenizeValues(String input, char delimiter) {
if (input.indexOf('"') == -1 && input.indexOf('\\') == -1 && input.indexOf('[') == -1) {
return tokenizeSimple(input, delimiter);
}
return tokenizeFull(input, delimiter);
}
// Most blocks should take this path
private static List<String> tokenizeSimple(String input, char delimiter) {
final List<String> result = new ArrayList<>();
final boolean isWhitespaceDelim = Character.isWhitespace(delimiter);
if (isWhitespaceDelim) {
for (String part : input.split("\\s+")) {
if (!part.isEmpty()) {
result.add(part);
}
}
} else {
for (String part : input.split(String.valueOf(delimiter))) {
final String trimmed = part.trim();
if (!trimmed.isEmpty()) {
result.add(trimmed);
}
}
}
return result;
}
// Found special characters
private static List<String> tokenizeFull(String input, char delimiter) {
final boolean isWhitespaceDelim = Character.isWhitespace(delimiter);
final List<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean inQuotes = false;
boolean escaped = false;
int bracketDepth = 0;
for (int i = 0; i < input.length(); i++) {
final char c = input.charAt(i);
if (escaped) {
if (bracketDepth > 0) {
// Preserve escapes inside brackets for parseBlockIdentifier
current.append('\\');
}
current.append(c);
escaped = false;
} else if (c == '\\') {
escaped = true;
} else if (bracketDepth > 0) {
current.append(c);
if (c == '[') {
bracketDepth++;
}
else if (c == ']') {
bracketDepth--;
}
} else if (c == '[' && !inQuotes) {
current.append(c);
bracketDepth = 1;
} else if (c == '"') {
inQuotes = !inQuotes;
} else if (!inQuotes && (isWhitespaceDelim ? Character.isWhitespace(c) : c == delimiter)) {
if (!current.isEmpty()) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(c);
}
}
if (inQuotes) {
Iris.logger.warn("Unclosed quote in properties token: {}", input);
}
if (!current.isEmpty()) {
result.add(current.toString());
}
return result;
}
// Begin reading the special case block
public static ParsedBlockIdentifier parseBlockIdentifier(String entry) {
final StringBuilder blockIdBuilder = new StringBuilder();
final Map<String, NbtValue> nbt = new LinkedHashMap<>();
boolean escaped = false;
boolean inDoubleQuotes = false;
boolean inSingleQuotes = false;
boolean inBracket = false;
// NBT pair
StringBuilder nbtKey = new StringBuilder();
StringBuilder nbtValue = new StringBuilder();
boolean readingValue = false;
boolean valueLiteral = false;
for (int i = 0; i < entry.length(); i++) {
final char c = entry.charAt(i);
// Ignore special characters
if (escaped) {
appendCurrent(inBracket, readingValue, blockIdBuilder, nbtKey, nbtValue, c);
escaped = false;
continue;
}
if (c == '\\') {
escaped = true;
continue;
}
// Detect special characters
if (inSingleQuotes) {
if (c == '\'') {
inSingleQuotes = false;
} else {
nbtValue.append(c);
}
continue;
}
if (inDoubleQuotes) {
if (c == '"') {
inDoubleQuotes = false;
} else {
appendCurrent(inBracket, readingValue, blockIdBuilder, nbtKey, nbtValue, c);
}
continue;
}
if (c == '"') {
inDoubleQuotes = true;
continue;
}
if (!inBracket) {
if (c == '[') {
inBracket = true;
nbtKey.setLength(0);
nbtValue.setLength(0);
readingValue = false;
valueLiteral = false;
} else {
blockIdBuilder.append(c);
}
} else {
if (c == ']') {
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
inBracket = false;
} else if (c == '=' && !readingValue) {
readingValue = true;
valueLiteral = false;
} else if (c == '\'' && readingValue && nbtValue.isEmpty()) {
inSingleQuotes = true;
valueLiteral = true;
} else if (c == ',') {
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
nbtKey.setLength(0);
nbtValue.setLength(0);
readingValue = false;
valueLiteral = false;
} else {
if (readingValue) {
nbtValue.append(c);
} else {
nbtKey.append(c);
}
}
}
}
if (inDoubleQuotes) {
Iris.logger.warn("Unclosed double quote in block entry: {}", entry);
}
if (inSingleQuotes) {
Iris.logger.warn("Unclosed single quote in block entry: {}", entry);
}
if (escaped) {
Iris.logger.warn("Trailing backslash in block entry: {}", entry);
}
if (inBracket) {
Iris.logger.warn("Unclosed bracket in block entry: {}", entry);
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
}
final String baseEntry = blockIdBuilder.toString().trim();
final Map<String, NbtValue> nbtProperties = nbt.isEmpty() ? Collections.emptyMap() : nbt;
return splitBaseEntry(entry, baseEntry, nbtProperties);
}
/**
* Splits the bracket-stripped portion of a block identifier (e.g. {@code minecraft:furnace:lit=true})
* into its components.
*
* Accepted forms:
* stone (vanilla block)
* stone:0 (vanilla block with meta)
* minecraft:stone (block with namespaced id specified)
* minecraft:stone:0 (single meta)
* minecraft:stone:0,1,2 (multiple metas)
* minecraft:furnace:lit=true (blockstate property)
* minecraft:oak_log:axis=y,x:variant=oak (multiple blockstate properties)
* flower_pot:1[Item=minecraft:red_flower,Data=0] (used together with NBT brackets)
*/
private static ParsedBlockIdentifier splitBaseEntry(String entry, String baseEntry, Map<String, NbtValue> nbtProperties) {
final Set<Integer> metas = new HashSet<>();
final Map<String, String> stateProperties = new LinkedHashMap<>();
if (baseEntry.isEmpty()) {
return new ParsedBlockIdentifier(
new NamespacedId("minecraft", baseEntry),
Collections.emptySet(),
Collections.emptyMap(),
nbtProperties
);
}
final String[] splitStates = baseEntry.split(":");
// Vanilla block (minecraft namespaced)
if (splitStates.length == 1) {
return new ParsedBlockIdentifier(
new NamespacedId("minecraft", baseEntry),
Collections.emptySet(),
Collections.emptyMap(),
nbtProperties
);
}
// Two-segment with no metas/state properties: "minecraft:stone"
if (splitStates.length == 2
&& !StringUtils.isNumeric(splitStates[1].substring(0, 1))
&& !splitStates[1].contains("=")) {
return new ParsedBlockIdentifier(
new NamespacedId(splitStates[0], splitStates[1]),
Collections.emptySet(),
Collections.emptyMap(),
nbtProperties
);
}
// Metas and/or state properties
final NamespacedId id;
final int statesStart;
if (splitStates.length == 2) {
// "stone:0" or "stone:lit=true"
id = new NamespacedId("minecraft", splitStates[0]);
statesStart = 1;
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
// "stone:0:something" or "stone:lit=true:something", likely will need this for snowy blocks in the future.
id = new NamespacedId("minecraft", splitStates[0]);
statesStart = 1;
} else {
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
id = new NamespacedId(splitStates[0], splitStates[1]);
statesStart = 2;
}
for (int index = statesStart; index < splitStates.length; index++) {
final String segment = splitStates[index];
if (segment.contains("=")) {
// Blockstate property segment: key=value[,key=value...]
for (String prop : segment.split(",")) {
final int eq = prop.indexOf('=');
if (eq > 0 && eq < prop.length() - 1) {
stateProperties.put(prop.substring(0, eq), prop.substring(eq + 1));
}
}
} else {
// Metadata segment (stone:[1,2,3,4,5])
for (String metaPart : segment.split(",")) {
try {
metas.add(Integer.parseInt(metaPart));
} catch (NumberFormatException e) {
Iris.logger.warn("Warning: the block ID map entry \"{}\" could not be fully parsed:", entry);
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but {} is not of that form!", segment);
}
}
}
}
return new ParsedBlockIdentifier(
id,
metas.isEmpty() ? Collections.emptySet() : metas,
stateProperties.isEmpty() ? Collections.emptyMap() : stateProperties,
nbtProperties
);
}
private static void appendCurrent(boolean inBracket, boolean readingValue,
StringBuilder blockIdBuilder, StringBuilder nbtKey,
StringBuilder nbtValue, char c) {
if (!inBracket) {
blockIdBuilder.append(c);
} else if (readingValue) {
nbtValue.append(c);
} else {
nbtKey.append(c);
}
}
private static void flushNbtPair(StringBuilder key, StringBuilder value,
boolean readingValue, boolean valueLiteral,
Map<String, NbtValue> nbt) {
final String k = key.toString().trim();
if (k.isEmpty()) return;
if (!readingValue) {
// No = was found, existence is good enough
nbt.put(k, null);
return;
}
nbt.put(k, new NbtValue(value.toString().trim(), valueLiteral));
}
public static String stripQuotes(String value) {
if (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') {
return value.substring(1, value.length() - 1);
}
return value;
}
public record NbtValue(String value, boolean literal) {
}
public record ParsedBlockIdentifier(
NamespacedId id,
Set<Integer> metas,
Map<String, String> stateProperties,
Map<String, NbtValue> nbtProperties
) {
}
}
@@ -14,6 +14,7 @@ import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.rendertarget.RenderTarget;
import net.coderbot.iris.shaderpack.PackShadowDirectives;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
@@ -283,8 +284,9 @@ public class ShadowRenderTargets {
framebuffer.drawBuffers(actualDrawBuffers);
framebuffer.readBuffer(0);
if (!framebuffer.isComplete()) {
throw new IllegalStateException("Unexpected error while creating framebuffer");
final int status = framebuffer.getStatus();
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
throw new IllegalStateException(String.format( "Unexpected error while creating shadow framebuffer: glCheckFramebufferStatus=0x%X, resolution=%d, drawBuffers=%d", status, resolution, drawBuffers.length));
}
return framebuffer;
@@ -1,10 +1,16 @@
package net.coderbot.iris.uniforms;
import it.unimi.dsi.fastutil.ints.Int2LongLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.minecraft.nbt.NBTTagCompound;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
@@ -21,10 +27,16 @@ public final class EntityIdHelper {
private static final NamespacedId LIGHTNING_BOLT_ID = new NamespacedId("minecraft", "lightning_bolt");
private static final Object2IntMap<Class<?>> entityIdCache = new Object2IntOpenHashMap<>();
private static final Map<Class<?>, NamespacedId> entityNameCache = new IdentityHashMap<>();
private static Object2IntFunction<NamespacedId> cachedEntityIdMap;
private static final Int2LongLinkedOpenHashMap entityNbtCache = new Int2LongLinkedOpenHashMap();
private static final int NBT_CACHE_INTERVAL_TICKS = 20;
private static final int ENTITY_NBT_CACHE_MAX = 256;
static {
entityIdCache.defaultReturnValue(Integer.MIN_VALUE);
entityNbtCache.defaultReturnValue(-1L);
}
private EntityIdHelper() {
@@ -42,17 +54,48 @@ public final class EntityIdHelper {
return -1;
}
// Invalidate cache if the map changed (shader reload)
// Invalidate caches if the map changed (shader reload)
if (entityIdMap != cachedEntityIdMap) {
entityIdCache.clear();
entityNameCache.clear();
entityNbtCache.clear();
cachedEntityIdMap = entityIdMap;
}
// Check NBT-conditional match first
final NbtConditionalIdMap<NamespacedId> entityNbtMap = BlockRenderingSettings.INSTANCE.getEntityNbtMap();
if (entityNbtMap != null && !entityNbtMap.isEmpty() && entity.worldObj != null) {
final NamespacedId namespacedId = getCachedEntityName(entity);
if (namespacedId != null && entityNbtMap.hasConditions(namespacedId)) {
final int entityRuntimeId = entity.getEntityId();
final long currentTick = entity.worldObj.getTotalWorldTime();
final long cached = entityNbtCache.get(entityRuntimeId);
final int nbtId;
if (cached != -1L && (currentTick - (cached >>> 32)) < NBT_CACHE_INTERVAL_TICKS) {
nbtId = (int) cached;
} else {
final NBTTagCompound nbt = new NBTTagCompound();
entity.writeToNBT(nbt);
nbtId = entityNbtMap.resolve(namespacedId, nbt);
final long packed = ((currentTick & 0x7FFFFFFFL) << 32) | (nbtId & 0xFFFFFFFFL);
entityNbtCache.put(entityRuntimeId, packed);
while (entityNbtCache.size() > ENTITY_NBT_CACHE_MAX) {
entityNbtCache.removeFirstLong();
}
}
if (nbtId != -1) {
return nbtId;
}
}
}
// Normal entity type lookup
int normalId = getNormalEntityId(entity, entityIdMap);
final int normalId = getNormalEntityId(entity, entityIdMap);
// Check for special entity type overrides
int specialId = getSpecialEntityId(entity, entityIdMap);
final int specialId = getSpecialEntityId(entity, entityIdMap);
if (specialId != -1) {
return specialId;
}
@@ -134,4 +177,24 @@ public final class EntityIdHelper {
return id;
}
/**
* Returns a cached NamespacedId for the entity's registered type.
*/
private static NamespacedId getCachedEntityName(Entity entity) {
Class<?> entityClass = entity.getClass();
NamespacedId cached = entityNameCache.get(entityClass);
if (cached != null) {
return cached;
}
String entityType = EntityList.getEntityString(entity);
if (entityType == null) {
return null;
}
cached = new NamespacedId(entityType);
entityNameCache.put(entityClass, cached);
return cached;
}
}
@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
@@ -20,6 +21,7 @@ import net.minecraft.util.ResourceLocation;
public class ItemMaterialHelper {
private static final Reference2ObjectMap<Item, Int2IntMap> MATERIAL_CACHE = new Reference2ObjectOpenHashMap<>();
private static final int CACHE_MISS_SENTINEL = Integer.MIN_VALUE;
private static final Reference2ObjectMap<Item, NamespacedId> ITEM_NAME_CACHE = new Reference2ObjectOpenHashMap<>();
/**
* Get the material ID for an ItemStack.
* For ItemBlock: checks block.properties first, then item.properties.
@@ -32,6 +34,19 @@ public class ItemMaterialHelper {
if (itemStack == null || itemStack.getItem() == null) {
return -1;
}
// Check item NBT conditions first
final NbtConditionalIdMap<NamespacedId> itemNbtMap = BlockRenderingSettings.INSTANCE.getItemNbtMap();
if (itemNbtMap != null && !itemNbtMap.isEmpty() && itemStack.hasTagCompound()) {
NamespacedId namespacedId = getCachedItemName(itemStack.getItem());
if (namespacedId != null && itemNbtMap.hasConditions(namespacedId)) {
int nbtId = itemNbtMap.resolve(namespacedId, itemStack.getTagCompound());
if (nbtId != -1) {
return nbtId;
}
}
}
return getMaterialId(itemStack.getItem(), itemStack.getItemDamage());
}
@@ -73,8 +88,7 @@ public class ItemMaterialHelper {
*/
private static int lookupMaterialId(Item item, int metadata) {
// For ItemBlock: check block.properties first
if (item instanceof ItemBlock) {
final ItemBlock itemBlock = (ItemBlock) item;
if (item instanceof ItemBlock itemBlock) {
final Block block = itemBlock.field_150939_a; // The block this item places
if (block != null) {
@@ -94,7 +108,7 @@ public class ItemMaterialHelper {
// Fall back to item.properties
Object2IntFunction<NamespacedId> itemIds = BlockRenderingSettings.INSTANCE.getItemIds();
if (itemIds != null) {
String itemIdString = (String) Item.itemRegistry.getNameForObject(item);
String itemIdString = Item.itemRegistry.getNameForObject(item);
if (itemIdString != null) {
ResourceLocation itemId = new ResourceLocation(itemIdString);
return itemIds.applyAsInt(new NamespacedId(itemId.getResourceDomain(), itemId.getResourcePath()));
@@ -110,5 +124,23 @@ public class ItemMaterialHelper {
*/
public static void clearCache() {
MATERIAL_CACHE.clear();
ITEM_NAME_CACHE.clear();
}
private static NamespacedId getCachedItemName(Item item) {
NamespacedId cached = ITEM_NAME_CACHE.get(item);
if (cached != null) {
return cached;
}
String itemIdString = Item.itemRegistry.getNameForObject(item);
if (itemIdString == null) {
return null;
}
ResourceLocation itemId = new ResourceLocation(itemIdString);
cached = new NamespacedId(itemId.getResourceDomain(), itemId.getResourcePath());
ITEM_NAME_CACHE.put(item, cached);
return cached;
}
}
@@ -26,6 +26,10 @@ options.terrain_fog=Terrain Fog
options.terrain_fog.tooltip=Terrain Fog (aka View Distance Fog)
options.fog_near_distance=Near Fog Distance
options.fog_near_distance.tooltip=Distance away from the player when the view distance fog will start (0%% opacity then linear increase to render distance)
options.bobview_mode=View Bobbing Mode
options.bobview_mode.tooltip=Change the view bobbing behaviour to bob only the hand or camera.
options.bobview.hand=Hand Only
options.bobview.camera=Camera Only
options.fov.all=Enabled
options.fov.mods=Mods Only
options.fov.none=Disabled
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.mixins.early.angelica;
import com.gtnewhorizons.angelica.compat.mojang.Camera;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import jss.notfine.core.Settings;
import jss.notfine.gui.options.named.BobviewMode;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.renderer.texture.DynamicTexture;
@@ -54,4 +56,14 @@ public abstract class MixinEntityRenderer implements EntityRendererAccessor {
int value = (int) Settings.HURT_SHAKE.option.getStore();
return orig * (value/100F);
}
@WrapWithCondition(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;setupViewBobbing(F)V"))
private boolean angelica$cameraBobbing(EntityRenderer entity, float partialTicks) {
return Settings.BOBVIEW_MODE.option.getStore() != BobviewMode.HAND;
}
@WrapWithCondition(method = "renderHand", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;setupViewBobbing(F)V"))
private boolean angelica$handBobbing(EntityRenderer entity, float partialTicks) {
return Settings.BOBVIEW_MODE.option.getStore() != BobviewMode.CAMERA;
}
}
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.mixins.early.angelica;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.shader.Framebuffer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Minecraft.class)
public class MixinMinecraft_IconifyGuard {
@Redirect(
method = "runGameLoop",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;updateCameraAndRender(F)V")
)
private void angelica$skipRenderWhenIconified(EntityRenderer entityRenderer, float partialTicks) {
final Framebuffer fb = Minecraft.getMinecraft().getFramebuffer();
if (fb == null || fb.framebufferWidth < 16 || fb.framebufferHeight < 16) {
return;
}
entityRenderer.updateCameraAndRender(partialTicks);
}
}
@@ -42,10 +42,10 @@ public class MixinItemRenderer_EdgeDepth {
)
private void angelica$stencilWriteStart(CallbackInfo ci) {
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glStencilMask(0x03);
GLStateManager.glClearStencil(0);
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
}
@@ -69,7 +69,7 @@ public class MixinItemRenderer_EdgeDepth {
private void angelica$glintStart(int func) {
angelica$glintMode = true;
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
}
@@ -83,7 +83,7 @@ public class MixinItemRenderer_EdgeDepth {
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glDepthMask(true);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glStencilMask(0x03);
}
// Stencil pre-pass inside renderItemIn2D when in glint mode
@@ -102,21 +102,21 @@ public class MixinItemRenderer_EdgeDepth {
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glDepthMask(false);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
GLStateManager.glStencilMask(0x03);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
original.call(tess, minU, minV, maxU, maxV, w, h, thickness);
// Color pass: draw only at pre-pass pixels.
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 2, 0xFF);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 2, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
original.call(tess, minU, minV, maxU, maxV, w, h, thickness);
// Restore state for the next glint renderItemIn2D call
GLStateManager.glStencilMask(0x00);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import net.minecraft.client.model.ModelSkeleton;
import net.minecraft.entity.Entity;
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;
/**
* Prevents z-fighting between the skeleton's leg tops and the pelvis (bipedBody) bottom face.
*/
@Mixin(ModelSkeleton.class)
public class MixinModelSkeleton_LegPelvisZFight {
@Unique
private static final float LEG_PIVOT_Y = Float.intBitsToFloat(Float.floatToIntBits(12.0F) + 128);
@Inject(
method = "setRotationAngles(FFFFFFLnet/minecraft/entity/Entity;)V",
at = @At("TAIL")
)
private void angelica$nudgeLegsBelowPelvis(float swing, float swingAmount, float ticks,
float headYaw, float headPitch, float scale,
Entity entity, CallbackInfo ci) {
final ModelSkeleton self = (ModelSkeleton) (Object) this;
self.bipedRightLeg.rotationPointY = LEG_PIVOT_Y;
self.bipedLeftLeg.rotationPointY = LEG_PIVOT_Y;
}
}
@@ -0,0 +1,70 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.renderer.entity.RenderMooshroom;
import net.minecraft.entity.passive.EntityMooshroom;
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;
/**
* Make it so that the mushrooms on the mooshroom are also tinted when the mob is damaged.
*/
@Mixin(RenderMooshroom.class)
public class MixinRenderMooshroom_MushroomTint {
@Unique private static final float ANGELICA$RED_MIX = 1.0F - (0xB2 / 255.0F);
@Unique private boolean angelica$mushroomTintActive;
@Unique private boolean angelica$mushroomTintShaderPath;
@Inject(
method = "renderEquippedItems(Lnet/minecraft/entity/passive/EntityMooshroom;F)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V",
ordinal = 0),
require = 1, expect = 1
)
private void angelica$applyMushroomTint(EntityMooshroom entity, float partialTick, CallbackInfo ci) {
if (entity.hurtTime <= 0 && entity.deathTime <= 0) return;
angelica$mushroomTintShaderPath = angelica$isShaderPackActive();
if (angelica$mushroomTintShaderPath) {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
} else {
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
}
angelica$mushroomTintActive = true;
}
@Inject(
method = "renderEquippedItems(Lnet/minecraft/entity/passive/EntityMooshroom;F)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V",
ordinal = 2,
shift = At.Shift.AFTER),
require = 1, expect = 1
)
private void angelica$clearMushroomTint(EntityMooshroom entity, float partialTick, CallbackInfo ci) {
if (!angelica$mushroomTintActive) return;
if (angelica$mushroomTintShaderPath) {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
} else {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
}
angelica$mushroomTintActive = false;
}
@Unique
private static boolean angelica$isShaderPackActive() {
if (!AngelicaConfig.enableIris) return false;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
return pipeline != null && !(pipeline instanceof FixedFunctionWorldRenderingPipeline);
}
}
@@ -0,0 +1,179 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
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.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Single-pass damage-overlay tint, modeled on modern Minecraft's OverlayTexture.
*/
@Mixin(RendererLivingEntity.class)
public class MixinRendererLivingEntity_OverlayTint {
@Unique private static final float ANGELICA$RED_MIX = 1.0F - (0xB2 / 255.0F);
@Unique private boolean angelica$skipReRender;
@Unique private boolean angelica$ffpOverlayActive;
@Unique private boolean angelica$shaderEntityColorActive;
@Unique private boolean angelica$emissivePass;
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderModel(Lnet/minecraft/entity/EntityLivingBase;FFFFFF)V"),
require = 1, expect = 1
)
private void angelica$setupOverlay(EntityLivingBase entity, double x, double y, double z,
float yaw, float partialTick, CallbackInfo ci) {
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glDepthMask(true);
if (entity.hurtTime <= 0 && entity.deathTime <= 0) return;
if (angelica$isShaderPackActive()) {
// Iris path
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
angelica$shaderEntityColorActive = true;
} else {
// FFP path
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
angelica$ffpOverlayActive = true;
}
angelica$skipReRender = true;
}
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V"),
require = 1, expect = 1
)
private void angelica$teardownOverlay(EntityLivingBase entity, double x, double y, double z,
float yaw, float partialTick, CallbackInfo ci) {
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$ffpOverlayActive = false;
}
if (angelica$shaderEntityColorActive) {
// Don't tint held items
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$shaderEntityColorActive = false;
}
}
/**
* Skip the vanilla re-render block.
*/
@WrapOperation(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
slice = @Slice(
from = @At(value = "FIELD",
target = "Lnet/minecraft/entity/EntityLivingBase;hurtTime:I",
ordinal = 1)
),
require = 1, expect = 1
)
private void angelica$skipOverlayReRender(ModelBase model, Entity entity,
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
if (angelica$skipReRender) return;
original.call(model, entity, p1, p2, p3, p4, p5, p6);
}
/**
* Prevent bleed by saving last state and comparing with current blend state.
*/
@WrapOperation(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
require = 1, expect = 1
)
private int angelica$detectEmissivePass(RendererLivingEntity self, EntityLivingBase entity, int pass,
float partialTick, Operation<Integer> original) {
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
final int result = original.call(self, entity, pass, partialTick);
final int srcAfter = GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB);
final int dstAfter = GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB);
angelica$emissivePass = srcAfter == GL11.GL_ONE && dstAfter == GL11.GL_ONE;
return result;
}
/**
* Skip the overlay on emissive eye pass.
*/
@WrapOperation(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
slice = @Slice(
from = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
to = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
),
require = 1, expect = 1
)
private void angelica$untintEmissivePass(ModelBase model, Entity entity,
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
if (!angelica$skipReRender || !angelica$emissivePass) {
original.call(model, entity, p1, p2, p3, p4, p5, p6);
return;
}
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
original.call(model, entity, p1, p2, p3, p4, p5, p6);
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
} else if (angelica$shaderEntityColorActive) {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
original.call(model, entity, p1, p2, p3, p4, p5, p6);
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
} else {
original.call(model, entity, p1, p2, p3, p4, p5, p6);
}
}
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At("RETURN")
)
private void angelica$reset(CallbackInfo ci) {
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$ffpOverlayActive = false;
}
if (angelica$shaderEntityColorActive) {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$shaderEntityColorActive = false;
}
angelica$skipReRender = false;
angelica$emissivePass = false;
}
@Unique
private static boolean angelica$isShaderPackActive() {
if (!AngelicaConfig.enableIris) return false;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
return pipeline != null && !(pipeline instanceof FixedFunctionWorldRenderingPipeline);
}
}
@@ -19,8 +19,9 @@ public class MixinEntityRenderer {
private void updateDynamicLights(float p_78471_1_, long p_78471_2_, CallbackInfo ci) {
IDynamicLightWorldRenderer renderer = DynamicLights.getActiveRenderer();
if (renderer != null && renderer.isActive()) {
mc.mcProfiler.endStartSection("angelica_dynamic_lighting");
mc.mcProfiler.startSection("angelica_dynamic_lighting");
DynamicLights.get().updateAll(renderer);
mc.mcProfiler.endSection();
}
}
}
@@ -1,12 +1,9 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.renderer.Tessellator;
import org.spongepowered.asm.mixin.Intrinsic;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -16,43 +13,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
* except when GTNHLib needs to intercept (display list compilation, DirectTessellator capture).
*/
@Mixin(Tessellator.class)
public class MixinTessellator_CoreProfile implements ITessellatorData {
public class MixinTessellator_CoreProfile {
@Shadow public boolean isDrawing;
@Shadow public int vertexCount;
@Shadow public int[] rawBuffer;
@Shadow public int rawBufferIndex;
@Shadow public int rawBufferSize;
@Shadow public int drawMode;
@Shadow public boolean hasTexture;
@Shadow public boolean hasColor;
@Shadow public boolean hasNormals;
@Shadow public boolean hasBrightness;
@Shadow public void reset() {}
public void angelica$reset() {
reset();
}
@Inject(method = "draw", at = @At("HEAD"), cancellable = true)
@Inject(method = "draw", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;isDrawing:Z", opcode = Opcodes.GETFIELD), cancellable = true)
private void angelica$coreProfileDraw(CallbackInfoReturnable<Integer> cir) {
// Let GTNHLib handle display list compilation and DirectTessellator capture
if (TessellatorManager.shouldInterceptDraw((Tessellator)(Object)this)) return;
cir.setReturnValue(TessellatorStreamingDrawer.draw((ITessellatorData) this));
// Injecting after HEAD because GTNHLib's shouldInterceptDraw() takes priority
cir.setReturnValue(TessellatorStreamingDrawer.draw((Tessellator) (Object) this));
}
@Override public boolean isDrawing() { return isDrawing; }
@Override public void setDrawing(boolean drawing) { isDrawing = drawing; }
@Override public int getVertexCount() { return vertexCount; }
@Override public int[] getRawBuffer() { return rawBuffer; }
@Override public int getRawBufferIndex() { return rawBufferIndex; }
@Override public int getRawBufferSize() { return rawBufferSize; }
@Override public void setRawBufferSize(int size) { rawBufferSize = size; }
@Override public void setRawBuffer(int[] buffer) { rawBuffer = buffer; }
@Override public int getDrawMode() { return drawMode; }
@Override public boolean hasTexture() { return hasTexture; }
@Override public boolean hasColor() { return hasColor; }
@Override public boolean hasNormals() { return hasNormals; }
@Override public boolean hasBrightness() { return hasBrightness; }
}
@@ -2,6 +2,13 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.fontrenderer;
import com.gtnewhorizon.gtnhlib.util.font.IFontParameters;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_LENGTH;
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
import com.gtnewhorizons.angelica.client.font.ColorCodeUtils;
import com.gtnewhorizons.angelica.compat.GTNHLibCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import net.minecraft.client.gui.FontRenderer;
@@ -16,6 +23,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -110,16 +118,13 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
@Unique
public BatchingFontRenderer angelica$batcher;
@Unique
private static final char angelica$FORMATTING_CHAR = 167; // §
@Unique
private static final float angelica$1_over_255 = 1.0f/255.0f; // §
@Inject(method = "<init>", at = @At("TAIL"))
private void angelica$injectBatcher(GameSettings settings, ResourceLocation fontLocation, TextureManager texManager,
boolean unicodeMode, CallbackInfo ci) {
angelica$batcher = new BatchingFontRenderer((FontRenderer) (Object) this, this.charWidth, this.colorCode, this.locationFontTexture);
if (GTNHLibCompat.HAS_TEXT_PREPROCESSOR) {
GTNHLibCompat.registerPreprocessor();
}
}
@Unique
@@ -158,6 +163,8 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
}
else
{
text = ColorCodeUtils.convertAmpersandToSectionX(text);
if (this.bidiFlag)
{
text = this.bidiReorder(text);
@@ -226,4 +233,91 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
public float getCharWidthFine(char chr) {
return angelica$getBatcher().getCharWidthFine(chr);
}
@Inject(method = "getFormatFromString", at = @At("HEAD"), cancellable = true)
private static void angelica$getFormatFromString(String text, CallbackInfoReturnable<String> cir) {
if (!AngelicaConfig.enableRGBColors) return;
cir.setReturnValue(angelica$extractFormat(text));
}
@Unique private static final StringBuilder EXTRACT_STYLES = new StringBuilder();
@Unique private static final StringBuilder EXTRACT_EFFECTS = new StringBuilder();
@Unique private static final StringBuilder EXTRACT_COLOR = new StringBuilder();
@Unique private static final StringBuilder EXTRACT_RESULT = new StringBuilder();
@Unique
private static String angelica$extractFormat(String text) {
text = ColorCodeUtils.convertAmpersandToSectionX(text);
final int len = text.length();
if (len < 2) return "";
String lastColor = "";
final StringBuilder styles = EXTRACT_STYLES; styles.setLength(0);
final StringBuilder effects = EXTRACT_EFFECTS; effects.setLength(0);
for (int i = 0; i < len - 1; i++) {
if (text.charAt(i) != FORMATTING_CHAR) continue;
final char code = Character.toLowerCase(text.charAt(i + 1));
if (code == 'g' && i + GRADIENT_LENGTH <= len && ColorCodeUtils.isValidSectionX(text, i + 2) && ColorCodeUtils.isValidSectionX(text, i + 2 + SECTION_X_LENGTH)) {
final StringBuilder cb = EXTRACT_COLOR; cb.setLength(0);
for (int j = 0; j < GRADIENT_LENGTH; j++) cb.append(text.charAt(i + j));
lastColor = cb.toString();
styles.setLength(0);
effects.setLength(0);
i += GRADIENT_LENGTH - 1;
continue;
}
if (code == 'x' && i + SECTION_X_LENGTH <= len && ColorCodeUtils.isValidSectionX(text, i)) {
final StringBuilder cb = EXTRACT_COLOR; cb.setLength(0);
for (int j = 0; j < SECTION_X_LENGTH; j++) cb.append(text.charAt(i + j));
lastColor = cb.toString();
styles.setLength(0);
effects.setLength(0);
i += SECTION_X_LENGTH - 1;
continue;
}
if ((code >= '0' && code <= '9') || (code >= 'a' && code <= 'f')) {
lastColor = ColorCodeUtils.sectionPrefix(code);
styles.setLength(0);
effects.setLength(0);
i++;
} else if (code == 'r') {
lastColor = "";
styles.setLength(0);
effects.setLength(0);
i++;
} else if (code >= 'k' && code <= 'o') {
styles.append(ColorCodeUtils.sectionPrefix(code));
i++;
} else if (code == 'q' || code == 'z' || code == 'v') {
effects.append(ColorCodeUtils.sectionPrefix(code));
i++;
} else {
i++;
}
}
if (styles.length() == 0 && effects.length() == 0) return lastColor;
final StringBuilder result = EXTRACT_RESULT; result.setLength(0);
result.append(lastColor).append(styles).append(effects);
return result.toString();
}
@ModifyVariable(method = "getStringWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeWidth(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
}
@ModifyVariable(method = "listFormattedStringToWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeListWrap(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
}
@ModifyVariable(method = "wrapFormattedStringToWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private String angelica$convertBeforeWrap(String str) {
return ColorCodeUtils.convertAmpersandToSectionX(str);
}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.gui;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.minecraft.client.gui.GuiMainMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GuiMainMenu.class)
public abstract class MixinGuiMainMenuSplash {
@Unique
private static final String VANILLA_GL_SPLASH = "OpenGL 1.2!";
@Shadow private String splashText;
@Inject(method = "<init>", at = @At("RETURN"))
private void angelica$rewriteGlSplash(CallbackInfo ci) {
if (!VANILLA_GL_SPLASH.equals(this.splashText)) return;
final int major = RenderSystem.getGlMajor();
final int minor = RenderSystem.getGlMinor();
if (major == 0) return;
this.splashText = "OpenGL " + major + "." + minor + (RenderSystem.isCoreProfile() ? " core!" : "!");
}
}
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more