Compare commits

...
23 Commits
Author SHA1 Message Date
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
Ruling-0 edc0081b7c Fix CTM inventory texture bleed (#1721) 2026-05-04 12:25:51 -07:00
Alexdoru 913256b032 fix world client leak in CTMUtils (#1724) 2026-05-04 12:24:25 -07:00
sisyphussy e1f5083d3e Fix intel detection (#1722) 2026-05-04 12:23:50 -07:00
sisyphussy b55e693ad2 Remove DSA from GLSM & other stuff (#1723) 2026-05-04 12:23:38 -07:00
80 changed files with 2679 additions and 2320 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
@@ -520,6 +520,12 @@ public class GLStateManager {
// After Display.create(), viewport is (0, 0, width, height)
viewportState.setViewPort(0, 0, displayWidth, displayHeight);
}
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
final String os = System.getProperty("os.name").toLowerCase();
windows = os.contains("win");
}
static void init(Runnable initCallback) {
@@ -527,13 +533,6 @@ public class GLStateManager {
DEFAULT_DRAW_BUFFER = RENDER_BACKEND.getInteger(GL11.GL_DRAW_BUFFER);
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
final String os = System.getProperty("os.name").toLowerCase();
windows = os.contains("win");
// The initial mask value should be defined as all 1's. However, some drivers have it set to 0's.
// To ensure consistency & correctness across all drivers, we're setting them to 0xFF.
RENDER_BACKEND.stencilFunc(stencilState.getFuncFront(), stencilState.getRefFront(), 0xFF);
@@ -627,16 +626,6 @@ public class GLStateManager {
return BYPASS_CACHE || !isCachingEnabled();
}
/**
* Returns true if we should use DSA for texture operations. DSA relies on cached texture bindings, which aren't tracked when caching is disabled. Also,
* proxy textures are special query textures that shouldn't use DSA.
*/
private static boolean shouldUseDSA(int target) {
if (!isCachingEnabled()) return false;
// Proxy textures are for capability queries, not real textures
return target != GL11.GL_PROXY_TEXTURE_1D && target != GL11.GL_PROXY_TEXTURE_2D && target != GL12.GL_PROXY_TEXTURE_3D;
}
// LWJGL Overrides
public static void glEnable(int cap) {
final RecordMode mode = DisplayListManager.getRecordMode();
@@ -1834,13 +1823,7 @@ public class GLStateManager {
}
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
suspendPixelUnpackBuffer();
if (shouldUseDSA(target)) {
// Use DSA to upload directly to the texture
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
} else {
// Non-main thread or proxy texture - use direct GL call
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
}
@@ -1885,11 +1868,7 @@ public class GLStateManager {
}
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels != null ? pixels.asIntBuffer() : null);
suspendPixelUnpackBuffer();
if (shouldUseDSA(target)) {
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
} else {
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
}
@@ -2012,7 +1991,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);
@@ -2118,22 +2099,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);
}
}
@@ -2248,7 +2227,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);
@@ -3369,7 +3347,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;
}
@@ -3383,8 +3360,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;
}
@@ -3404,8 +3379,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;
}
@@ -3445,8 +3418,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;
}
@@ -3458,10 +3429,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);
@@ -3470,10 +3440,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);
@@ -3482,10 +3451,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);
@@ -3494,10 +3462,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);
@@ -3511,10 +3478,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);
@@ -3522,6 +3488,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();
@@ -3529,10 +3502,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);
@@ -3540,20 +3512,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();
}
}
@@ -3561,24 +3537,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);
@@ -3587,10 +3568,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);
@@ -4412,6 +4392,7 @@ public class GLStateManager {
}
public static int glGetError() {
if (initConfig.noErrorChecks()) return 0;
if (!RENDER_BACKEND.hasContext()) return 0;
return RENDER_BACKEND.getError();
}
@@ -4467,13 +4448,7 @@ public class GLStateManager {
}
}
suspendPixelUnpackBuffer();
if (shouldUseDSA(target)) {
// Use DSA to upload directly to the texture - keeps GL binding state unchanged
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels);
} else {
// Non-main thread or proxy texture - use direct GL call
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
}
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
}
@@ -4486,13 +4461,7 @@ public class GLStateManager {
}
}
suspendPixelUnpackBuffer();
if (shouldUseDSA(target)) {
// Use DSA to upload directly to the texture
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels);
} else {
// Non-main thread or proxy texture - use direct GL call
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
}
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
}
@@ -4996,25 +4965,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);
}
@@ -5212,7 +5181,7 @@ public class GLStateManager {
}
public static boolean isFramebufferEnabled() {
return initConfig != null && initConfig.isFramebufferSupported() && initConfig.isFboEnabled();
return true;
}
private static final boolean FFP_WARN_ON_UNSUPPORTED = Boolean.parseBoolean(System.getProperty("angelica.ffp.warnOnUnsupported", "false"));
@@ -5947,4 +5916,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;
}
}
@@ -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.
@@ -315,10 +316,10 @@ 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);
}
@@ -1,5 +1,6 @@
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;
@@ -182,7 +183,7 @@ public final class VertexKey {
// Check if texture matrix unit 0 is non-identity
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
if (!DisplayListManager.isIdentity(texMat)) {
if (!MatrixHelper.isIdentity(texMat)) {
bits |= (1L << BIT_TEX_MATRIX);
}
@@ -8,20 +8,18 @@ import java.util.function.Consumer;
public final class GLSMInitConfig {
private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final boolean framebufferSupported;
private final boolean fboEnabled;
private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy;
private final int displayWidth;
private final int displayHeight;
private final Runnable postInitCallback;
private final boolean enableDSA;
private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.framebufferSupported = builder.framebufferSupported;
this.fboEnabled = builder.fboEnabled;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
@@ -36,26 +34,24 @@ public final class GLSMInitConfig {
public boolean isLwjglDebug() { return lwjglDebug; }
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
public boolean isFramebufferSupported() { return framebufferSupported; }
public boolean isFboEnabled() { return fboEnabled; }
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
public int getDisplayWidth() { return displayWidth; }
public int getDisplayHeight() { return displayHeight; }
public Runnable getPostInitCallback() { return postInitCallback; }
public boolean isDSAEnabled() { return enableDSA; }
public boolean noErrorChecks() { return noErrorChecks; }
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private boolean framebufferSupported = false;
private boolean fboEnabled = false;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private boolean enableDSA = false;
private boolean noErrorChecks;
private Builder() {}
@@ -69,13 +65,8 @@ public final class GLSMInitConfig {
return this;
}
public Builder framebufferSupported(boolean framebufferSupported) {
this.framebufferSupported = framebufferSupported;
return this;
}
public Builder fboEnabled(boolean fboEnabled) {
this.fboEnabled = fboEnabled;
public Builder noErrorChecks(boolean noErrorChecks) {
this.noErrorChecks = noErrorChecks;
return this;
}
@@ -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;
}
@@ -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);
@@ -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
@@ -302,4 +331,9 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean disableGLVersionPinning;
@Config.Comment("Disables GL Error checks. Always set to false in dev env or if LWJGL debug is on. Improves performance.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean disableErrorChecks;
}
@@ -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 || !GLStateManager.isFramebufferEnabled()) {
if (!AngelicaConfig.hudCachingActive || mc.displayWidth < 16 || mc.displayHeight < 16) {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
return;
}
@@ -11,6 +11,7 @@ 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;
@@ -29,6 +30,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;
@@ -101,14 +104,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
@@ -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;
}
}
@@ -0,0 +1,98 @@
package com.gtnewhorizons.angelica.loading.fml.transformers;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
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 class IsbrhTessellatorAbuseClassTransformer implements IClassTransformer {
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";
private final String draw;
private final String startDrawingQuads;
private final String startDrawing;
public IsbrhTessellatorAbuseClassTransformer(boolean isObf) {
this.draw = isObf ? "func_78381_a" : "draw";
this.startDrawingQuads = isObf ? "func_78382_b" : "startDrawingQuads";
this.startDrawing = isObf ? "func_78371_b" : "startDrawing";
}
public boolean shouldTransform(byte[] classBytes) {
return CST_POOL_PARSER.find(classBytes);
}
public boolean transformClassNode(String transformedName, ClassNode cn) {
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;
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;
}
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (basicClass == null || !shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (!transformClassNode(transformedName, cn)) 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,8 @@ package com.gtnewhorizons.angelica.loading.fml.tweakers;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower;
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
import com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseClassTransformer;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.ITweaker;
@@ -60,6 +62,10 @@ public class AngelicaLateTweaker implements ITweaker {
final List<IClassTransformer> transformers = (List<IClassTransformer>) transformersField.get(Launch.classLoader);
transformers.removeIf(t -> t.getClass().getName().equals(EARLY_REDIRECTOR_CLASS));
final IClassTransformer isbrhAbuse = new IsbrhTessellatorAbuseClassTransformer(AngelicaClientTweaker.isObfEnv());
FMLRelaunchLog.finer("Registering transformer %s", isbrhAbuse.getClass().getName());
transformers.add(isbrhAbuse);
} catch (Exception e) {
FMLRelaunchLog.warning("[Angelica] Failed to remove EarlyRedirectorTransformer: %s", e.getMessage());
}
@@ -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,56 @@
package com.gtnewhorizons.angelica.loading.rfb.transformers;
import com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseClassTransformer;
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
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 IsbrhTessellatorAbuseClassTransformer} */
public class RFBIsbrhTessellatorAbuseTransformer implements RfbClassTransformer {
private final IsbrhTessellatorAbuseClassTransformer inner;
public RFBIsbrhTessellatorAbuseTransformer(boolean isObf) {
inner = new IsbrhTessellatorAbuseClassTransformer(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(IsbrhTessellatorAbuseClassTransformer.ISBRH);
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
if (inner.transformClassNode(className, classNode.getNode())) {
classNode.computeMaxs();
AngelicaClassDump.dumpRFBClass(className, classNode, this);
}
}
}
@@ -35,9 +35,11 @@ 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"
@@ -70,6 +72,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 +130,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 +205,7 @@ public enum Mixins implements IMixins {
, "rendering.MixinTileEntity"
, "rendering.MixinTileEntityMobSpawner"
, "rendering.MixinTileEntityRendererDispatcher"
, "rendering.MixinRenderBlocksEmissive"
)
),
@@ -388,7 +400,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,6 +22,7 @@ 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;
@@ -102,6 +103,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();
}
@@ -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);
}
}
}
}
@@ -0,0 +1,74 @@
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 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);
}
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;
@@ -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();
}
@@ -180,6 +184,7 @@ public class CTMUtils {
local.renderBlockState.setBlock(block, blockAccess, x, y, z);
local.renderBlockState.setFace(face);
TileOverride lastOverride = local.ijkIterator.go(local.renderBlockState, icon);
local.renderBlockState.blockAccess = null;
if (lastOverride != null) {
return skipDefaultRendering(block) ? RenderBlocksUtils.blankIcon : local.ijkIterator.getIcon();
}
@@ -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);
@@ -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);
@@ -534,4 +534,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) {
@@ -113,10 +113,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() {
@@ -1560,6 +1560,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.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;
@@ -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);
}
}
@@ -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;
}
}
@@ -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!" : "!");
}
}
@@ -27,9 +27,7 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.itemrenderer;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.rendering.ItemRenderListManager;
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.renderer.ItemRenderer;
@@ -0,0 +1,46 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.itemrenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(RenderBlocks.class)
public abstract class MixinRenderBlocks {
@Shadow
public boolean useInventoryTint;
@Shadow
public boolean enableAO;
@Shadow
public IIcon overrideBlockTexture;
@WrapMethod(method = "renderBlockAsItem")
private void angelica$cacheBlockItemRenderer(Block block, int meta, float brightness, Operation<Void> original) {
if (BlockRenderListManager.isISBRH(block.getRenderType())
|| enableAO
|| this.overrideBlockTexture != null
|| brightness != 1.0F
|| !this.useInventoryTint
) {
// Do not cache those
original.call(block, meta, brightness);
return;
}
int list = BlockRenderListManager.getDisplayList(block, meta);
if (list == 0) {
list = BlockRenderListManager.startCompiling();
original.call(block, meta, brightness);
BlockRenderListManager.endCompiling(list, block, meta);
}
GLStateManager.glCallList(list);
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.startup;
import com.gtnewhorizon.gtnhlib.core.GTNHLibCore;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
@@ -25,13 +26,13 @@ public class MixinInitGLStateManager {
@Inject(method = "initializeTextures", at = @At("RETURN"))
private static void angelica$initializeGLStateManager(CallbackInfo ci) {
final Minecraft mc = Minecraft.getMinecraft();
mc.gameSettings.fboEnable = true; // Angelica & many other GTNH features require FBO's
GLStateManager.setDrawableGL(Display.getDrawable());
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(mc.displayWidth, mc.displayHeight)
.lwjglDebug(AngelicaMod.lwjglDebug)
.streamingUploadStrategy(ClientProxy.options().advanced.streamingUploadStrategy)
.framebufferSupported(OpenGlHelper.framebufferSupported)
.fboEnabled(mc.gameSettings.fboEnable)
.noErrorChecks(AngelicaConfig.disableErrorChecks && GTNHLibCore.isObf() && !AngelicaMod.lwjglDebug)
.enableDSA(AngelicaConfig.enableDSA)
.directDrawer(TessellatorStreamingDrawer::drawDirect)
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
@@ -8,7 +8,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.prupe.mcpatcher.ctm.CTMUtils;
import com.prupe.mcpatcher.ctm.CompactCtmQuadProcessor;
import com.prupe.mcpatcher.ctm.TileOverrideImpl;
import com.prupe.mcpatcher.ctm.RenderBlockState;
import cpw.mods.fml.client.registry.RenderingRegistry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -20,7 +20,6 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
@@ -152,7 +151,8 @@ public abstract class MixinRenderBlocks {
private boolean handleISBRHException(RenderBlocks rb, IBlockAccess world, int x, int y, int z, Block block, int modelId) {
try {
return RenderingRegistry.instance().renderWorldBlock(rb, world, x, y, z, block, modelId);
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
} catch (Exception e) {
CTMUtils.clearCurrentCompact();
// Render Error Block
int meta = exceptionErrorBlockMap.getOrDefault(e.getClass(), 0);
rb.overrideBlockTexture = BlockError.icons[exceptionErrorBlockMap.getOrDefault(e.getClass(), 0)];
@@ -168,19 +168,16 @@ public abstract class MixinRenderBlocks {
return false;
}
@ModifyExpressionValue(method = { "renderFaceXPos", "renderFaceYPos", "renderFaceZPos",
"renderFaceXNeg", "renderFaceYNeg", "renderFaceZNeg" },
at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z"))
@ModifyExpressionValue(method = { "renderStandardBlockWithColorMultiplier" },
at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/renderer/RenderBlocks;renderAllFaces:Z"))
private boolean applyAOBrightness(boolean original, @Local(ordinal = 0) Tessellator tessellator) {
((StateAwareTessellator)tessellator).angelica$setAppliedAo(this.applyingCeleritasAO);
return original;
}
@Inject(method = { "renderFaceXPos", "renderFaceYPos", "renderFaceZPos",
"renderFaceXNeg", "renderFaceYNeg", "renderFaceZNeg" },
@Inject(method = { "renderStandardBlockWithColorMultiplier" },
at = @At("RETURN"))
private void resetAOFlag(Block p_147764_1_, double p_147764_2_, double p_147764_4_, double p_147764_6_, IIcon p_147764_8_, CallbackInfo ci,
@Local(ordinal = 0) Tessellator tessellator) {
private void resetAOFlag(Block p_147736_1_, int p_147736_2_, int p_147736_3_, int p_147736_4_, float p_147736_5_, float p_147736_6_, float p_147736_7_, CallbackInfoReturnable<Boolean> cir, @Local(ordinal = 0) Tessellator tessellator) {
((StateAwareTessellator)tessellator).angelica$setAppliedAo(false);
}
@@ -188,80 +185,51 @@ public abstract class MixinRenderBlocks {
public IBlockAccess blockAccess;
@Unique
private boolean compactCtmRendering = false;
@Unique
private CompactCtmQuadProcessor getCompactProcessor() {
TileOverrideImpl.CTMCompact compact = CTMUtils.getCurrentCompact();
if (compact == null) {
return null;
}
return compact.getProcessor();
}
private void handleCompactCtmFace(int face, Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
// recursion protection
if (compactCtmRendering) {
private void angelica$handleCompactCtmFace(IIcon icon, CallbackInfo ci) {
CTMUtils.CTMCompactContext ctx = CTMUtils.getCurrentCompact();
if (ctx == null) {
return;
}
CompactCtmQuadProcessor processor = ctx.compact().getProcessor();
RenderBlockState renderBlockState = ctx.renderBlockState();
CTMUtils.clearCurrentCompact();
if (this.blockAccess == null) {
return;
}
CompactCtmQuadProcessor processor = getCompactProcessor();
if (processor == null) {
return;
}
compactCtmRendering = true;
try {
boolean handled = processor.processFace(
(RenderBlocks)(Object)this,
this.blockAccess,
MathHelper.floor_double(x),
MathHelper.floor_double(y),
MathHelper.floor_double(z),
face,
icon
);
if (handled) {
ci.cancel();
}
} finally {
compactCtmRendering = false;
if (processor.processFace((RenderBlocks)(Object)this, renderBlockState, icon)) {
ci.cancel();
}
}
@Inject(method = "renderFaceYNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(0, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderFaceYPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(1, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderFaceZNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(2, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderFaceZPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(3, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderFaceXNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(4, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderFaceXPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(5, block, x, y, z, icon, ci);
angelica$handleCompactCtmFace(icon, ci);
}
@Inject(method = "renderStandardBlock(Lnet/minecraft/block/Block;III)Z", at = @At("RETURN"))
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.mixins.early.rendering;
import com.gtnewhorizons.angelica.render.EmissiveTextureHelper;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.world.IBlockAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderBlocks.class)
public class MixinRenderBlocksEmissive {
@Inject(method = "renderStandardBlock", at = @At("TAIL"))
private void angelica$renderEmissiveOverlay(Block block, int x, int y, int z, CallbackInfoReturnable<Boolean> cir) {
RenderBlocks self = (RenderBlocks)(Object)this;
IBlockAccess world = self.blockAccess;
if (world == null) return;
int meta = world.getBlockMetadata(x, y, z);
boolean hasEmissive = false;
for (int side = 0; side < 6; side++) {
if (EmissiveTextureHelper.getEmissiveIcon(block, side, meta) != null) {
hasEmissive = true;
break;
}
}
if (!hasEmissive) return;
Tessellator tess = Tessellator.instance;
tess.setBrightness(0xF000F0);
EmissiveTextureHelper.renderEmissiveBlockOverlay(self, block, x, y, z, meta);
}
}
@@ -28,7 +28,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* Ported with additional fixes from the ShaderFixer mod
* @author kotmatross28729
*/
@Mixin(value = SkyProviderCelestial.class, priority = 100)
@Mixin(value = SkyProviderCelestial.class, priority = 100, remap = false)
public class MixinSkyProviderCelestial {
/**
@@ -38,7 +38,9 @@ public class MixinSkyProviderCelestial {
"renderSunset",
"renderCelestials",
"renderAtmosphereGlow",
}, at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
}
, at = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"))
private boolean iris$common$redirectTex2D(int cap) {
return this.iris$common$redirectTexture(cap);
}
@@ -46,9 +48,11 @@ public class MixinSkyProviderCelestial {
/**
* Avoid program rebinding due to pipeline.setInputs | Doesn't affect texture disabling before glSkyList
*/
@WrapWithCondition(method = "render",
slice = @Slice(from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V", ordinal = 1)),
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
@WrapWithCondition(method = "render"
, slice = @Slice(from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"
, ordinal = 1))
, at = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"))
private boolean iris$common$redirectTex2DException(int cap) {
return this.iris$common$redirectTexture(cap);
}
@@ -65,66 +69,98 @@ public class MixinSkyProviderCelestial {
/**
* Force skybox to render with the default program | Fix various issues with shaders enabled
*/
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"), remap = false)
@Inject(method = "render"
, at = @At(value = "INVOKE"
, target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"
, remap = true))
public void iris$main$renderInDefaultProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
main$previousProgram.set(GLStateManager.getActiveProgram());
GLStateManager.glUseProgram(0);
}
@WrapOperation(method = "render",
at = @At(value = "INVOKE", target = "Lcom/hbm/dim/SkyProviderCelestial;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"), remap = false)
@WrapOperation(method = "render"
, at = @At(value = "INVOKE"
, target = "Lcom/hbm/dim/SkyProviderCelestial;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"))
private void iris$main$renderSunInShaderProgram(SkyProviderCelestial instance, float partialTicks, WorldClient world, Minecraft mc, CelestialBody sun, double sunSize, double coronaSize, float visibility, float pressure, Operation<Void> original, @Share("main$previousProgram") LocalIntRef main$previousProgram){
GLStateManager.glUseProgram(main$previousProgram.get());
original.call(instance, partialTicks, world, mc, sun, sunSize, coronaSize, visibility, pressure);
GLStateManager.glUseProgram(0);
}
@Inject(method = "render", at = @At(value = "TAIL"), remap = false)
@Inject(method = "render"
, at = @At(value = "TAIL"))
public void iris$main$restorePreviousProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
GLStateManager.glUseProgram(main$previousProgram.get());
}
@Inject(method = "renderSun", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"),
slice = @Slice(
from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V", ordinal = 0),
to = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V", ordinal = 1)
@Inject(method = "renderSun"
, at = @At(value = "INVOKE"
, target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"
, remap = true)
, slice = @Slice(
from = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V"
, ordinal = 0),
to = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V"
, ordinal = 1)
)
)
private void iris$sun$disableBlanking(CallbackInfo ci) {
Tessellator.instance.vertexCount = 0;
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"),
slice = @Slice(
from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 1),
to = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 2)
@Inject(method = "render"
, at = @At(value = "INVOKE"
, target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"
, remap = true)
, slice = @Slice(
from = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V"
, ordinal = 1),
to = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V"
, ordinal = 2)
)
)
private void iris$main$disableVoid(CallbackInfo ci) {
Tessellator.instance.vertexCount = 0;
}
@WrapWithCondition(method = "render", at = {
@At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 1),
@At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 2)
}, remap = false)
@WrapWithCondition(method = "render"
, at = {
@At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V"
, ordinal = 1),
@At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V"
, ordinal = 2)
})
private boolean angelica$renderHorizon(int i) {
return (boolean) Settings.HORIZON.option.getStore();
}
@Inject(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;", opcode = Opcodes.GETSTATIC))
@Inject(method = "render"
, at = @At(value = "FIELD"
, target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;"
, remap = true
, opcode = Opcodes.GETSTATIC))
private void iris$renderSky$beginNormalSky(CallbackInfo ci) {
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SKY));
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glRotatef(FFFF)V", ordinal = 2), remap = false)
@Inject(method = "render"
, at = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glRotatef(FFFF)V"
, ordinal = 2))
private void iris$renderSky$tiltSun(CallbackInfo ci) {
Iris.getPipelineManager().getPipeline().ifPresent(
p -> GLStateManager.glRotatef(p.getSunPathRotation(), 0.0F, 0.0F, 1.0F)
);
}
@Inject(method = "renderSun", at = @At(value = "HEAD"), remap = false)
@Inject(method = "renderSun"
, at = @At(value = "HEAD"))
private void iris$setSunRenderStage(CallbackInfo ci) {
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SUN));
}
@@ -8,13 +8,15 @@ import org.lwjgl.opengl.GL11;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(value = SkyProviderLaytheSunset.class, priority = 100)
@Mixin(value = SkyProviderLaytheSunset.class, priority = 100, remap = false)
public class MixinSkyProviderLaytheSunset {
/**
* Avoid program rebinding due to pipeline.setInputs
*/
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
@WrapWithCondition(method = "renderSunset"
, at = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"))
private boolean iris$sunset$redirectTex2D(int cap) {
if(cap == GL11.GL_TEXTURE_2D) {
Minecraft.getMinecraft().renderEngine.bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
@@ -17,13 +17,15 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = SkyProviderOrbit.class, priority = 100)
@Mixin(value = SkyProviderOrbit.class, priority = 100, remap = false)
public class MixinSkyProviderOrbit {
/**
* Avoid program rebinding due to pipeline.setInputs
*/
@WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
@WrapWithCondition(method = "render"
, at = @At(value = "INVOKE"
, target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"))
private boolean iris$common$redirectTex2D(int cap) {
if (cap == GL11.GL_TEXTURE_2D) {
Minecraft.getMinecraft().renderEngine.bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
@@ -35,24 +37,26 @@ public class MixinSkyProviderOrbit {
/**
* Force skybox to render with the default program | Fix various issues with shaders enabled
*/
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"), remap = false)
@Inject(method = "render"
, at = @At(value = "INVOKE"
, target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"
, remap = true))
public void iris$main$renderInDefaultProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
main$previousProgram.set(GLStateManager.getActiveProgram());
GLStateManager.glUseProgram(0);
}
@WrapOperation(method = "render",
at = @At(value = "INVOKE", target = "Lcom/hbm/dim/orbit/SkyProviderOrbit;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"), remap = false)
@WrapOperation(method = "render"
, at = @At(value = "INVOKE"
, target = "Lcom/hbm/dim/orbit/SkyProviderOrbit;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"))
private void iris$main$renderSunInShaderProgram(SkyProviderOrbit instance, float partialTicks, WorldClient world, Minecraft mc, CelestialBody sun, double sunSize, double coronaSize, float visibility, float pressure, Operation<Void> original, @Share("main$previousProgram") LocalIntRef main$previousProgram){
GLStateManager.glUseProgram(main$previousProgram.get());
original.call(instance, partialTicks, world, mc, sun, sunSize, coronaSize, visibility, pressure);
GLStateManager.glUseProgram(0);
}
/**
* Sets the program back before {@link #iris$main$renderInDefaultProgram(CallbackInfo, LocalIntRef)} call
*/
@Inject(method = "render", at = @At(value = "TAIL"), remap = false)
@Inject(method = "render"
, at = @At(value = "TAIL"))
public void iris$main$restorePreviousProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
GLStateManager.glUseProgram(main$previousProgram.get());
}
@@ -73,8 +73,8 @@ public class UmbraClientTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
"Missing dependency: Umbra requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
"GTNHLib is outdated: Umbra requires GTNHLib 0.9.0+! Download: https://modrinth.com/mod/gtnhlib")
"/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
));
}