Compare commits

..
27 Commits
Author SHA1 Message Date
tiffit 5d1fabfca9 Add API for allowing other mods to have celeritas support (#1936) 2026-07-16 09:54:55 -05:00
tiffit 555b2b7dd2 Fix typo in PR template (#1937) 2026-07-14 00:10:41 -05:00
SuperSouprandJason Mitchell 7017786a7c CTM integration improvements (#1931)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-12 10:45:16 -05:00
Eclipse-Sol 1c49649685 Fix Streams not rendering correctly (#1932) 2026-07-12 10:41:36 -05:00
Caedis 2bae79c4ee Up default zoom speed to 20 (#1910) 2026-07-09 21:34:03 -04:00
Jason Mitchell c22935660a Reenable compute shaders (#1918) 2026-07-05 20:38:13 -07:00
tiffit 79c488102b CTM: Fix "flipped" value not set to false in setFace() (#1913) 2026-07-05 18:24:28 -07:00
Jason Mitchell cc1353b73a Fix infinite farplane causing incorrect culling in pdim with no clouds (#1917) 2026-07-05 18:18:17 -07:00
Eclipse-Sol d1e6dc8c94 Fix/bug fixes (#1905) 2026-07-04 13:06:36 -07:00
GribanovIvan ecbf8fea2f font: load bundled .ttf/.otf fonts from fontfiles/ and config/angelia/fonts/ (#1904) 2026-07-04 12:16:48 -07:00
tiffit 610209605f Compact CTM: Decouple block face and rendered face (#1907) 2026-07-04 03:22:04 +03:00
mist475andJason Mitchell 112aae3735 Fix lotr biome colours again (essentially copy of https://github.com/GTNewHorizons/Angelica/pull/25) (#1902)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-03 14:21:05 -07:00
RecursivePineapple 5844b1b5c1 Cubic Chunks Compatibility (#1808) 2026-07-03 12:41:04 -07:00
Jason Mitchell 05f01c17e6 No point continuously building clouds if we're not rendering them... (#1900) 2026-06-29 20:17:06 -07:00
SpacEagle17 f07d96316a Add shader setting search functionality and center selection (#1897) 2026-06-29 19:36:55 -07:00
Jason Mitchell 5b704d1bc2 Fix space elevator cable (#1896) 2026-06-28 16:33:58 -07:00
Jason Mitchell 26135124f4 Support more glDrawElement overloads in display lists (#1895) 2026-06-28 15:07:24 -07:00
Jason Mitchell 3abc89b7b2 Betweenlands fix (#1894) 2026-06-28 13:46:10 -07:00
Jason Mitchell 03d908d658 Update readme and version detectors (#1893) 2026-06-27 15:47:32 -07:00
Eclipse-Sol 76bf19c8f6 Majority shader fixes (#1868) 2026-06-27 14:48:51 -07:00
Eclipse-Sol b27834cbd9 Narrow and make damage overlay a config option (#1886) 2026-06-27 10:12:39 -07:00
sisyphussyandJason Mitchell eb0ecb0483 Fix deps (#1888)
Co-authored-by: Jason Mitchell <[email protected]>
2026-06-27 10:11:30 -07:00
Caedis 9acb0e67a7 feat: add smooth zoom option (#1890) 2026-06-27 09:57:21 -07:00
Jason Mitchell f4f9b56b17 End portal fix (#1891) 2026-06-27 09:56:21 -07:00
Midnight145 2196439e6c ClientEvent framework (primarily written for LiteloaderLoader) (#1843) 2026-06-25 20:13:18 -07:00
RedNicStone dd68c64239 Fix map rendering issue in xaero's map mods (#1880) 2026-06-21 14:13:11 -07:00
Jason Mitchell 0decc515e4 Fix universium shader (#1877) 2026-06-18 23:42:21 -07:00
142 changed files with 4212 additions and 704 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
# Description or your *PR* and other info
# Description of your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
+1 -1
View File
@@ -23,7 +23,7 @@ Targets GL 3.3+ core profile
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
+7 -2
View File
@@ -76,7 +76,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.11.4:dev")
api("com.github.GTNewHorizons:GTNHLib:0.11.19:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
@@ -90,7 +90,7 @@ dependencies {
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
@@ -173,8 +173,13 @@ dependencies {
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
testRuntimeOnly("it.unimi.dsi:fastutil:8.5.18")
testRuntimeOnly("org.joml:joml:1.10.8")
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
}
+2
View File
@@ -96,6 +96,8 @@ dependencies {
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}")
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false }
// Test
testImplementation(sourceSets["stubs"].output)
+3 -1
View File
@@ -4,7 +4,9 @@ celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.10.0
gtnhlibVersion = 0.11.19
fastutilVersion = 8.5.18
jomlVersion = 1.10.8
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
@@ -1,5 +1,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -8,9 +13,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -182,8 +189,9 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
final FFPInputs ffp = FFPInputs.detect(source);
assignVertexInputLocations(transformer, ffp);
transformVertexAttributes(transformer, source, ffp);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
@@ -310,25 +318,25 @@ public class CompatShaderTransformer {
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
if (ffp.position()) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
if (ffp.color()) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
if (ffp.uv0()) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
if (ffp.uv1()) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
if (ffp.normal()) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
@@ -337,6 +345,145 @@ public class CompatShaderTransformer {
}
}
private static final int MAX_VERTEX_ATTRIBS = 16;
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
static FFPInputs detect(String source) {
return new FFPInputs(
source.contains("gl_Vertex") || source.contains("ftransform"),
source.contains("gl_Color"),
source.contains("gl_MultiTexCoord0"),
source.contains("gl_MultiTexCoord1"),
source.contains("gl_Normal"));
}
}
/** Adaptation of Mesa's assign_attribute_or_color_locations */
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
int used = 0;
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
final List<Input> toAssign = new ArrayList<>();
int index = 0;
for (TerminalNode storage : transformer.collectStorage()) {
final int tokenType = storage.getSymbol().getType();
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
ParseTree node = storage.getParent();
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
node = node.getParent();
}
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
if (decl.typeless_declaration() == null) continue;
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
final String typeArray = arraySuffix(typeSpec.array_specifier());
if (hasLayoutQualifier(decl)) {
final int loc = explicitLocation(decl);
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
if (loc >= 0) used |= slotMask(slots) << loc;
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
}
}
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
for (Input in : toAssign) {
final int loc = findAvailableSlots(used, in.slots());
if (loc < 0) continue;
used |= slotMask(in.slots()) << loc;
transformer.removeVariable(in.name());
transformer.variable = null;
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
}
}
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
}
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
if (arraySpec == null) return 1;
int product = 1;
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
if (dim.constant_expression() == null) continue;
try {
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {}
}
return product;
}
private static int baseSlots(String type) {
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
return dims.charAt(0) - '0';
}
return 1;
}
private static int slotMask(int slots) {
return (slots >= 32) ? -1 : (1 << slots) - 1;
}
private static int findAvailableSlots(int used, int needed) {
if (needed <= 0) return -1;
final int mask = slotMask(needed);
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
if (((mask << i) & ~used) == (mask << i)) return i;
}
return -1;
}
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return -1;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
if (layout == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
try {
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {
return -1;
}
}
}
}
return -1;
}
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
if (single.layout_qualifier() != null) return true;
}
return false;
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
@@ -133,7 +133,10 @@ public class DisplayListManager {
}
public static UnsupportedOperationException unsupportedInList(String what) {
return new UnsupportedOperationException(what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
final UnsupportedOperationException e = new UnsupportedOperationException( what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
GLStateManager.warnOnce("unsupported:" + what, "Aborting display list {} compilation: {} unsupported during recording", glListId, what);
abortCompilation();
return e;
}
public static String describeCurrentCompilation() {
@@ -798,7 +801,8 @@ public class DisplayListManager {
*/
public static void glEndList() {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
try {
finishCurrentList();
@@ -906,17 +910,56 @@ public class DisplayListManager {
// Parent's COMPILING callback is now active again
} else {
// Not nested - clear all compilation state
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
glListId = -1;
glListMode = 0;
resetCompilationState();
}
}
/** Clear all root-level compilation state back to "not recording". */
private static void resetCompilationState() {
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
glListId = -1;
glListMode = 0;
}
public static void abortCompilation() {
if (glListMode == 0 && compilationStack.isEmpty()) {
return;
}
final int levels = compilationStack.size() + (glListMode != 0 ? 1 : 0);
for (int i = 0; i < levels; i++) {
try {
TessellatorManager.stopCapturingDirect();
} catch (IllegalStateException ignored) {
break;
}
}
if (currentRecorder != null) {
currentRecorder.delete();
}
for (CompilationContext ctx : compilationStack) {
if (ctx.recorder() != null) {
ctx.recorder().delete();
}
}
compilationStack.clear();
resetCompilationState();
}
public static void abortIfLeaked() {
if (glListMode != 0 || !compilationStack.isEmpty()) {
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
abortCompilation();
}
}
@@ -16,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
@@ -94,6 +95,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntSupplier;
import static com.gtnewhorizons.angelica.glsm.Vendor.AMD;
@@ -119,6 +121,12 @@ public class GLStateManager {
public static final Logger LOGGER = LogManager.getLogger("GLSM");
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
public static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) LOGGER.warn(fmt, args);
}
// Thread Checking - must be early in static init order so isMainThread() works for state initialization
@Getter private static final Thread MainThread = Thread.currentThread();
@@ -187,6 +195,14 @@ public class GLStateManager {
private static boolean dirtyTexCoordAttrib;
private static boolean dirtyLightmapAttrib = true;
private static boolean unit23TexCoordSetDuringDraw = false;
public static boolean consumeUnit23TexCoordSetDuringDraw() {
boolean v = unit23TexCoordSetDuringDraw;
unit23TexCoordSetDuringDraw = false;
return v;
}
// vertexFlags bits mark attribs the VBO already supplies; FFP supplies the rest via u_Current* uniforms.
// Skip the backend vertexAttrib call in either case.
public static void flushDeferredVertexAttribs(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
@@ -1605,6 +1621,11 @@ public class GLStateManager {
ImmediateModeRecorder.setColor(red, green, blue, alpha);
}
dirtyColorAttrib = true;
// Only set while an enchantment-glint span is active
final GlintColorHandler glintHandler = GLSMHooks.glintColorHandler;
if (glintHandler != null) {
glintHandler.onColorChanged(red, green, blue, alpha);
}
return true;
}
return false;
@@ -2158,6 +2179,7 @@ public class GLStateManager {
}
public static void glBegin(int mode) {
unit23TexCoordSetDuringDraw = false;
if (DisplayListManager.isRecording()) {
ImmediateModeRecorder.begin(mode);
return;
@@ -2233,70 +2255,99 @@ public class GLStateManager {
}
public static void glDrawElements(int mode, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
CommandRecorder savedRecorder = null;
final RecordMode recordMode = DisplayListManager.getRecordMode();
if (recordMode != RecordMode.NONE) {
final IndexedDrawCapture capture = IndexedDrawCapture.createFromClientIndices(mode, indices.remaining(), GL11.GL_UNSIGNED_BYTE, MemoryUtilities.memAddress(indices), indices.remaining());
if (capture != null) DisplayListManager.recordIndexedDrawCapture(capture);
if (recordMode == RecordMode.COMPILE) return;
savedRecorder = DisplayListManager.pauseRecording();
} else if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices.remaining(), GL11.GL_UNSIGNED_BYTE, indices);
return;
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices.remaining(), GL11.GL_UNSIGNED_BYTE, indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, IntBuffer indices) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
CommandRecorder savedRecorder = null;
final RecordMode recordMode = DisplayListManager.getRecordMode();
if (recordMode != RecordMode.NONE) {
final IndexedDrawCapture capture = IndexedDrawCapture.createFromClientIndices(mode, indices.remaining(), GL11.GL_UNSIGNED_INT, MemoryUtilities.memAddress(indices), (long) indices.remaining() << 2);
if (capture != null) DisplayListManager.recordIndexedDrawCapture(capture);
if (recordMode == RecordMode.COMPILE) return;
savedRecorder = DisplayListManager.pauseRecording();
} else if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
return;
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, ShortBuffer indices) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
CommandRecorder savedRecorder = null;
final RecordMode recordMode = DisplayListManager.getRecordMode();
if (recordMode != RecordMode.NONE) {
final IndexedDrawCapture capture = IndexedDrawCapture.createFromClientIndices(mode, indices.remaining(), GL11.GL_UNSIGNED_SHORT, MemoryUtilities.memAddress(indices), (long) indices.remaining() << 1);
if (capture != null) DisplayListManager.recordIndexedDrawCapture(capture);
if (recordMode == RecordMode.COMPILE) return;
savedRecorder = DisplayListManager.pauseRecording();
} else if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
return;
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
CommandRecorder savedRecorder = null;
final RecordMode recordMode = DisplayListManager.getRecordMode();
if (recordMode != RecordMode.NONE) {
final IndexedDrawCapture capture = IndexedDrawCapture.createFromClientIndices(mode, count, type, MemoryUtilities.memAddress(indices), indices.remaining());
if (capture != null) DisplayListManager.recordIndexedDrawCapture(capture);
if (recordMode == RecordMode.COMPILE) return;
savedRecorder = DisplayListManager.pauseRecording();
} else if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, count, type, indices);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
return;
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, count, type, indices);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, count, type, indices);
}
public static void glDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
@@ -2317,14 +2368,16 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices_count, type, indices_buffer_offset);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
public static void glDrawBuffer(int mode) {
@@ -2417,6 +2470,19 @@ public class GLStateManager {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
private static void ffpClientArrayPointer(int index, int size, int type, boolean normalized, int stride, long offset) {
if (VAOManager.isGenericPointerEnabled(index)) return;
VAOManager.setAttribute(index, size, type, normalized, stride, offset, boundVBO);
VAOManager.markConventional(index);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, offset);
}
private static void ffpClientArrayPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (VAOManager.isGenericPointerEnabled(index)) return;
VAOManager.setAttribute(index, size, type, normalized, stride, pointer);
VAOManager.markConventional(index);
}
public static void glVertexPointer(int size, int stride, IntBuffer pointer) {
glVertexPointer(size, GL11.GL_INT, stride, MemoryUtilities.memByteBuffer(pointer));
}
@@ -2426,40 +2492,40 @@ public class GLStateManager {
}
public static void glVertexPointer(int size, int stride, FloatBuffer pointer) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexPointer(int size, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexPointer(int size, int type, int stride, ByteBuffer pointer) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
ffpClientArrayPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
}
public static void glVertexPointer(int size, int type, int stride, long pointer_buffer_offset) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer_buffer_offset);
ffpClientArrayPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer_buffer_offset);
}
public static void glColorPointer(int size, int stride, FloatBuffer pointer) {
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_FLOAT, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_FLOAT, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glColorPointer(int size, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_DOUBLE, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_DOUBLE, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glColorPointer(int size, int type, int stride, ByteBuffer pointer) {
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
ffpClientArrayPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
}
public static void glColorPointer(int size, boolean unsigned, int stride, ByteBuffer pointer) {
final int type = unsigned ? GL11.GL_UNSIGNED_BYTE : GL11.GL_BYTE;
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
ffpClientArrayPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
}
public static void glColorPointer(int size, int type, int stride, long pointer_buffer_offset) {
glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer_buffer_offset);
ffpClientArrayPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer_buffer_offset);
}
public static void glNormalPointer(int stride, FloatBuffer pointer) {
@@ -2479,33 +2545,33 @@ public class GLStateManager {
}
public static void glNormalPointer(int type, int stride, ByteBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
}
public static void glNormalPointer(int type, int stride, FloatBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, ShortBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_SHORT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_SHORT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, IntBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_DOUBLE, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_DOUBLE, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
ffpClientArrayPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
}
public static void glTexCoordPointer(int size, int type, int stride, ByteBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, type, false, stride, pointer);
ffpClientArrayPointer(loc, size, type, false, stride, pointer);
}
public static void glTexCoordPointer(int size, int stride, FloatBuffer pointer) {
@@ -2527,31 +2593,31 @@ public class GLStateManager {
public static void glTexCoordPointer(int size, int type, int stride, FloatBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(loc, size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, ShortBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, GL11.GL_SHORT, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(loc, size, GL11.GL_SHORT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, IntBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, DoubleBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
ffpClientArrayPointer(loc, size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, type, false, stride, pointer_buffer_offset);
ffpClientArrayPointer(loc, size, type, false, stride, pointer_buffer_offset);
}
public static void glEnableClientState(int cap) {
@@ -5325,6 +5391,9 @@ public class GLStateManager {
} else {
final int unit = target - GL13.GL_TEXTURE0;
if (unit >= 2 && unit < 4) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
unit23TexCoordSetDuringDraw = true;
}
ShaderManager.setCurrentTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
@@ -5774,6 +5843,9 @@ public class GLStateManager {
generateVertexShaderIfNeeded(program);
}
RENDER_BACKEND.linkProgram(program);
if (ShaderManager.getInstance().isEnabled() && RENDER_BACKEND.getProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
LOGGER.warn("Program {} failed to link: {}", program, RENDER_BACKEND.getProgramInfoLog(program));
}
CompatUniformManager.onLinkProgram(program);
}
@@ -6030,15 +6102,15 @@ public class GLStateManager {
}
public static void glUniform1(int location, IntBuffer values) {
if (values.remaining() >= 1) RENDER_BACKEND.uniform1i(location, values.get(values.position()));
RENDER_BACKEND.uniform1iv(location, values);
}
public static void glUniform2(int location, FloatBuffer values) {
if (values.remaining() >= 2) RENDER_BACKEND.uniform2f(location, values.get(values.position()), values.get(values.position() + 1));
RENDER_BACKEND.uniform2(location, values);
}
public static void glUniform2(int location, IntBuffer values) {
if (values.remaining() >= 2) RENDER_BACKEND.uniform2i(location, values.get(values.position()), values.get(values.position() + 1));
RENDER_BACKEND.uniform2iv(location, values);
}
public static void glUniform3(int location, FloatBuffer values) {
@@ -6046,7 +6118,7 @@ public class GLStateManager {
}
public static void glUniform3(int location, IntBuffer values) {
if (values.remaining() >= 3) RENDER_BACKEND.uniform3i(location, values.get(values.position()), values.get(values.position() + 1), values.get(values.position() + 2));
RENDER_BACKEND.uniform3iv(location, values);
}
public static void glUniform4(int location, FloatBuffer values) {
@@ -6054,7 +6126,7 @@ public class GLStateManager {
}
public static void glUniform4(int location, IntBuffer values) {
if (values.remaining() >= 4) RENDER_BACKEND.uniform4i(location, values.get(values.position()), values.get(values.position() + 1), values.get(values.position() + 2), values.get(values.position() + 3));
RENDER_BACKEND.uniform4iv(location, values);
}
public static void glUniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {
@@ -822,6 +822,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20.glUniform3(location, value);
@@ -832,6 +837,26 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20.glUniform1(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20.glUniform3(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20.glUniform4(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix3(location, transpose, value);
@@ -201,8 +201,13 @@ public abstract class RenderBackend {
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform2(int location, FloatBuffer value);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniform1iv(int location, IntBuffer value);
public abstract void uniform2iv(int location, IntBuffer value);
public abstract void uniform3iv(int location, IntBuffer value);
public abstract void uniform4iv(int location, IntBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
@@ -135,7 +135,8 @@ public final class FragmentKey {
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
@@ -78,7 +78,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", v_TexCoord").append(i).append(".st);\n");
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
}
}
@@ -40,6 +40,7 @@ public final class VAOManager {
current.vertexFlags = currentVertexFlags;
current.ebo = boundEBO;
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
}
VAOData data = vaoMap.get(vaoId);
@@ -51,8 +52,7 @@ public final class VAOManager {
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
recomputeClientSideCount();
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
@@ -68,6 +68,7 @@ public final class VAOManager {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public int clientSideEnabledCount;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
@@ -85,6 +86,7 @@ public final class VAOManager {
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
@@ -101,11 +103,24 @@ public final class VAOManager {
a.offset = 0;
a.vboId = 0;
a.clientPointer = pointer;
a.genericPointer = true;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
}
}
public static boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
@@ -169,14 +184,6 @@ public final class VAOManager {
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (isClientSideAttrib(i)) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
@@ -239,6 +246,7 @@ public final class VAOManager {
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean genericPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
@@ -253,6 +261,7 @@ public final class VAOManager {
offset = 0;
vboId = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
@@ -49,6 +49,7 @@ public final class VertexKey {
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -85,6 +86,7 @@ public final class VertexKey {
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
public boolean unit23UvFromUnit0() { return bit(BIT_UNIT23_UV_FROM_UNIT0); }
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
@@ -213,6 +215,10 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
@@ -106,8 +106,8 @@ public final class VertexShaderGenerator {
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.unitTexCoordEnabled(2) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
@@ -363,8 +363,10 @@ public final class VertexShaderGenerator {
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = "u_CurrentTexCoord" + i;
if (key.unitTexMatEnabled(i)) {
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
@@ -383,7 +385,7 @@ public final class VertexShaderGenerator {
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
emitTexGenComponent(sb, key.texGenModeR(), "p", "R", key);
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
@@ -8,6 +8,7 @@ public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface GlintColorHandler {
void onColorChanged(float red, float green, float blue, float alpha);
}
@@ -9,8 +9,6 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -52,20 +50,14 @@ public final class IndexedDrawCapture {
return freed;
}
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
private static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
}
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
if (indicesCount == 0) return null;
if (eboId == 0) {
warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
GLStateManager.warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
return null;
}
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
@@ -74,29 +66,53 @@ public final class IndexedDrawCapture {
final int prevEBO = GLStateManager.getBoundEBO();
final int prevVBO = GLStateManager.getBoundVBO();
ByteBuffer eboSrc = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
GLStateManager.warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
GLStateManager.warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
return bake(mode, indicesCount, srcIndexType, MemoryUtilities.memAddress0(eboSrc));
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
public static IndexedDrawCapture createFromClientIndices(int mode, int indicesCount, int srcIndexType, long indexSrcAddr, long srcByteLength) {
if (indicesCount == 0) return null;
if (indexSrcAddr == 0L) return null;
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 - skipping", indicesCount);
return null;
}
final int idxSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (idxSize <= 0) return null;
if ((long) indicesCount * idxSize > srcByteLength) {
GLStateManager.warnOnce("client-idx-range", "[IndexedDrawCapture] index count {} ({} bytes) exceeds client buffer {} bytes - skipping", indicesCount, (long) indicesCount * idxSize, srcByteLength);
return null;
}
return bake(mode, indicesCount, srcIndexType, indexSrcAddr);
}
private static IndexedDrawCapture bake(int mode, int indicesCount, int srcIndexType, long indexSrcAddr) {
AttribSnapshot snap = null;
ByteBuffer vertexData = null;
ByteBuffer indexData = null;
boolean success = false;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
final long eboSrcAddr = MemoryUtilities.memAddress0(eboSrc);
final long minMax = QuadConverter.scanMinMaxIndex(eboSrcAddr, srcIndexType, indicesCount);
final long minMax = QuadConverter.scanMinMaxIndex(indexSrcAddr, srcIndexType, indicesCount);
if (minMax == -1L) {
warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
GLStateManager.warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
return null;
}
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
@@ -110,7 +126,7 @@ public final class IndexedDrawCapture {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
GLStateManager.warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
return null;
}
final int[] locations = new int[enabledCount];
@@ -137,6 +153,13 @@ public final class IndexedDrawCapture {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
final long readStart = relOffset + (long) minVtx * d.effectiveStride();
final long readEnd = relOffset + (long) maxVtx * d.effectiveStride() + (long) d.size() * d.typeSizeBytes();
if (readStart < 0 || readEnd > d.readBuffer().capacity()) {
GLStateManager.warnOnce("vtx-oob", "[IndexedDrawCapture] attrib {} read extent {} exceeds snapshot {} bytes - skipping", d.location(), readEnd, d.readBuffer().capacity());
return null;
}
if (a == 0) {
sharedSrcBuffer = d.readBuffer();
sharedSrcStride = d.effectiveStride();
@@ -169,12 +192,12 @@ public final class IndexedDrawCapture {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
QuadConverter.triangulateQuads(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
bakedDrawMode = GL11.GL_TRIANGLES;
} else {
bakedIndexCount = indicesCount;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
QuadConverter.widenIndices(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
@@ -184,14 +207,11 @@ public final class IndexedDrawCapture {
success = true;
return out;
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
@@ -0,0 +1,103 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class ArrayUniformUploadGLTest {
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static int link(String vs, String fs) {
final int v = compile(GL20.GL_VERTEX_SHADER, vs);
final int f = compile(GL20.GL_FRAGMENT_SHADER, fs);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
private static final String VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
@Test
void glUniform2FloatBufferUploadsWholeArray() {
// Runtime index keeps the whole array active so element locations stay queryable.
final String fs = "#version 330 core\n"
+ "uniform vec2 cosmicuvs[10];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(cosmicuvs[idx], cosmicuvs[(idx+1)%10]); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final FloatBuffer buf = BufferUtils.createFloatBuffer(20);
for (int i = 0; i < 20; i++) buf.put(i + 1.0f); // 1..20, all distinct, all non-zero
buf.flip();
GLStateManager.glUniform2(GL20.glGetUniformLocation(p, "cosmicuvs"), buf);
final FloatBuffer read = BufferUtils.createFloatBuffer(2);
for (int i = 0; i < 10; i++) {
final int loc = GL20.glGetUniformLocation(p, "cosmicuvs[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(2 * i + 1.0f, read.get(0), 0.0f, "cosmicuvs[" + i + "].x");
assertEquals(2 * i + 2.0f, read.get(1), 0.0f, "cosmicuvs[" + i + "].y");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
@Test
void glUniform1IntBufferUploadsWholeArray() {
final String fs = "#version 330 core\n"
+ "uniform int vals[4];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(float(vals[idx])); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final IntBuffer buf = BufferUtils.createIntBuffer(4);
buf.put(11).put(22).put(33).put(44).flip();
GLStateManager.glUniform1(GL20.glGetUniformLocation(p, "vals"), buf);
final IntBuffer read = BufferUtils.createIntBuffer(1);
final int[] expect = {11, 22, 33, 44};
for (int i = 0; i < 4; i++) {
final int loc = GL20.glGetUniformLocation(p, "vals[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(expect[i], read.get(0), "vals[" + i + "]");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -613,4 +615,239 @@ class CompatShaderTransformerTest {
assertFalse(result.matches("(?s).*\\bnew\\b(?!\\s*\\().*"), "'new' as identifier must be renamed in core profile output\n\n" + result);
assertTrue(result.contains("angelica_renamed_new"), "'new' should be renamed to angelica_renamed_new\n\n" + result);
}
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static String core(String src) {
return compact(CompatShaderTransformer.transform(src, false));
}
private static void assertLoc(String c, int loc, String decl) {
assertTrue(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " expected at location " + loc + "\n\n" + c);
}
private static void assertNoLoc(String c, int loc, String decl) {
assertFalse(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " must not be at location " + loc + "\n\n" + c);
}
@Test
void testCustomPositionAttributePinnedToLocationZero() {
// Betweenlands blshader fix
String src = """
#version 120
attribute vec4 a_position;
uniform mat4 u_projMat;
uniform vec2 u_outSize;
varying vec2 v_texCoord;
void main(){
gl_Position = u_projMat * vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy / u_outSize;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertLoc(compact(result), 0, "vec4 a_position");
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultipleLocationlessInputsGetSequentialLocations() {
String c = core("""
#version 120
attribute vec4 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_pos");
assertLoc(c, 1, "vec2 a_uv");
}
@Test
void testCustomInputReservesAroundGlVertex() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = ftransform() + a_foo;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertLoc(c, 1, "vec4 a_foo");
}
@Test
void testCustomInputSkipsFfpSlots() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = vec4(a_foo.xy, 0.0, 1.0) + gl_Color;
}
""");
assertLoc(c, 0, "vec4 a_foo");
assertLoc(c, 1, "vec4 angelica_Color");
}
@Test
void testExplicitLowLocationReserved() {
String c = core("""
#version 120
layout(location = 0) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertLoc(c, 0, "vec4 a_x");
assertLoc(c, 1, "vec4 a_y");
}
@Test
void testMatrixInputSortedFirst() {
String c = core("""
#version 120
attribute vec4 a_a;
attribute mat4 a_m;
void main() {
gl_Position = a_m * a_a;
}
""");
assertLoc(c, 0, "mat4 a_m"); // 4-slot matrix sorted first
assertLoc(c, 4, "vec4 a_a");
}
@Test
void testInputNamedLocationStillPinned() {
assertLoc(core("""
#version 120
attribute vec4 a_location;
void main() {
gl_Position = vec4(a_location.xy, 0.0, 1.0);
}
"""), 0, "vec4 a_location");
}
@Test
void testInputWithExplicitLayoutLeftAlone() {
String c = core("""
#version 120
layout(location = 3) in vec4 a_data;
attribute vec4 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_data.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_position"); // sole location-less input
assertLoc(c, 3, "vec4 a_data"); // explicit location preserved
assertNoLoc(c, 0, "vec4 a_data");
}
@Test
void testPinnedShaderRemainsWellFormed() {
String src = """
#version 120
attribute vec4 a_position;
void main() {
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewMatrix * vec4(a_position.xyz, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
String c = compact(result);
assertLoc(c, 0, "vec4 a_position");
assertLoc(c, 1, "vec4 angelica_Color");
assertTrue(c.contains("uniformmat4angelica_ModelViewMatrix"), "matrix uniform injected\n\n" + result);
assertFalse(c.contains("gl_ModelViewMatrix"), "builtin rewritten (not fallback)\n\n" + result);
final int mainIdx = result.indexOf("void main");
assertTrue(mainIdx > 0, result);
assertTrue(result.indexOf("angelica_ModelViewMatrix") < mainIdx, "uniform before main\n\n" + result);
assertTrue(result.indexOf("a_position") < mainIdx, "pinned input before main\n\n" + result);
}
@Test
void testGlVertexShaderDoesNotPinCustomAttribute() {
String c = core("""
#version 120
attribute float activelights;
void main() {
gl_Position = ftransform();
float x = activelights;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertNoLoc(c, 0, "float activelights");
}
@ParameterizedTest // array on the declarator (vec4 a[2]) and on the type (vec4[2] a) must both round-trip
@ValueSource(strings = {"attribute vec4 positions[2];", "attribute vec4[2] positions;"})
void testArrayAttributePreservedEitherSyntax(String decl) {
String c = core("""
#version 120
%s
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""".formatted(decl));
assertLoc(c, 0, "vec4 positions[2]"); // 2-slot array pinned to 0..1
assertLoc(c, 2, "vec4 a_tail");
}
@Test
void testMultiDeclaratorSplitIntoSeparateLocations() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b;
void main() {
gl_Position = a + b;
}
""", false);
String c = compact(result);
assertLoc(c, 0, "vec4 a");
assertLoc(c, 1, "vec4 b");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultiDeclaratorWithMixedArray() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b[2];
void main() {
gl_Position = a + b[0] + b[1];
}
""", false);
String c = compact(result);
// b takes 2 slots; sorted before the 1-slot a, so b -> 0..1, a -> 2.
assertLoc(c, 0, "vec4 b[2]");
assertLoc(c, 2, "vec4 a");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
}
@Test
void testHexExplicitLocationReserved() {
String c = core("""
#version 120
layout(location = 0x1) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertTrue(c.contains("layout(location=0x1)invec4a_x"), "hex explicit location preserved\n\n" + c);
assertNoLoc(c, 1, "vec4 a_y"); // must not collide with the hex-reserved slot
assertLoc(c, 0, "vec4 a_y");
}
}
@@ -9,9 +9,11 @@ import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** Tests GLSM display list behavior matches OpenGL spec. */
@@ -257,4 +259,30 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glEndList();
}
@Test
void glsm_unsupportedOpInList_abortsCleanly() {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
assertTrue(DisplayListManager.isRecording(), "recording after glNewList");
assertThrows(UnsupportedOperationException.class, () -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
assertFalse(DisplayListManager.isRecording(), "recording state must be reset after an in-list throw");
assertEquals(DisplayListManager.RecordMode.NONE, DisplayListManager.getRecordMode());
assertDoesNotThrow(() -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
assertDoesNotThrow(GLStateManager::glEndList);
}
@Test
void glsm_abortIfLeaked_recoversOpenList() {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
assertTrue(DisplayListManager.isRecording());
DisplayListManager.abortIfLeaked();
assertFalse(DisplayListManager.isRecording(), "frame-begin backstop must clear a leaked open list");
}
}
@@ -122,6 +122,68 @@ public class GLSM_VAO_UnitTest {
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testFfpConventionalAppliesWithoutGenericAttribute() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
private static void testClientStates() {
verifyState(GL11.GL_VERTEX_ARRAY, false, "GL_VERTEX_ARRAY Default State");
verifyState(GL11.GL_TEXTURE_COORD_ARRAY, false, "GL_TEXTURE_COORD_ARRAY Default State");
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HugeFarPlaneFrustumTest {
private static final float SECTION_HALF = 8.0f + 1.0f + 0.125f;
private static final float DEGENERATE_FAR = 2.147483648e9f;
private static Matrix4f combined(float zFar) {
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 854.0f / 480.0f, 0.05f, zFar);
final Matrix4f mv = new Matrix4f()
.rotate((float) Math.toRadians(25.0), 1, 0, 0)
.rotate((float) Math.toRadians(123.0), 0, 1, 0)
.translate(0.0f, -1.62f, 0.0f);
return new Matrix4f().set(proj).mul(mv);
}
private static boolean sectionVisible(FrustumIntersection f, int sx, int sy, int sz) {
final float cx = sx * 16.0f + 8.0f;
final float cy = sy * 16.0f + 8.0f;
final float cz = sz * 16.0f + 8.0f;
return f.testAab(cx - SECTION_HALF, cy - SECTION_HALF, cz - SECTION_HALF, cx + SECTION_HALF, cy + SECTION_HALF, cz + SECTION_HALF);
}
@Test
void unnormalizedFrustumMatchesNormalFarPlane() {
final FrustumIntersection normal = new FrustumIntersection(combined(512.0f), false);
final FrustumIntersection huge = new FrustumIntersection(combined(DEGENERATE_FAR), false);
int visible = 0;
int mismatches = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
final boolean vNormal = sectionVisible(normal, x, y, z);
if (vNormal) visible++;
if (vNormal != sectionVisible(huge, x, y, z)) mismatches++;
}
}
}
assertTrue(visible > 100, "sanity check broken: normal frustum accepted only " + visible + " sections");
assertEquals(0, mismatches, "sections culled differently under the degenerate far plane");
}
@Test
void normalizedSetCullsEverythingOnDegenerateFarPlane() {
final FrustumIntersection normalized = new FrustumIntersection();
normalized.set(combined(DEGENERATE_FAR));
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (sectionVisible(normalized, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "set(m) with a degenerate far plane no longer culls everything");
}
@Test
void identityFrustumOnlyPassesCameraSection() {
final FrustumIntersection identity = new FrustumIntersection(new Matrix4f());
assertTrue(sectionVisible(identity, 0, 0, 0), "identity frustum rejected the camera's own section");
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (Math.abs(x) <= 1 && Math.abs(y) <= 1 && Math.abs(z) <= 1) continue;
if (sectionVisible(identity, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "non-adjacent sections accepted by the identity frustum");
}
}
@@ -28,6 +28,7 @@ class FFPCombineShaderTest {
private final List<Integer> shadersToDelete = new ArrayList<>();
private final List<Integer> programsToDelete = new ArrayList<>();
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
@@ -37,15 +38,25 @@ class FFPCombineShaderTest {
programsToDelete.clear();
shadersToDelete.clear();
// Reset texture units to defaults
// Reset texture units to defaults (unbind so a later test does not see a stale binding)
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.getTextures().getTexEnvState(i).reset();
if (i > 0) {
GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private int bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
return tex;
}
@Test
@@ -113,6 +124,7 @@ class FFPCombineShaderTest {
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -169,6 +181,7 @@ class FFPCombineShaderTest {
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -202,6 +215,7 @@ class FFPCombineShaderTest {
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -258,6 +272,7 @@ class FFPCombineShaderTest {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -273,6 +288,7 @@ class FFPCombineShaderTest {
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -286,6 +302,7 @@ class FFPCombineShaderTest {
// Unit 3: simple MODULATE
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
// Restore active unit
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class FFPUnboundTextureUnitTest {
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (i > 0) GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private void bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
}
@Test
void enabledUnitWithoutTextureIsExcluded() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertEquals(1, fk.nrEnabledUnits(), "units 2/3 have no texture bound -> excluded -> only unit 0");
assertEquals(0b0001, fk.enabledUnitMask(), "only unit 0 in the key; enabled-but-unbound units 2/3 excluded");
assertTrue(fk.unitEnabled(0), "unit 0 (texture bound) stays enabled");
}
@Test
void enabledUnitsWithTextureAreCounted() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertTrue(fk.unitEnabled(0));
assertTrue(fk.unitEnabled(2), "unit 2 with a bound texture must be counted");
assertEquals(3, fk.nrEnabledUnits(), "highest enabled is unit 2 -> nrEnabledUnits=3");
}
}
@@ -9,6 +9,9 @@ import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -17,7 +20,10 @@ import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -25,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
@@ -521,7 +528,153 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
"listBA: batch[1] must match the second-recorded layout");
}
// === local helpers ===
private static Stream<Arguments> clientIndexedDrawCases() {
return Stream.of(
arguments("byte indices", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3);
idx.put(new byte[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("short indices", GL11.GL_COMPILE, (Runnable) () -> {
final ShortBuffer idx = BufferUtils.createShortBuffer(3);
idx.put(new short[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("int indices", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("explicit count+type", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("quads triangulate at record", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_QUADS, idx);
}, GL11.GL_TRIANGLES, 6),
arguments("compile-and-execute fires live and bakes", GL11.GL_COMPILE_AND_EXECUTE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("clientIndexedDrawCases")
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount) {
final int list = recordClientIndexDraw(listMode, draw);
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(expectedMode, cmd.getDrawMode());
assertEquals(expectedCount, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "client indices always widen to UNSIGNED_INT");
}
@Test
void clientVerticesAndIndices_pointersSetDuringRecording_bake() {
final float[][] verts = {{0f, 0f, 0f}, {1f, 0f, 0f}, {1f, 1f, 0f}, {0f, 1f, 0f}};
final ByteBuffer clientVerts = BufferUtils.createByteBuffer(verts.length * 12).order(ByteOrder.nativeOrder());
for (float[] v : verts) {
clientVerts.putFloat(v[0]);
clientVerts.putFloat(v[1]);
clientVerts.putFloat(v[2]);
}
clientVerts.flip();
final IntBuffer indices = BufferUtils.createIntBuffer(6);
indices.put(new int[]{0, 1, 2, 2, 3, 0}).flip();
final int vao = newVao();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(GL11.GL_TRIANGLES, cmd.getDrawMode());
assertEquals(6, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType());
final ByteBuffer owned = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), verts.length * 12);
for (int i = 0; i < verts.length; i++) {
assertEquals(verts[i][0], owned.getFloat(i * 12), 0f, "pos.x v=" + i);
assertEquals(verts[i][1], owned.getFloat(i * 12 + 4), 0f, "pos.y v=" + i);
assertEquals(verts[i][2], owned.getFloat(i * 12 + 8), 0f, "pos.z v=" + i);
}
MemoryUtilities.memFree(owned);
}
@Test
void quadOffsetDraw_compileAndExecute_doesNotPauseRecordingForRestOfList() {
final int vao = setupPosColorVao(defaultQuadVbo());
final int quadEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int triEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, quadEbo);
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
assertTrue(DisplayListManager.isRecording(), "recording must resume after a GL_QUADS offset draw");
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, triEbo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertEquals(2, allBatchedCmds(list).size(), "both the QUADS draw and the following triangle draw must be recorded");
}
private enum MalformedClientDraw { OVERSIZED_COUNT, INDEX_PAST_VERTEX_BUFFER }
@ParameterizedTest(name = "{0}")
@EnumSource(MalformedClientDraw.class)
void malformedClientDraw_skippedAndRecordingSurvives(MalformedClientDraw scenario) {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
switch (scenario) {
case OVERSIZED_COUNT -> {
GLStateManager.glBindVertexArray(setupPosColorVao(defaultQuadVbo()));
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 100, GL11.GL_UNSIGNED_SHORT, idx);
}
case INDEX_PAST_VERTEX_BUFFER -> {
final int vbo = uploadPosColorVbo(new float[]{0f, 0f, 1f, 0f}, new byte[][]{{1, 1, 1, 1}, {2, 2, 2, 2}});
GLStateManager.glBindVertexArray(setupPosColorVao(vbo));
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}
}
assertTrue(DisplayListManager.isRecording(), "recording survives a skipped malformed draw");
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertNull(firstBatchedCmd(list), "malformed draw must be skipped, not captured or over-read");
}
private int recordClientIndexDraw(int listMode, Runnable draw) {
final int vao = setupPosColorVao(defaultQuadVbo());
final int list = newList();
GLStateManager.glNewList(list, listMode);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
draw.run();
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
return list;
}
private static void assertUintIndices(ByteBuffer ib, int... expected) {
for (int i = 0; i < expected.length; i++) {
+5 -1
View File
@@ -34,6 +34,10 @@ repositories {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Minecraft Libraries"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
@@ -57,7 +61,7 @@ dependencies {
api(project(":glsm"))
compileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.15:dev") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { isTransitive = false }
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
@@ -866,6 +866,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20C.glUniform2fv(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20C.glUniform3fv(location, value);
@@ -876,6 +881,26 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4fv(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20C.glUniform1iv(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20C.glUniform2iv(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20C.glUniform3iv(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20C.glUniform4iv(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20C.glUniformMatrix3fv(location, transpose, value);
@@ -16,7 +16,7 @@ import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
name = "Angelica",
version = Tags.VERSION,
dependencies = """
required-after:gtnhlib@[0.8.21,);\
required-after:gtnhlib@[0.11.19,);\
before:lwjgl3ify@[1.5.3,);\
after:hodgepodge@[2.4.4,);\
after:CodeChickenCore@[1.2.0,);\
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.api;
public interface ExtCeleritasRenderBlocks {
boolean angelica$shouldApplyCeleritasAO();
void angelica$setApplyingCeleritasAO(boolean val);
}
@@ -11,9 +11,13 @@ import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.Loader;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -27,13 +31,13 @@ public class FontStrategist {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
static {
HashMap<String, Font> fontSet = new HashMap<>();
if (GraphicsEnvironment.isHeadless()) {
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
availableFonts = new Font[0];
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Only bundled fonts will be available. This is likely a MacOS issue.");
} else {
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
HashMap<String, Font> fontSet = new HashMap<>();
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
for (Font font : availableFontsDirty) {
@@ -57,11 +61,14 @@ public class FontStrategist {
sb.append(". Some fonts may be missing from the font selection menu.");
LOGGER.warn(sb.toString());
}
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, fontSet.size());
}
loadBundledFonts(fontSet);
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
@@ -74,6 +81,44 @@ public class FontStrategist {
defaultResourcePacks.add(fontResourcePack);
}
// Load .ttf/.otf shipped with the pack so customFontName* can point at a font that isn't installed
// on the system. fontfiles/ is SmoothFont's folder, kept so packs built for it work unchanged.
private static void loadBundledFonts(HashMap<String, Font> fontSet) {
File configDir = Loader.instance().getConfigDir();
if (configDir == null) {
LOGGER.warn("Loader.instance().getConfigDir() returned null. Bundled fonts will not be loaded.");
return;
}
File parent = configDir.getParentFile();
File[] fontDirs = { new File(parent, "fontfiles"), new File(configDir, "angelica/fonts") };
GraphicsEnvironment ge = GraphicsEnvironment.isHeadless() ? null : GraphicsEnvironment.getLocalGraphicsEnvironment();
int loaded = 0;
for (File dir : fontDirs) {
File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf") || name.toLowerCase().endsWith(".otf"));
if (files == null) continue;
for (File file : files) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, file);
String name = font.getFontName();
if (fontSet.containsKey(name)) {
String deduped = name;
int n = 1;
while (fontSet.containsKey(deduped)) deduped = name + "_" + n++;
LOGGER.warn("Font name {} (from {}) is already registered; registering this one as {}", name, file.getName(), deduped);
name = deduped;
}
if (ge != null) ge.registerFont(font);
fontSet.put(name, font);
loaded++;
LOGGER.info("Loaded font {} from {}/{}", name, dir.getName(), file.getName());
} catch (FontFormatException | IOException e) {
LOGGER.error("Couldn't load font {}", file.getPath(), e);
}
}
}
if (loaded > 0) LOGGER.info("Loaded {} bundled font(s) from the pack", loaded);
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
@@ -1,13 +1,14 @@
package com.gtnewhorizons.angelica.compat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusEvents;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
@@ -29,7 +30,9 @@ public class ModStatus {
public static boolean isBaublesLoaded;
public static boolean isCosmeticArmorReworkedLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isCubicChunksLoaded;
public static boolean isBOPLoaded;
public static boolean isEtFuturumLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
@@ -49,6 +52,8 @@ public class ModStatus {
isBaublesLoaded = Loader.isModLoaded("Baubles");
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isCubicChunksLoaded = Loader.isModLoaded("cubicchunks");
isEtFuturumLoaded = Loader.isModLoaded("etfuturum");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBOPLoaded = Loader.isModLoaded("BiomesOPlenty");
@@ -69,6 +74,10 @@ public class ModStatus {
}
}
if (isCubicChunksLoaded) {
CubeStatusEvents.init();
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.compat.cubicchunks;
import net.minecraft.world.World;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import com.cardinalstar.cubicchunks.api.IColumn;
import com.cardinalstar.cubicchunks.api.ICube;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.cardinalstar.cubicchunks.world.cube.Cube;
import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
public class CubicChunksAPI {
public static ExtendedBlockStorage getCubeStorage(World world, int x, int y, int z) {
Cube cube = ((ICubeProviderInternal) world.getChunkProvider()).getLoadedCube(x, y, z);
if (cube == null || cube.isEmpty()) return null;
return cube.getStorage();
}
public static int[] getLoadedCubeLevelsInColumn(World world, int chunkX, int chunkZ) {
IColumn column = (IColumn) world.getChunkFromChunkCoords(chunkX, chunkZ);
return column.getLoadedCubes().stream().mapToInt(ICube::getY).toArray();
}
public static int getMinHeight(World world) {
return ((ICubicWorld) world).getMinHeight();
}
public static int getMaxHeight(World world) {
return ((ICubicWorld) world).getMaxHeight();
}
public static int getMinSectionY(World world) {
return ChunkSectionPos.getSectionCoord(getMinHeight(world));
}
public static int getMaxSectionYExclusive(World world) {
return ChunkSectionPos.getSectionCoord(getMaxHeight(world) - 1) + 1;
}
}
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.compat.etfuturum;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.relauncher.ReflectionHelper;
import lombok.Getter;
import net.minecraft.entity.player.EntityPlayer;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public final class EtFuturumCompat {
private static final String IELYTRA_PLAYER = "ganymedes01.etfuturum.api.elytra.IElytraPlayer";
@Getter
private static boolean loaded;
private static Class<?> elytraPlayerClass;
private static final MethodHandle isElytraFlyingHandle;
static {
MethodHandle handle = null;
try {
if (Loader.isModLoaded("etfuturum")) {
elytraPlayerClass = ReflectionHelper.getClass(EtFuturumCompat.class.getClassLoader(), IELYTRA_PLAYER);
handle = MethodHandles.lookup().findVirtual(elytraPlayerClass, "etfu$isElytraFlying",
MethodType.methodType(boolean.class));
loaded = true;
}
} catch (Throwable t) {
LOGGER.error("Failed to load Et Futurum Requiem elytra compat", t);
loaded = false;
}
isElytraFlyingHandle = handle;
}
private EtFuturumCompat() {
}
public static boolean isElytraFlying(EntityPlayer player) {
if (!loaded || isElytraFlyingHandle == null || !elytraPlayerClass.isInstance(player)) {
return false;
}
try {
return (boolean) isElytraFlyingHandle.invoke(player);
} catch (Throwable t) {
return false;
}
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.compat.lotr;
import net.minecraft.world.WorldProvider;
public final class LOTRCompat {
private static final Class<?> LOTR_WORLD_PROVIDER_CLASS;
static {
Class<?> clazz = null;
try {
clazz = Class.forName("lotr.common.world.LOTRWorldProvider", false, LOTRCompat.class.getClassLoader());
} catch (ClassNotFoundException ignored) {}
LOTR_WORLD_PROVIDER_CLASS = clazz;
}
private LOTRCompat() {}
public static boolean isLotrProvider(WorldProvider provider) {
return LOTR_WORLD_PROVIDER_CLASS != null && LOTR_WORLD_PROVIDER_CLASS.isInstance(provider);
}
}
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import org.joml.Vector3i;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
// See if we can merge/mixin/extend ChunkPosition maybe?
public class ChunkSectionPos extends Vector3i {
@@ -25,11 +27,8 @@ public class ChunkSectionPos extends Vector3i {
}
public static long asLong(int x, int y, int z) {
long l = 0L;
l |= ((long)x & 4194303L) << 42;
l |= ((long)y & 1048575L) << 0;
l |= ((long)z & 4194303L) << 20;
return l;
// Use CoordinatePacker so that cubic chunks can change the bit packing layout
return CoordinatePacker.pack(x, y, z);
}
public static int getLocalCoord(int coord) {
@@ -1,5 +1,9 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.util.MathHelper;
import com.gtnewhorizons.angelica.compat.ModStatus;
public class CompatMathHelper {
public static int smallestEncompassingPowerOfTwo(int value) {
int j = value - 1;
@@ -24,4 +28,13 @@ public class CompatMathHelper {
return k == 0 ? value : value + divisor - k;
}
}
public static int clampHeight(int y) {
if (ModStatus.isCubicChunksLoaded) {
// no clamping
return y;
} else {
return MathHelper.clamp_int(y, 0, 255);
}
}
}
@@ -357,8 +357,13 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean disableErrorChecks;
@Config.Comment("Fixes various issues with entity overlays, such as z-fighting, damage animations and eyes.")
@Config.Comment("Fixes various issues with entity overlays, such as z-fighting and eyes.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean entityOverlayFixes;
@Config.Comment("Swap the vanilla damage overlay with one similar to modern. Fixes specific issues with z-fighting.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean entityModernDamageOverlay;
}
@@ -0,0 +1,36 @@
package com.gtnewhorizons.angelica.event;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public class ClientEvent extends MutableEvent {
public static final EventBus<ClientEvent> BUS = EventBus.create(ClientEvent.class);
public final ClientEventType event;
public final EntityRenderer renderer;
public final float partialTicks;
private ClientEvent(ClientEventType event) {
this.event = event;
this.renderer = null;
this.partialTicks = 0;
}
private ClientEvent(ClientEventType event, EntityRenderer renderer, float partialTicks) {
this.event = event;
this.renderer = renderer;
this.partialTicks = partialTicks;
}
public static void post(ClientEventType event, EntityRenderer renderer, float partialTicks) {
if (!BUS.hasListeners()) return;
BUS.post(new ClientEvent(event, renderer, partialTicks));
}
public static void post(ClientEventType event) {
if (!BUS.hasListeners()) return;
BUS.post(new ClientEvent(event));
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.event;
public enum ClientEventType {
PRE_RENDER_GUI,
PRE_RENDER_HUD,
POST_RENDER_HUD,
ON_TICK,
}
@@ -6,11 +6,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.CustomFramebuffer
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.SharedDepthFramebuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.holoinventory.HoloInventoryReflectionCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.event.ClientEvent;
import com.gtnewhorizons.angelica.event.ClientEventType;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.GuiIngameAccessor;
@@ -65,6 +66,8 @@ public class HUDCaching {
}
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
if (ModStatus.isXaerosMinimapLoaded && ingame instanceof GuiIngameForge) {
// this used to be called by asming into renderGameOverlay, but we removed it
XaeroMinimapCore.beforeIngameGuiRender(partialTicks);
@@ -78,6 +81,7 @@ public class HUDCaching {
if (!AngelicaConfig.hudCachingActive || mc.displayWidth < 16 || mc.displayHeight < 16) {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
ClientEvent.post(ClientEventType.POST_RENDER_HUD, renderer, partialTicks);
return;
}
@@ -170,6 +174,8 @@ public class HUDCaching {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
mc.getTextureManager().bindTexture(Gui.icons);
ClientEvent.post(ClientEventType.POST_RENDER_HUD, renderer, partialTicks);
}
/**
@@ -120,7 +120,10 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
"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")
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib")
));
} catch (RuntimeException ex) {
fatalDependencyError(ex.getMessage());
@@ -64,7 +64,15 @@ public enum Mixins implements IMixins {
.addExcludedMod(TargetedMod.CUSTOM_PLAYER_MODELS)
.addClientMixins(
"angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
, "angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
)
),
ANGELICA_DAMAGE_OVERLAY(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.entityModernDamageOverlay)
.addExcludedMod(TargetedMod.CUSTOM_PLAYER_MODELS)
.addClientMixins(
"angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
)
),
@@ -75,6 +83,13 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.vbo.MixinRenderGlobal")
),
ANGELICA_VBO_CLOUDS_FAR_PLANE(
new MixinBuilder()
.setApplyIf(() -> AngelicaConfig.enableVBOClouds && !AngelicaConfig.enableNotFineFeatures)
.setPhase(Phase.EARLY)
.addClientMixins("notfine.clouds.MixinEntityRenderer")
),
ANGELICA_PANORAMA_BLUR(
new MixinBuilder("Replace main menu panorama with modern equivalent")
.setPhase(Phase.EARLY)
@@ -196,6 +211,8 @@ public enum Mixins implements IMixins {
.addClientMixins(
"rendering.MixinBlock"
, "rendering.MixinBlockFluidBase"
, "rendering.MixinBlockLiquid"
, "rendering.BlockLiquidFlowInvoker"
, "rendering.AccessorBiomeColorEvent"
, "rendering.MixinBiomeGenBase"
, "rendering.MixinChunk"
@@ -223,8 +240,7 @@ public enum Mixins implements IMixins {
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
.addClientMixins(
"celeritas.terrain.ChunkTrackerAccessor"
, "celeritas.terrain.MixinChunkProviderClient"
"celeritas.terrain.MixinChunkProviderClient"
, "celeritas.terrain.MixinMinecraft_ChunkUpdates"
, "celeritas.terrain.MixinRenderGlobal"
, "celeritas.terrain.MixinRenderSectionManager"
@@ -290,6 +306,9 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderEndPortal"
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
, "shaders.MixinTextureManager_ReloadCount"
, "shaders.AccessorModelBox"
, "shaders.MixinModelBiped"
)
),
@@ -398,6 +417,12 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.removeUnicodeEvenScaling)
.addClientMixins("angelica.bugfixes.MixinScaledResolution_UnicodeFix")),
FARSEEK_WORLDSLICE_COMPAT(new MixinBuilder("Let Farseek resolve celeritas' WorldSlice so Streams' water renders properly")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.FARSEEK)
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
.addClientMixins("client.farseek.MixinFarseekIBlockAccessValue")),
SECURITYCRAFT_COMPAT(new MixinBuilder("Fix reflection in SecurityCraft for compat with Angelica")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.SECURITYCRAFT)
@@ -17,6 +17,7 @@ public enum TargetedMod implements ITargetMod {
DRAGON_API("Reika.DragonAPI.Auxiliary.DragonAPIASMHandler", "DragonAPI"),
ET_FUTURUM_REQUIEM(null, "etfuturum"),
EXTRAUTILS(null, "ExtraUtilities"),
FARSEEK("farseek.core.FarseekCoreMod", "farseek"),
FALSETWEAKS("com.falsepattern.falsetweaks.asm.CoreLoadingPlugin", "falsetweaks"),
MINEFACTORY_RELOADED(null, "MineFactoryReloaded"),
NTM_SPACE("com.hbm.dim.SolarSystem"),
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
public interface ChunkTrackerAccessor {
Long2IntOpenHashMap angelica$getChunkStatus();
LongOpenHashSet angelica$getChunkReady();
LongSet angelica$getUnloadQueue();
LongSet angelica$getLoadQueue();
}
@@ -1,8 +1,11 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import org.joml.FrustumIntersection;
import org.joml.Matrix4fc;
public interface ClippingHelperExt {
FrustumIntersection celeritas$getFrustumIntersection();
void celeritas$updateFrustumIntersection();
Matrix4fc celeritas$getCombinedMatrix();
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import java.util.Map;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
public interface IChunkTileEntityMapHolder {
ConcurrentTileEntityMap angelica$getConcurrentTEMap();
@@ -1,5 +1,35 @@
package com.gtnewhorizons.angelica.proxy;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import biomesoplenty.api.content.BOPCBlocks;
import com.google.common.base.Objects;
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
@@ -8,6 +38,7 @@ import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.config.CompatConfig;
@@ -53,34 +84,6 @@ import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import net.coderbot.iris.Iris;
import net.coderbot.iris.client.IrisDebugScreenHandler;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
public final class ClientProxy extends CommonProxy {
@@ -293,10 +296,10 @@ public final class ClientProxy extends CommonProxy {
event.left.set(i + 4, String.format(
"lc: %d b: %s bl: %d sl: %d rl: %d",
chunk.getTopFilledSegment() + 15,
chunk.getBiomeGenForWorldCoords(bX & 15, bZ & 15, mc.theWorld.getWorldChunkManager()).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getBlockLightValue(bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15, 0)));
this.mc.theWorld.getBiomeGenForCoords(bX & 15, bZ & 15).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getBlockLightValue(bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15, 0)));
}
}
// Draw a frametime graph
@@ -26,8 +26,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import jss.notfine.core.Settings;
import jss.notfine.gui.options.named.GraphicsQualityOff;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.shaderpack.CloudSetting;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
@@ -45,6 +49,7 @@ import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -76,6 +81,8 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private static final float SCROLL_SPEED = 1.0f / 256.0f;
private static final float EDGE_OVERLAP = 0.0001f;
private static final float INTERIOR_INSET = EDGE_OVERLAP * 8.0f;
private static final float MAX_FAR_PLANE_DISTANCE = 65536.0f;
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
@@ -103,11 +110,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
private int anchorMarginCells = 0;
private int cachedEdgesGeneration = Integer.MIN_VALUE;
private boolean cachedEmitTopFace, cachedEmitBottomFace, cachedCameraInsideCloud;
private boolean cachedEmitTopFace, cachedEmitBottomFace;
private int cachedRenderRadius = -1;
private float cachedCloudInteriorHeight = Float.NaN;
private boolean geomBuilt;
private int cloudMode = -1, renderDistance = -1, cloudElevation = -1, scaleMult = -1;
private boolean enabled;
private ByteBuffer emitBuffer;
private long emitBufferAddr;
@@ -136,6 +145,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private GlProgram<CloudUniforms> program;
private final Matrix4f mvpScratch = new Matrix4f();
private final Matrix4f mvScratch = new Matrix4f();
public CloudRenderer() {
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this);
@@ -160,10 +170,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff) Settings.MODE_CLOUDS.option.getStore();
final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY
|| cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1;
final float dimCloudHeight = mc.theWorld == null ? 128.0f : mc.theWorld.provider.getCloudHeight();
final boolean finiteCloudHeight = Float.isFinite(dimCloudHeight);
final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF
&& mc.gameSettings.shouldRenderClouds()
&& mc.theWorld != null
&& mc.theWorld.provider.isSurfaceWorld();
&& mc.theWorld.provider.isSurfaceWorld()
&& finiteCloudHeight;
final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int) Settings.RENDER_DISTANCE_CLOUDS.option.getStore());
final int cloudScaleMult = (int) Settings.CLOUD_SCALE.option.getStore();
@@ -174,11 +187,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
invalidateGeometry();
}
enabled = newEnabled;
cloudMode = cloudQualitySetting;
renderDistance = targetDistance;
scaleMult = cloudScaleMult;
cloudElevation = mc.theWorld == null ? 128 : (int) mc.theWorld.provider.getCloudHeight();
cloudElevation = finiteCloudHeight ? (int) dimCloudHeight : 128;
// Allows the setting to work with RFG and similar without hardcoding.
// The minimum height check is so stuff like Aether cloud height doesn't get messed up.
if (cloudElevation >= 96) {
@@ -186,6 +200,26 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
/**
* Far-plane distance needed so none of the cloud geometry gets clipped.
*/
public float getRequiredFarPlaneDistance() {
if (!enabled || scaleMult <= 0 || renderDistance <= 0) return 0.0f;
if (mc.theWorld == null) return 0.0f;
final float cloudInteriorWidth = 12.0f * scaleMult;
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
float radiusBlocks = (renderRadius + marginChunks) * CELLS_PER_CHUNK * cloudInteriorWidth;
if (cloudMode != 2) radiusBlocks *= (float) Math.sqrt(2);
float verticalSlack = cloudElevation;
final Entity view = mc.renderViewEntity;
if (view != null) {
verticalSlack = Math.abs(cloudElevation + 0.33f - (float) view.posY) + 4.0f * scaleMult;
}
return Math.min(radiusBlocks + verticalSlack, MAX_FAR_PLANE_DISTANCE);
}
private void invalidateGeometry() {
anchorCellX = Integer.MIN_VALUE;
anchorCellZ = Integer.MIN_VALUE;
@@ -193,6 +227,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
cachedEdgesGeneration = Integer.MIN_VALUE;
cachedCloudInteriorHeight = Float.NaN;
cachedVertexCount = 0;
geomBuilt = false;
}
private void initProgram() {
@@ -215,23 +250,30 @@ public class CloudRenderer implements IResourceManagerReloadListener {
program.unbind();
}
private void uploadFogUniforms(float partialTicks, int renderRadius, float cloudInteriorWidth) {
private void uploadFogUniforms() {
final CloudUniforms u = program.getInterface();
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
u.setFogEnabled(fogEnabled);
if (fogEnabled) {
final float start = renderDistance * 16.0f;
final float end = renderRadius * CELLS_PER_CHUNK * cloudInteriorWidth;
final float range = end - start;
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? end / range : 1.0f);
final Vec3 sky = mc.theWorld.getSkyColor(mc.renderViewEntity, partialTicks);
u.setFogColor((float) sky.xCoord, (float) sky.yCoord, (float) sky.zCoord);
if (!fogEnabled) return;
final FogState fog = GLStateManager.getFogState();
final Vector3d color = fog.getFogColor();
u.setFogColor((float) color.x, (float) color.y, (float) color.z);
switch (fog.getFogMode()) {
case GL11.GL_LINEAR -> {
final float range = fog.getEnd() - fog.getStart();
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fog.getEnd() / range : 1.0f, 0.0f, 0.0f);
}
case GL11.GL_EXP -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
default -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
}
}
public boolean render(int cloudTicks, float partialTicks) {
if (mc.theWorld == null || mc.renderViewEntity == null) return false;
if (cloudMode != 2 || scaleMult <= 0) return false;
if (!enabled || scaleMult <= 0) return false;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline != null && pipeline.getCloudSetting() == CloudSetting.OFF) return false;
boolean textureSetupDone = false;
if (edges == null) {
@@ -266,41 +308,45 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final boolean emitTopFace = cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean emitBottomFace = cameraRelativeY <= 0.0F;
final boolean cameraInsideY = cameraRelativeY <= 0.0F && cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean cameraInsideCloud = cameraInsideY && edges.cellOpaque(anchorX, anchorZ);
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final boolean anchorInRange = vao != null
&& Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells;
final boolean insideBand = cloudMode == 2 && emitTopFace && emitBottomFace;
final boolean anchorInRange = geomBuilt
&& (insideBand
? anchorX == anchorCellX && anchorZ == anchorCellZ
: Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells);
final boolean geomCacheValid = anchorInRange
&& cachedEdgesGeneration == edgesGeneration
&& cachedEmitTopFace == emitTopFace
&& cachedEmitBottomFace == emitBottomFace
&& cachedCameraInsideCloud == cameraInsideCloud
&& cachedRenderRadius == renderRadius
&& cachedCloudInteriorHeight == cloudInteriorHeight;
if (!geomCacheValid) {
final int marginCells = cameraInsideCloud ? 0 : Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, cameraInsideCloud, marginCells);
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, marginCells);
anchorCellX = anchorX;
anchorCellZ = anchorZ;
anchorMarginCells = marginCells;
cachedEdgesGeneration = edgesGeneration;
cachedEmitTopFace = emitTopFace;
cachedEmitBottomFace = emitBottomFace;
cachedCameraInsideCloud = cameraInsideCloud;
cachedRenderRadius = renderRadius;
cachedCloudInteriorHeight = cloudInteriorHeight;
geomBuilt = true;
}
if (vao == null || cachedVertexCount == 0) return true;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
if (cloudTexId != -1 && !textureSetupDone) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cloudTexId);
} else {
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
}
final Vec3 color = mc.theWorld.getCloudColour(partialTicks);
float r = (float) color.xCoord;
@@ -337,10 +383,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
mv.pushMatrix();
mv.scale(cloudInteriorWidth, 1.0f, cloudInteriorWidth);
mv.translate(-(cameraRelativeX + anchorDriftX), cameraRelativeY, -(cameraRelativeZ + anchorDriftZ));
mvScratch.set(mv);
GLStateManager.getProjectionMatrix().mul(mv, mvpScratch);
mv.popMatrix();
u.mvp.set(mvpScratch);
uploadFogUniforms(partialTicks, renderRadius, cloudInteriorWidth);
u.mv.set(mvScratch);
uploadFogUniforms();
drawAllFactors(r, g, b, false);
program.unbind();
@@ -362,15 +410,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private void drawAllFactors(float r, float g, float b, boolean shadersActive) {
vao.bind();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GLStateManager.disableCull();
GLStateManager.disableBlend();
GLStateManager.glDepthMask(true);
GLStateManager.glColorMask(false, false, false, false);
// Depth prepass: alpha must match color pass so discard threshold writes same depth coverage.
if (shadersActive) {
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, CloudUniforms.ALPHA);
} else {
program.getInterface().setColorMult(1.0f, 1.0f, 1.0f);
}
final int totalQuads = cachedVertexCount / 4;
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, totalQuads * 6, GL11.GL_UNSIGNED_INT, 0L);
@@ -403,25 +449,32 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
vao.unbind();
GLStateManager.enableCull();
GLStateManager.glDepthMask(true);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.disableBlend();
}
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, boolean cameraInsideCloud, int marginCells) {
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, int marginCells) {
prepareEmitBuffer();
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
final int chunkRange = cameraInsideCloud ? 0 : (renderRadius + marginChunks);
final int outerLo = cameraInsideCloud ? -1 : -chunkRange;
final int outerHi = cameraInsideCloud ? 0 : chunkRange;
final int chunkRange = renderRadius + marginChunks;
final int outerLo = -chunkRange;
final int outerHi = chunkRange;
final int radiusCellsWithMargin = cameraInsideCloud ? CELLS_PER_CHUNK : (chunkRange * CELLS_PER_CHUNK);
final int radiusCellsWithMargin = chunkRange * CELLS_PER_CHUNK;
final int radiusCellsSq = radiusCellsWithMargin * radiusCellsWithMargin;
final float scrollX = anchorX * SCROLL_SPEED;
final float scrollZ = anchorZ * SCROLL_SPEED;
if (cloudMode != 2) {
emitFastLayer(chunkRange * CELLS_PER_CHUNK, scrollX, scrollZ);
finishGeometry();
return;
}
final int rows = (outerHi - outerLo + 1) * CELLS_PER_CHUNK;
final int lpr = (rows + 63) >>> 6;
final int minX = outerLo * CELLS_PER_CHUNK;
@@ -446,7 +499,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
for (int chunkX = outerLo; chunkX <= outerHi; chunkX++) {
for (int chunkZ = outerLo; chunkZ <= outerHi; chunkZ++) {
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, cameraInsideCloud, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
}
}
@@ -454,6 +507,53 @@ public class CloudRenderer implements IResourceManagerReloadListener {
emitTopBottomGlobalGreedy(emitTopFace, emitBottomFace, ye, scrollX, scrollZ);
}
if (emitTopFace && emitBottomFace) {
emitInteriorFaces(anchorX, anchorZ, y1, scrollX, scrollZ);
}
finishGeometry();
}
private void emitInteriorFaces(int anchorX, int anchorZ, double y1, float scrollX, float scrollZ) {
for (int dz = -1; dz <= 1; dz++) {
for (int dx = -1; dx <= 1; dx++) {
if (!edges.cellOpaque(anchorX + dx, anchorZ + dz)) continue;
final double x0 = dx + INTERIOR_INSET;
final double x1 = dx + 1 - INTERIOR_INSET;
final double z0 = dz + INTERIOR_INSET;
final double z1 = dz + 1 - INTERIOR_INSET;
final double yb = INTERIOR_INSET;
final double yt = y1 - INTERIOR_INSET;
final float uL = dx * SCROLL_SPEED + scrollX;
final float uR = (dx + 1) * SCROLL_SPEED + scrollX;
final float uC = (dx + 0.5F) * SCROLL_SPEED + scrollX;
final float vN = dz * SCROLL_SPEED + scrollZ;
final float vS = (dz + 1) * SCROLL_SPEED + scrollZ;
final float vC = (dz + 0.5F) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, yb, z1, uL, vS, x1, yb, z1, uR, vS, x1, yb, z0, uR, vN, x0, yb, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, yt, z0, uL, vN, x1, yt, z0, uR, vN, x1, yt, z1, uR, vS, x0, yt, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, yb, z0, uC, vN, x0, yt, z0, uC, vN, x0, yt, z1, uC, vS, x0, yb, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(x1, yb, z1, uC, vS, x1, yt, z1, uC, vS, x1, yt, z0, uC, vN, x1, yb, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, yb, z0, uL, vC, x1, yb, z0, uR, vC, x1, yt, z0, uR, vC, x0, yt, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, yt, z1, uL, vC, x1, yt, z1, uR, vC, x1, yb, z1, uR, vC, x0, yb, z1, uL, vC, F_Z, N_NEG_Z);
}
}
}
private void emitFastLayer(int radiusCells, float scrollX, float scrollZ) {
final double r0 = -radiusCells;
final double r1 = radiusCells;
final float u0 = -radiusCells * SCROLL_SPEED + scrollX;
final float u1 = radiusCells * SCROLL_SPEED + scrollX;
final float v0 = -radiusCells * SCROLL_SPEED + scrollZ;
final float v1 = radiusCells * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(QUAD_BYTES);
emitCloudQuad(r0, 0.0, r0, u0, v0, r1, 0.0, r0, u1, v0, r1, 0.0, r1, u1, v1, r0, 0.0, r1, u0, v1, F_BOTTOM, N_POS_Y);
}
private void finishGeometry() {
final int totalBytes = (int) (writeAddr - emitBufferAddr);
final int vertexCount = totalBytes / VERTEX_STRIDE;
if (vertexCount == 0) {
@@ -514,7 +614,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean cameraInsideCloud, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
final float chunkOffsetX = chunkX * (float) CELLS_PER_CHUNK;
final float chunkOffsetZ = chunkZ * (float) CELLS_PER_CHUNK;
final int baseU = chunkX * CELLS_PER_CHUNK + anchorX;
@@ -534,43 +634,11 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final int relX = chunkX * CELLS_PER_CHUNK + k;
final int relZ = chunkZ * CELLS_PER_CHUNK + j;
if (relX * relX + relZ * relZ > radiusCellsSq) continue;
if (cameraInsideCloud && (Math.abs(relX) > 1 || Math.abs(relZ) > 1)) continue;
emittableBits |= 1L << (j * CELLS_PER_CHUNK + k);
}
}
if (emittableBits == 0L) return;
if (cameraInsideCloud) {
long bits = emittableBits;
while (bits != 0L) {
final int bit = Long.numberOfTrailingZeros(bits);
bits &= bits - 1;
final int k = bit & 7;
final int j = bit >>> 3;
final double x0 = chunkOffsetX + k;
final double x1 = chunkOffsetX + k + 1;
final double z0 = chunkOffsetZ + j;
final double z1 = chunkOffsetZ + j + 1;
final double xe = x1 - EDGE_OVERLAP;
final double zs = z1 - EDGE_OVERLAP;
final float uC = (chunkOffsetX + k + 0.5F) * SCROLL_SPEED + scrollX;
final float vC = (chunkOffsetZ + j + 0.5F) * SCROLL_SPEED + scrollZ;
final float uL = (chunkOffsetX + k) * SCROLL_SPEED + scrollX;
final float uR = (chunkOffsetX + k + 1) * SCROLL_SPEED + scrollX;
final float vN = (chunkOffsetZ + j) * SCROLL_SPEED + scrollZ;
final float vS = (chunkOffsetZ + j + 1) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, 0.0, z1, uL, vS, x1, 0.0, z1, uR, vS, x1, 0.0, z0, uR, vN, x0, 0.0, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, ye, z0, uL, vN, x1, ye, z0, uR, vN, x1, ye, z1, uR, vS, x0, ye, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, 0.0, z0, uC, vN, x0, y1, z0, uC, vN, x0, y1, z1, uC, vS, x0, 0.0, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(xe, 0.0, z1, uC, vS, xe, y1, z1, uC, vS, xe, y1, z0, uC, vN, xe, 0.0, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, 0.0, z0, uL, vC, x1, 0.0, z0, uR, vC, x1, y1, z0, uR, vC, x0, y1, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, y1, zs, uL, vC, x1, y1, zs, uR, vC, x1, 0.0, zs, uR, vC, x0, 0.0, zs, uL, vC, F_Z, N_NEG_Z);
}
return;
}
if (emitTopFace || emitBottomFace) {
final int globalColStart = chunkX * CELLS_PER_CHUNK - gridMinX;
final int globalRowStart = chunkZ * CELLS_PER_CHUNK - gridMinZ;
@@ -9,6 +9,7 @@ final class CloudUniforms {
static final float ALPHA = 0.8f;
final GlUniformMatrix4f mvp;
final GlUniformMatrix4f mv;
final GlUniformFloat4v colorMult;
final GlUniformFloat4v fogParams;
final GlUniformFloat4v fogColor;
@@ -18,11 +19,12 @@ final class CloudUniforms {
private int lastFogEnabled = -1;
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
private final float[] uploadBuf = new float[4];
CloudUniforms(ShaderBindingContext ctx) {
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
mv = ctx.bindUniform("u_MVMatrix", GlUniformMatrix4f::new);
colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new);
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
@@ -51,14 +53,16 @@ final class CloudUniforms {
}
}
void setFogParams(float x, float y) {
if (x != lastFogParamX || y != lastFogParamY) {
void setFogParams(float x, float y, float z, float w) {
if (x != lastFogParamX || y != lastFogParamY || z != lastFogParamZ || w != lastFogParamW) {
lastFogParamX = x;
lastFogParamY = y;
lastFogParamZ = z;
lastFogParamW = w;
uploadBuf[0] = x;
uploadBuf[1] = y;
uploadBuf[2] = 0f;
uploadBuf[3] = 0f;
uploadBuf[2] = z;
uploadBuf[3] = w;
fogParams.set(uploadBuf);
}
}
@@ -7,8 +7,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public class AngelicaRenderQueue {
private static final Thread MAIN_THREAD = Thread.currentThread();
private static final Queue<Runnable> TASKS = new ConcurrentLinkedQueue<>();
// Metrics
@@ -33,11 +34,11 @@ public class AngelicaRenderQueue {
}
private static final Executor EXECUTOR = (runnable) -> {
if(Thread.currentThread() == MAIN_THREAD) {
if(GLStateManager.isMainThread()) {
runnable.run();
} else {
TASKS.add(runnable);
LockSupport.unpark(MAIN_THREAD);
LockSupport.unpark(GLStateManager.getMainThread());
}
};
@@ -45,6 +46,11 @@ public class AngelicaRenderQueue {
return EXECUTOR;
}
public static void submit(Runnable runnable) {
TASKS.add(runnable);
LockSupport.unpark(GLStateManager.getMainThread());
}
public static int processTasks(int max) {
int tasksRun = 0;
while(tasksRun < max) {
@@ -0,0 +1,229 @@
package com.gtnewhorizons.angelica.rendering;
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.QuadConverter;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import net.coderbot.iris.gl.image.GlImage;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.player.EntityPlayer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Skins in 1.7.10 are 64x32 while modern has 64x64 textures. We have to address it here.
*/
public final class PlayerReflectionCapture {
public static final int VERTEX_COUNT = 288;
public static final int FLOATS_PER_VERTEX = 5; // x, y, z, u, v
private static final int RAW_INTS_PER_VERTEX = 8;
private static final int VERTEX_FLAGS = VertexFlags.convertToFlags(true, false, true, false);
private static boolean active;
private static boolean drawnThisCapture;
@Getter
private static Object target;
@Getter
private static EntityPlayer targetEntity;
private static int vbo;
private static int vao;
private static int[] rawData;
private static float[] vertexScratch;
private static int skin64Tex;
private static ByteBuffer skinPixels;
private static int lastCapturedSkin;
private static int lastUploadedAtlas;
private static int skinCopyWidth = 64;
private static int skinCopyHeight = 32;
private static ByteBuffer repackBuffer;
private static long repackAddress;
private static int repackCapacity;
private PlayerReflectionCapture() {}
public static void begin(EntityPlayer player) {
active = false;
drawnThisCapture = false;
target = null;
targetEntity = null;
if (player == null) return;
final Render render = RenderManager.instance.getEntityRenderObject(player);
if (render instanceof RendererLivingEntity living && living.mainModel instanceof ModelBiped model) {
target = model;
targetEntity = player;
active = true;
}
}
public static void end() {
active = false;
target = null;
targetEntity = null;
}
public static boolean shouldCapture() {
return active && !drawnThisCapture;
}
/**
* Emit the 288-vertex player batch VBO.
*/
public static void drawPlayerCapture(float[] xyzuv) {
if (drawnThisCapture) return;
drawnThisCapture = true;
if (xyzuv == null || xyzuv.length < VERTEX_COUNT * FLOATS_PER_VERTEX) return;
// Build the Tessellator layout
if (rawData == null) rawData = new int[VERTEX_COUNT * RAW_INTS_PER_VERTEX];
final int[] raw = rawData;
for (int i = 0; i < VERTEX_COUNT; i++) {
final int s = i * FLOATS_PER_VERTEX;
final int d = i * RAW_INTS_PER_VERTEX;
raw[d] = Float.floatToRawIntBits(xyzuv[s]);
raw[d + 1] = Float.floatToRawIntBits(xyzuv[s + 1]);
raw[d + 2] = Float.floatToRawIntBits(xyzuv[s + 2]);
raw[d + 3] = Float.floatToRawIntBits(xyzuv[s + 3]);
raw[d + 4] = Float.floatToRawIntBits(xyzuv[s + 4]);
raw[d + 5] = 0xFFFFFFFF; // color
raw[d + 6] = 0x00008000; // normal
raw[d + 7] = 0x00F000F0; // light
}
// Pack into our own native buffer using the player model's vertex format
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[VERTEX_FLAGS];
ensureRepackCapacity(VERTEX_COUNT * format.getVertexSize());
final long writePtr = format.writeToBuffer0(repackAddress, raw, VERTEX_COUNT * RAW_INTS_PER_VERTEX);
repackBuffer.position(0);
repackBuffer.limit((int) (writePtr - repackAddress));
// Upload to our dedicated VBO at offset 0
ensureVAO(format);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, repackBuffer, GL15.GL_STREAM_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Bind a 64x64 copy of the skin as `tex`
final int activeSkin = GLStateManager.getBoundTextureForServerState();
final boolean haveSkin = activeSkin != 0 && activeSkin != skin64Tex;
if (haveSkin) {
ensureSkin64Texture();
final GlImage atlas = GlImage.BY_NAME.get("playerAtlas_img");
final int atlasId = atlas != null ? atlas.getId() : 0;
final boolean skinChanged = activeSkin != lastCapturedSkin;
if (skinChanged) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(activeSkin);
final int srcW = info.getWidth();
final int srcH = info.getHeight();
if (srcW > 0 && srcH > 0) {
final int required = srcW * srcH * 4;
if (skinPixels == null || skinPixels.capacity() < required) {
skinPixels = ByteBuffer.allocateDirect(required).order(ByteOrder.nativeOrder());
}
// The 1.7.10 model UVs only address the top 64x32 region.
skinCopyWidth = Math.min(64, srcW);
skinCopyHeight = Math.min(32, srcH);
skinPixels.clear();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
lastCapturedSkin = activeSkin;
}
}
if (skinPixels != null && (skinChanged || atlasId != lastUploadedAtlas)) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
if (atlas != null) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, atlasId);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
}
lastUploadedAtlas = atlasId;
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
}
final ColorMask prevColor = GLStateManager.getColorMask();
final boolean pr = prevColor.red, pg = prevColor.green, pb = prevColor.blue, pa = prevColor.alpha;
final boolean prevDepthWrite = GLStateManager.getDepthState().isEnabled();
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glDepthMask(false);
QuadConverter.drawQuadsAsTriangles(0, VERTEX_COUNT);
GLStateManager.glDepthMask(prevDepthWrite);
GLStateManager.glColorMask(pr, pg, pb, pa);
if (haveSkin) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
}
GLStateManager.glBindVertexArray(0);
}
public static float[] vertexScratch() {
if (vertexScratch == null) vertexScratch = new float[VERTEX_COUNT * FLOATS_PER_VERTEX];
return vertexScratch;
}
private static void ensureVAO(VertexFormat format) {
if (vao != 0) return;
vbo = GLStateManager.glGenBuffers();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
private static void ensureSkin64Texture() {
if (skin64Tex != 0) return;
final int prev = GLStateManager.getBoundTextureForServerState();
skin64Tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
final ByteBuffer zero = ByteBuffer.allocateDirect(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zero);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prev);
}
private static void ensureRepackCapacity(int requiredBytes) {
if (repackBuffer != null && requiredBytes <= repackCapacity) return;
int newCapacity = Math.max(0x4000, repackCapacity);
while (newCapacity < requiredBytes) newCapacity *= 2;
if (repackBuffer != null) memFree(repackBuffer);
repackBuffer = memAlloc(newCapacity);
repackAddress = memAddress0(repackBuffer);
repackCapacity = newCapacity;
}
}
@@ -22,15 +22,14 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ChunkTrackerAccessor;
import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkStatus;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerImpl;
import org.embeddedt.embeddium.impl.util.PositionUtil;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
/**
* Configurable chunk tracker supporting "fast" and "aggressive" loading modes.
* Based on Beddium's SafeChunkTracker by Ven and FalsePattern.
@@ -41,109 +40,23 @@ import org.embeddedt.embeddium.impl.util.PositionUtil;
*
* Mode can be switched at runtime via options menu - triggers renderer reload.
*/
public class AngelicaChunkTracker extends ChunkTracker {
private final LongOpenHashSet chunkReadyForced = new LongOpenHashSet();
public class AngelicaChunkTracker extends ChunkTrackerImpl {
private ChunkTrackerAccessor self() {
return (ChunkTrackerAccessor) this;
@Override
public void forEachReady(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
super.forEachReady(sectionManager);
}
@Override
public void onChunkStatusAdded(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final var cur = prev | flags;
if (prev == cur) {
return;
public void forEachEvent(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
self().angelica$getChunkStatus().put(key, cur);
this.updateNeighbors(x, z);
}
@Override
public void onChunkStatusRemoved(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final int cur = prev & ~flags;
if (prev == cur) {
return;
}
if (cur == self().angelica$getChunkStatus().defaultReturnValue()) {
self().angelica$getChunkStatus().remove(key);
} else {
self().angelica$getChunkStatus().put(key, cur);
}
this.updateNeighbors(x, z);
}
private void updateNeighbors(int x, int z) {
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
this.updateMerged(ox + x, oz + z);
}
}
}
private void updateMerged(int x, int z) {
final long key = PositionUtil.packChunk(x, z);
final int selfFlags = self().angelica$getChunkStatus().get(key);
// If self doesn't have FLAG_ALL, can't be ready at all
if (selfFlags != ChunkStatus.FLAG_ALL) {
// Remove from ready if present
final boolean wasReady = self().angelica$getChunkReady().remove(key) | this.chunkReadyForced.remove(key);
if (wasReady && !self().angelica$getLoadQueue().remove(key)) {
self().angelica$getUnloadQueue().add(key);
}
return;
}
// Check if all neighbors also have FLAG_ALL
int mergedFlags = selfFlags;
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
mergedFlags &= self().angelica$getChunkStatus().get(PositionUtil.packChunk(ox + x, oz + z));
}
}
if (mergedFlags == ChunkStatus.FLAG_ALL) {
// All neighbors ready - add to both sets -- Trigger load if newly added to chunkReadyForced (first time ready)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().add(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
} else {
// Self ready but neighbors not - add to forced, remove from proper
// Trigger load if state changed (moved from proper to forced, or newly forced)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().remove(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
}
}
@Override
public LongCollection getReadyChunks() {
if (AngelicaConfig.useVanillaChunkTracking) {
// Aggressive mode: return both ready sets
final LongOpenHashSet combined = new LongOpenHashSet(self().angelica$getChunkReady());
combined.addAll(this.chunkReadyForced);
return LongSets.unmodifiable(combined);
} else {
// Default mode: only return chunks with all neighbors ready
return LongSets.unmodifiable(self().angelica$getChunkReady());
}
}
@Override
public void forEachEvent(ChunkEventHandler loadEventHandler, ChunkEventHandler unloadEventHandler) {
ChunkTracker.forEachChunk(self().angelica$getUnloadQueue(), unloadEventHandler);
self().angelica$getUnloadQueue().clear();
ChunkTracker.forEachChunk(self().angelica$getLoadQueue(), loadEventHandler);
self().angelica$getLoadQueue().clear();
super.forEachEvent(sectionManager);
}
}
@@ -1,5 +1,8 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
@@ -53,7 +56,18 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
final ChunkTaskProvider provider = ChunkTaskRegistry.getActiveProvider();
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, 0, 16, provider.threadCount(), provider);
final int minSection;
final int maxSection;
if (ModStatus.isCubicChunksLoaded && world instanceof ICubicWorld) {
minSection = CubicChunksAPI.getMinSectionY(world);
maxSection = CubicChunksAPI.getMaxSectionYExclusive(world);
} else {
minSection = 0;
maxSection = 16;
}
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, minSection, maxSection, provider.threadCount(), provider);
}
public void setCameraPosition(double x, double y, double z) {
@@ -125,6 +139,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
if (chunk.isEmpty()) {
return true;
}
if (ModStatus.isCubicChunksLoaded) {
final var section = CubicChunksAPI.getCubeStorage(this.world, x, y, z);
return section == null || section.isEmpty();
}
final var array = chunk.getBlockStorageArray();
if (y < 0 || y >= array.length) {
return true;
@@ -198,6 +218,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
return IrisShaderProviderHolder.isShadowPass();
}
public void markShadowGraphDirty() {
if (this.shadowRenderListManager != null) {
this.shadowRenderListManager.setNeedsUpdate(true);
}
}
@Override
public Collection<String> getDebugStrings() {
List<String> list = new ArrayList<>(super.getDebugStrings());
@@ -1,15 +1,10 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
@@ -17,6 +12,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraftforge.client.MinecraftForgeClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.gl.device.CommandList;
@@ -32,10 +28,18 @@ import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, AngelicaRenderSectionManager, BlockRenderLayer, TileEntity, CeleritasWorldRenderer.TileEntityRenderContext> implements IDynamicLightWorldRenderer {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
@@ -110,12 +114,20 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
@Override
public int getMinimumBuildHeight() {
return 0;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMinHeight(this.world);
} else {
return 0;
}
}
@Override
public int getMaximumBuildHeight() {
return 256;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMaxHeight(this.world);
} else {
return 256;
}
}
@Override
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder;
import com.cardinalstar.cubicchunks.api.event.CubeEvent;
import com.cardinalstar.cubicchunks.util.CubePos;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class CubeStatusEvents {
public static void init() {
MinecraftForge.EVENT_BUS.register(new CubeStatusEvents());
}
private ChunkTracker getTracker() {
return ChunkTrackerHolder.get(Minecraft.getMinecraft().theWorld);
}
@SubscribeEvent
public void onCubeLoaded(CubeEvent.Load event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeLoaded(pos.getX(), pos.getY(), pos.getZ());
}
}
@SubscribeEvent
public void onCubeUnloaded(CubeEvent.Unload event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeUnloaded(pos.getX(), pos.getY(), pos.getZ());
}
}
}
@@ -0,0 +1,157 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import com.cardinalstar.cubicchunks.util.HashMap3D;
import com.cardinalstar.cubicchunks.util.HashSet3D;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
public class CubeStatusTracker implements ChunkTracker {
/// Cubes that have been synced to the client and are currently loaded.
private final HashSet3D loadedCubes = new HashSet3D();
/// Cubes that have their neighbours loaded (as needed) and are ready to be rendered.
private final HashSet3D validCubes = new HashSet3D();
private final HashMap3D<PendingState> changedCubes = new HashMap3D<>();
private enum PendingState {
Add,
Remove
}
/// When true, adjacent cubes will not be checked.
private boolean fastMode = false;
public CubeStatusTracker() {
}
@Override
public void setFastMode(boolean fastMode) {
this.fastMode = fastMode;
this.loadedCubes.forEach(this::pollCube);
}
@Override
public boolean isFastModeEnabled() {
return fastMode;
}
public void onCubeLoaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.add(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
public void onCubeUnloaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.remove(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
private void onCubeChanged(int cubeX, int cubeY, int cubeZ) {
pollCube(cubeX, cubeY, cubeZ);
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
pollCube(cubeX + dx, cubeY + dy, cubeZ + dz);
}
}
}
}
}
private void pollCube(int cubeX, int cubeY, int cubeZ) {
boolean renderable = canRender(cubeX, cubeY, cubeZ);
boolean isRendering = validCubes.contains(cubeX, cubeY, cubeZ);
if (renderable != isRendering) {
changedCubes.put(cubeX, cubeY, cubeZ, renderable ? PendingState.Add : PendingState.Remove);
} else {
changedCubes.remove(cubeX, cubeY, cubeZ);
}
}
private boolean canRender(int cubeX, int cubeY, int cubeZ) {
if (!loadedCubes.contains(cubeX, cubeY, cubeZ)) return false;
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
if (!loadedCubes.contains(cubeX + dx, cubeY + dy, cubeZ + dz)) return false;
}
}
}
}
return true;
}
@Override
public void onChunkStatusAdded(int i, int i1, int i2) {
// do nothing
}
@Override
public void onChunkStatusRemoved(int i, int i1, int i2) {
// do nothing
}
@Override
public void forEachReady(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
} else {
validCubes.remove(x, y, z);
}
});
changedCubes.clear();
for (var pos : validCubes.fastEntryIterable()) {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
renderSectionManager.onSectionAdded(x, y, z);
}
}
@Override
public void forEachEvent(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
renderSectionManager.onSectionAdded(x, y, z);
} else {
validCubes.remove(x, y, z);
renderSectionManager.onSectionRemoved(x, y, z);
}
});
changedCubes.clear();
}
}
@@ -1,26 +1,12 @@
package com.gtnewhorizons.angelica.rendering.celeritas.world;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import java.util.Arrays;
import java.util.List;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
import net.minecraft.block.Block;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.MathHelper;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
@@ -30,9 +16,24 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
/**
* Takes a slice of world state (block states, biome and light data arrays) and copies the data for use in off-thread
@@ -111,7 +112,13 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
public static ChunkRenderContext prepare(World world, ChunkSectionPos origin, ClonedChunkSectionCache sectionCache) {
final Chunk chunk = world.getChunkFromChunkCoords(origin.x, origin.z);
final ExtendedBlockStorage section = chunk.getBlockStorageArray()[origin.y];
final ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, origin.x, origin.y, origin.z);
} else {
section = chunk.getBlockStorageArray()[origin.y];
}
// If the chunk section is absent or empty, terminate early
if (section == null || section.isEmpty()) {
@@ -241,7 +248,10 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
@Override
public int getLightBrightnessForSkyBlocks(int x, int y, int z, int min) {
if (y < 0 || y >= 256 || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
final int minHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMinHeight(this.world) : 0;
final int maxHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMaxHeight(this.world) : 256;
if (y < minHeight || y >= maxHeight || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
// skyBrightness = 15, blockBrightness = min
return (15 << 20) | (min << 4);
}
@@ -372,7 +382,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
if (!blockBoxContains(this.volume, x, y, z)) {
return 0;
}
y = MathHelper.clamp_int(y, 0, 255);
y = CompatMathHelper.clampHeight(y);
final int relX = x - this.baseX;
final int relY = y - this.baseY;
@@ -4,12 +4,11 @@ import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ExtendedBlockStorageExt;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.lotr.LOTRCompat;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import mega.fluidlogged.internal.mixin.hook.FLSubChunk;
@@ -25,9 +24,13 @@ import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraftforge.fluids.Fluid;
import java.util.Map;
public class ClonedChunkSection {
private static final ExtendedBlockStorage EMPTY_SECTION = new ExtendedBlockStorage(0, false);
private static final int SECTION_BLOCK_LENGTH = 16;
private static final int BIOME_DATA_LENGTH = SECTION_BLOCK_LENGTH * SECTION_BLOCK_LENGTH;
private final ClonedChunkSectionCache backingCache;
private final World world;
@@ -53,7 +56,13 @@ public class ClonedChunkSection {
throw new RuntimeException("Couldn't retrieve chunk at " + pos.toChunkPos());
}
ExtendedBlockStorage section = getChunkSection(chunk, pos);
ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, pos.x, pos.y, pos.z);
} else {
section = getChunkSection(chunk, pos);
}
if (section == null) {
section = EMPTY_SECTION;
@@ -62,7 +71,7 @@ public class ClonedChunkSection {
this.pos = pos;
this.data = new ExtendedBlockStorageExt(chunk, section);
this.biomeData = new BiomeGenBase[16 * 16];
this.biomeData = new BiomeGenBase[BIOME_DATA_LENGTH];
copyBlockEntities(chunk, pos);
@@ -72,15 +81,22 @@ public class ClonedChunkSection {
}
private void fillBiomeData(Chunk chunk) {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < 16; lz++) {
for (int lx = 0; lx < 16; lx++) {
this.biomeData[lx | (lz << 4)] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
if (LOTRCompat.isLotrProvider(world.provider)) {
for (int z = pos.getMinZ(); z <= pos.getMaxZ(); z++) {
for (int x = pos.getMinX(); x <= pos.getMaxX(); x++) {
this.biomeData[((z & 15) << 4) | (x & 15)] = world.getBiomeGenForCoords(x, z);
}
}
} else {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < SECTION_BLOCK_LENGTH; lz++) {
for (int lx = 0; lx < SECTION_BLOCK_LENGTH; lx++) {
this.biomeData[(lz << 4) | lx] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
}
}
}
}
@SuppressWarnings("unchecked")
private void copyBlockEntities(Chunk chunk, ChunkSectionPos pos) {
this.tileEntities.clear();
@@ -88,22 +104,13 @@ public class ClonedChunkSection {
map.readLock();
try {
final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = map.getDelegate();
if (!delegate.isEmpty()) {
final int minY = pos.getMinY();
final int maxY = pos.getMaxY();
for (Object2ObjectMap.Entry<ChunkPosition, TileEntity> entry : Object2ObjectMaps.fastIterable(delegate)) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
this.tileEntities.put(ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15), te);
}
}
}
map.forEachInYRange(pos.getMinY(), pos.getMaxY(), entry -> {
final ChunkPosition tePos = entry.getKey();
this.tileEntities.put(
ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15),
entry.getValue()
);
});
} finally {
map.readUnlock();
}
@@ -169,12 +176,15 @@ public class ClonedChunkSection {
this.lastUsedTimestamp = timestamp;
}
public static boolean isOutOfBuildLimitVertically(int y) {
public static boolean isOutOfBuildLimitVertically(World world, int y) {
if (ModStatus.isCubicChunksLoaded) {
return y < CubicChunksAPI.getMinHeight(world) || y >= CubicChunksAPI.getMaxHeight(world);
}
return y < 0 || y >= 256;
}
private static ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(ChunkSectionPos.getBlockCoord(pos.y))) {
private ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(this.world, ChunkSectionPos.getBlockCoord(pos.y))) {
return chunk.getBlockStorageArray()[pos.y];
}
return null;
@@ -0,0 +1,166 @@
package com.gtnewhorizons.angelica.shadercompat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Maybe overcomplicated, but it's required to bridge the gap between the modern glint texture and white texture 1.7.10
* has. Some shaders are able to render the glint just fine as they seem to have some amount of support for older
* Minecraft, but others like AstraLex do *not* render correctly. It expects the color to be within the texture.
* This might introduce some oddities that I'm not seeing, but for now this appears to fix the issue.
*/
public final class ShaderGlint {
private static final ResourceLocation GLINT_MASK = new ResourceLocation("textures/misc/enchanted_item_glint.png");
private static final int MAX_CACHE = 64;
private static boolean maskLoaded;
private static boolean maskFailed;
private static int maskW;
private static int maskH;
private static int[] maskLum;
private static final Int2IntLinkedOpenHashMap colorToTexture = new Int2IntLinkedOpenHashMap();
static {
colorToTexture.defaultReturnValue(0);
}
private static final GlintColorHandler COLOR_HANDLER = ShaderGlint::onColorChanged;
private static boolean injecting;
private static boolean swapped;
private static int depth;
private static int prevTexture;
private static ByteBuffer tintBuffer;
private ShaderGlint() {}
public static void beginGlint() {
if (ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
// If a glint span is already active, keep the outer span's saved state and
// let the nested render ride on its handler.
if (depth++ > 0) return;
swapped = false;
injecting = false;
// Save the texture bound before the glint section so endGlint can put it back if we swap
prevTexture = GLStateManager.getBoundTextureForServerState(0);
// Observe vertex-color changes for the rest of the glint section
GLSMHooks.glintColorHandler = COLOR_HANDLER;
}
public static void endGlint() {
if (depth == 0 || --depth > 0) return;
if (GLSMHooks.glintColorHandler != COLOR_HANDLER) return;
GLSMHooks.glintColorHandler = null;
if (swapped) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prevTexture);
}
swapped = false;
injecting = false;
}
private static void onColorChanged(float red, float green, float blue, float alpha) {
if (injecting) return;
final int texture = getTintedTexture(red, green, blue);
if (texture <= 0) return;
injecting = true;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, alpha);
injecting = false;
swapped = true;
}
private static int getTintedTexture(float r, float g, float b) {
ensureMaskLoaded();
if (!maskLoaded) return -1;
final int ri = clamp8(r);
final int gi = clamp8(g);
final int bi = clamp8(b);
final int key = (ri << 16) | (gi << 8) | bi;
final int cached = colorToTexture.getAndMoveToLast(key);
if (cached != 0) return cached;
if (colorToTexture.size() >= MAX_CACHE) {
GLStateManager.glDeleteTextures(colorToTexture.remove(colorToTexture.firstIntKey()));
}
final int texture = uploadTinted(ri / 255.0F, gi / 255.0F, bi / 255.0F);
colorToTexture.putAndMoveToLast(key, texture);
return texture;
}
private static int uploadTinted(float r, float g, float b) {
final ByteBuffer pixels = tintBuffer;
pixels.clear();
for (final int l : maskLum) {
pixels.put((byte) Math.round(l * r));
pixels.put((byte) Math.round(l * g));
pixels.put((byte) Math.round(l * b));
pixels.put((byte) 0xFF);
}
pixels.flip();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
// Match the vanilla glint texture
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, maskW, maskH, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return texture;
}
private static void ensureMaskLoaded() {
if (maskLoaded || maskFailed) return;
try (final InputStream in = Minecraft.getMinecraft().getResourceManager().getResource(GLINT_MASK).getInputStream()) {
final BufferedImage img = ImageIO.read(in);
maskW = img.getWidth();
maskH = img.getHeight();
maskLum = new int[maskW * maskH];
final int[] argb = img.getRGB(0, 0, maskW, maskH, null, 0, maskW);
for (int i = 0; i < argb.length; i++) {
final int p = argb[i];
final int rr = (p >> 16) & 0xFF;
final int gg = (p >> 8) & 0xFF;
final int bb = p & 0xFF;
maskLum[i] = (rr + gg + bb) / 3;
}
tintBuffer = BufferUtils.createByteBuffer(maskW * maskH * 4);
maskLoaded = true;
} catch (final Exception e) {
maskFailed = true;
}
}
private static int clamp8(float v) {
final int i = Math.round(v * 255.0F);
if (i < 0) return 0;
return Math.min(i, 255);
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
@@ -10,15 +11,30 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
/**
* Thread-safe wrapper around Object2ObjectOpenHashMap for chunkTileEntityMap.
* Thread-safe wrapper around a tile entity map for chunkTileEntityMap.
* May wrap either a plain Object2ObjectOpenHashMap (vanilla) or a ColumnTileEntityMap (cubic chunks).
*/
public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = new Object2ObjectOpenHashMap<>();
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> flatDelegate;
private final Map<ChunkPosition, TileEntity> delegate;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ConcurrentLinkedQueue<ChunkPosition> invalidationQueue = new ConcurrentLinkedQueue<>();
/** Vanilla path: backs the map with a fresh flat map. */
public ConcurrentTileEntityMap() {
this.flatDelegate = new Object2ObjectOpenHashMap<>();
this.delegate = this.flatDelegate;
}
/** cubic chunks path: wraps an existing map (e.g. ColumnTileEntityMap). */
public ConcurrentTileEntityMap(Map<ChunkPosition, TileEntity> toWrap) {
this.flatDelegate = null;
this.delegate = toWrap;
}
public void queueInvalidation(ChunkPosition pos) {
invalidationQueue.add(pos);
}
@@ -59,14 +75,27 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
}
}
public Object2ObjectOpenHashMap<ChunkPosition, TileEntity> getDelegate() {
return delegate;
}
public TileEntity putDirect(ChunkPosition key, TileEntity value) {
return delegate.put(key, value);
}
/**
* Iterates all non-invalid tile entities whose Y coordinate falls within [minY, maxY].
* Caller must hold the read lock.
*/
public void forEachInYRange(int minY, int maxY, Consumer<Map.Entry<ChunkPosition, TileEntity>> action) {
final Iterable<? extends Map.Entry<ChunkPosition, TileEntity>> entries =
flatDelegate != null ? Object2ObjectMaps.fastIterable(flatDelegate) : delegate.entrySet();
for (Map.Entry<ChunkPosition, TileEntity> entry : entries) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
action.accept(entry);
}
}
}
private static boolean isRenderWorkerThread() {
return RenderThreadContext.hasWorldSlice();
}
@@ -135,7 +164,10 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
lock.writeLock().lock();
try {
invalidationQueue.clear();
delegate.clear();
if (flatDelegate != null) {
flatDelegate.clear();
}
// wrapped delegate manages its own TE lifetime; skip clear()
} finally {
lock.writeLock().unlock();
}
@@ -4,6 +4,8 @@ import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import jss.notfine.core.Settings;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
@@ -19,6 +21,13 @@ public class Zoom {
@Getter
private static float zoom = ZOOM_DEFAULT;
public static final float ZOOM_SMOOTH_SNAP_EPSILON = 0.001F;
@Getter
private static float currentZoom = 1.0F;
private static long zoomLerpLastNano = 0L;
@Getter
private static final KeyBinding zoomKey = new KeyBinding("Zoom", 0, "key.categories.misc");
@@ -38,6 +47,29 @@ public class Zoom {
zoom = MathHelper.clamp_float((float) (zoom * Math.pow(ZOOM_STEP, Integer.signum(eventDWheel))), ZOOM_MIN, ZOOM_MAX);
}
public static void updateZoomLerp() {
final long now = System.nanoTime();
float dt = zoomLerpLastNano == 0L ? 0.0F : (now - zoomLerpLastNano) / 1.0e9F;
zoomLerpLastNano = now;
final float target = zoomEnabled ? zoom : 1.0F;
if (dt <= 0.0F || !(boolean) Settings.ZOOM_SMOOTH.option.getStore()) {
currentZoom = target;
return;
}
// Clamp dt so a long frame (alt-tab, GC pause) does not snap the zoom.
dt = Math.min(dt, 0.1F);
final int rate = (int) Settings.ZOOM_SMOOTH_SPEED.option.getStore();
currentZoom += (target - currentZoom) * (1.0F - (float) Math.exp(-dt * rate));
if (Math.abs(target - currentZoom) < ZOOM_SMOOTH_SNAP_EPSILON) {
currentZoom = target;
}
}
private static void resetMouseFilters(Minecraft mc) {
if (mc.entityRenderer != null) {
((IMouseFilterExt) mc.entityRenderer.mouseFilterXAxis).angelica$reset();
@@ -79,4 +111,10 @@ public class Zoom {
handleKeyEvent();
}
}
@SubscribeEvent
public void onRenderTick(TickEvent.RenderTickEvent e) {
if (e.phase != TickEvent.Phase.START) return;
updateZoomLerp();
}
}
@@ -6,6 +6,7 @@ import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import com.prupe.mcpatcher.mal.block.BlockStateMatcher;
import net.minecraftforge.common.util.ForgeDirection;
final class BlockOrientation extends RenderBlockState {
@@ -39,12 +40,13 @@ final class BlockOrientation extends RenderBlockState {
private int altMetadata;
private int metadataBits;
private int renderType;
private boolean iCtmBlock;
private int blockFace;
private int textureFace;
private int textureFaceOrig;
private int rotateUV;
private boolean flipBottom;
private boolean flipped;
@Override
public void clear() {
@@ -57,7 +59,7 @@ final class BlockOrientation extends RenderBlockState {
offsetsComputed = false;
haveOffsets = false;
dx = dy = dz = 0;
flipBottom = !fixedBottomFaceUV;
flipped = false;
}
@Override
@@ -75,6 +77,11 @@ final class BlockOrientation extends RenderBlockState {
return z;
}
@Override
public int getMetadata() {
return metadata;
}
@Override
public int getBlockFace() {
return blockFace;
@@ -102,7 +109,10 @@ final class BlockOrientation extends RenderBlockState {
@Override
public int[] getOffset(int blockFace, int relativeDirection) {
return NEIGHBOR_OFFSET[flipBottom && blockFace == 0 ? 1 : blockFace][rotateUV(relativeDirection)];
if(flipped){
blockFace = ForgeDirection.OPPOSITES[blockFace];
}
return NEIGHBOR_OFFSET[blockFace][rotateUV(relativeDirection)];
}
@Override
@@ -146,14 +156,14 @@ final class BlockOrientation extends RenderBlockState {
}
public void setFlipBottom(){
flipBottom = true;
public void flipFace() {
flipped = true;
}
@Override
public boolean shouldConnectByBlock(Block neighbor, int neighborX, int neighborY, int neighborZ) {
return block == neighbor
&& (metadataBits & (1 << blockAccess.getBlockMetadata(neighborX, neighborY, neighborZ))) != 0;
return iCtmBlock ? ((ICTMBlock) block).shouldConnectByBlock(this, neighbor, neighborX, neighborY, neighborZ)
: block == neighbor && (metadataBits & (1 << blockAccess.getBlockMetadata(neighborX, neighborY, neighborZ))) != 0;
}
@Override
@@ -171,11 +181,12 @@ final class BlockOrientation extends RenderBlockState {
copy.altMetadata = altMetadata;
copy.metadataBits = metadataBits;
copy.renderType = renderType;
copy.iCtmBlock = iCtmBlock;
copy.blockFace = blockFace;
copy.textureFace = textureFace;
copy.textureFaceOrig = textureFaceOrig;
copy.rotateUV = rotateUV;
copy.flipBottom = flipBottom;
copy.flipped = flipped;
copy.blockAccess = blockAccess;
copy.block = block;
@@ -199,6 +210,7 @@ final class BlockOrientation extends RenderBlockState {
this.z = z;
renderType = block.getRenderType();
metadata = altMetadata = blockAccess.getBlockMetadata(x, y, z);
iCtmBlock = block instanceof ICTMBlock;
offsetsComputed = false;
}
@@ -208,7 +220,7 @@ final class BlockOrientation extends RenderBlockState {
rotateUV = 0;
textureFace = blockFaceToTextureFace(blockFace);
metadataBits = (1 << metadata) | (1 << altMetadata);
flipBottom = !fixedBottomFaceUV;
flipped = !fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal();
}
void setBlockMetadata(Block block, int metadata, int face) {
@@ -219,6 +231,7 @@ final class BlockOrientation extends RenderBlockState {
renderType = block.getRenderType();
blockFace = textureFace = textureFaceOrig = face;
this.metadata = metadata;
iCtmBlock = block instanceof ICTMBlock;
metadataBits = 1 << metadata;
dx = dy = dz = 0;
rotateUV = 0;
@@ -9,6 +9,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Consumer;
import com.github.bsideup.jabel.Desugar;
import net.minecraft.block.Block;
@@ -34,10 +35,22 @@ public class CTMUtils {
public static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
private static class Overrides {
public static class Overrides {
private final List<TileOverride> all = new ArrayList<>();
private final Map<Block, List<BlockStateMatcher>> block = new IdentityHashMap<>();
private final Map<String, List<TileOverride>> tile = new HashMap<>();
public List<TileOverride> getAll() {
return all;
}
public Map<Block, List<BlockStateMatcher>> getBlock() {
return block;
}
public Map<String, List<TileOverride>> getTile() {
return tile;
}
}
private static Overrides overrides = new Overrides();
@@ -82,6 +95,8 @@ public class CTMUtils {
private static final AtomicInteger texturePackChangeCounter = new AtomicInteger(0);
private static final ArrayList<Consumer<Overrides>> ctmRegistrationCallbacks = new ArrayList<>();
static {
try {
Class.forName(MCPatcherUtils.RENDER_PASS_CLASS)
@@ -117,6 +132,9 @@ public class CTMUtils {
.listResources(TexturePackAPI.MCPATCHER_SUBDIR + "ctm", ".properties", true)) {
registerOverrideWithoutLock(newOverrides, TileOverride.create(resource, tileLoader));
}
for (Consumer<Overrides> callback : ctmRegistrationCallbacks) {
callback.accept(newOverrides);
}
}
for (ResourceLocation resource : BlendMethod.getAllBlankResources()) {
tileLoader.preloadTile(resource, false);
@@ -257,7 +275,7 @@ public class CTMUtils {
return RenderPassAPI.instance.skipDefaultRendering(block);
}
private static void registerOverrideWithoutLock(Overrides overrides, TileOverride override) {
public static void registerOverrideWithoutLock(Overrides overrides, TileOverride override) {
if (override != null && !override.isDisabled()) {
boolean registered = false;
List<BlockStateMatcher> matchingBlocks = override.getMatchingBlocks();
@@ -308,4 +326,8 @@ public class CTMUtils {
private static TileOverrideIterator.Metadata newMetadataIterator() {
return new TileOverrideIterator.Metadata(overrides.block, overrides.tile);
}
public static void addCtmRegistrationCallback(Consumer<Overrides> consumer) {
ctmRegistrationCallbacks.add(consumer);
}
}
@@ -4,6 +4,7 @@ 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.minecraftforge.common.util.ForgeDirection;
public final class CompactCtmQuadProcessor {
@@ -52,29 +53,33 @@ public final class CompactCtmQuadProcessor {
this.override = compact;
}
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon) {
if(renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.setFlipBottom();
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon, int face) {
boolean shouldFlip = face == ForgeDirection.DOWN.ordinal();
if(ForgeDirection.OPPOSITES[face] == renderBlockState.getBlockFace()){
shouldFlip = !shouldFlip;
}
if(shouldFlip && renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.flipFace();
}
int neighborBits = override.getNeighborBits(renderBlockState, origIcon);
int ctmIndex = TileOverride.neighborMap[neighborBits & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, renderBlockState, replacement);
renderWholeFace(rb, renderBlockState, replacement, face);
return true;
}
if (neighborBits == 0 || neighborBits == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(neighborBits)];
if (sprite != null) {
renderWholeFace(rb, renderBlockState, sprite);
renderWholeFace(rb, renderBlockState, sprite, face);
return true;
}
return false;
}
renderSplitFace(rb, renderBlockState, neighborBits);
renderSplitFace(rb, renderBlockState, neighborBits, face);
return true;
}
@@ -117,8 +122,7 @@ public final class CompactCtmQuadProcessor {
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections) {
final int face = renderBlockState.getBlockFace();
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections, int face) {
final double minX = rb.renderMinX, minY = rb.renderMinY, minZ = rb.renderMinZ;
final double maxX = rb.renderMaxX, maxY = rb.renderMaxY, maxZ = rb.renderMaxZ;
@@ -155,7 +159,7 @@ public final class CompactCtmQuadProcessor {
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
}
if (ao) {
@@ -219,18 +223,17 @@ public final class CompactCtmQuadProcessor {
return (sky << 16) | (block & 0xFFFF);
}
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
int face = renderBlockState.getBlockFace();
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
Block block = renderBlockState.getBlock();
int x = renderBlockState.getX();
int y = renderBlockState.getY();
@@ -0,0 +1,10 @@
package com.prupe.mcpatcher.ctm;
import net.minecraft.block.Block;
/**
* Implement this interface on blocks that support connected textures to allow them to control if they connect to neighbors or not
*/
public interface ICTMBlock {
boolean shouldConnectByBlock(RenderBlockState renderBlockState, Block neighbor, int neighborX, int neighborY, int neighborZ);
}
@@ -109,6 +109,8 @@ abstract public class RenderBlockState {
abstract public int getZ();
abstract public int getMetadata();
abstract public int getBlockFace();
abstract public int getTextureFace();
@@ -33,7 +33,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
protected final PropertiesFile properties;
public final PropertiesFile properties;
private final String baseFilename;
protected final TileLoader tileLoader;
protected final int renderPass;
@@ -79,7 +79,10 @@ public abstract class TileOverride implements Comparable<TileOverride> {
if (properties == null) {
return null;
}
return create(properties, tileLoader);
}
public static TileOverride create(PropertiesFile properties, TileLoader tileLoader) {
String method = properties.getString("method", "default")
.toLowerCase();
TileOverride override = null;
@@ -211,7 +214,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
for (int i = 0;; i++) {
ResourceLocation resource = TileLoader
.parseTileAddress(properties.getResource(), String.valueOf(i), blankResource);
if (!TexturePackAPI.hasResource(resource)) {
if (!TexturePackAPI.hasResource(resource) && !tileLoader.ignoreMissingTextures()) {
break;
}
if (!addIcon(resource)) {
@@ -237,7 +240,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
.parseTileAddress(properties.getResource(), token, blankResource);
if (resource == null) {
tileNames.add(null);
} else if (TexturePackAPI.hasResource(resource)) {
} else if (TexturePackAPI.hasResource(resource) || tileLoader.ignoreMissingTextures()) {
addIcon(resource);
} else {
properties.warning("could not find image %s", resource);
@@ -252,7 +255,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
for (int i = from; i <= to; i++) {
ResourceLocation resource = TileLoader
.parseTileAddress(properties.getResource(), String.valueOf(i), blankResource);
if (TexturePackAPI.hasResource(resource)) {
if (TexturePackAPI.hasResource(resource) || tileLoader.ignoreMissingTextures()) {
addIcon(resource);
} else {
properties.warning("could not find image %s", resource);
@@ -9,12 +9,12 @@ import net.minecraft.util.ResourceLocation;
import com.prupe.mcpatcher.MCLogger;
import com.prupe.mcpatcher.MCPatcherUtils;
final public class PropertiesFile {
public class PropertiesFile {
private final MCLogger logger;
private final ResourceLocation resource;
private final String prefix;
private final Properties properties;
protected final MCLogger logger;
protected final ResourceLocation resource;
protected final String prefix;
protected final Properties properties;
private int errorCount;
public static PropertiesFile get(MCLogger logger, ResourceLocation resource) {
@@ -147,4 +147,16 @@ final public class PropertiesFile {
return resource.equals(((PropertiesFile) that).resource);
}
}
public MCLogger getLogger() {
return logger;
}
public String getPrefix() {
return prefix;
}
public Properties getProperties() {
return properties;
}
}
@@ -275,7 +275,7 @@ public class TileLoader {
return false;
}
private boolean registerOneIcon(TextureMap textureMap, String mapName, Map<String, TextureAtlasSprite> map) {
protected boolean registerOneIcon(TextureMap textureMap, String mapName, Map<String, TextureAtlasSprite> map) {
ResourceLocation resource = tilesToRegister.iterator()
.next();
String name = resource.toString();
@@ -304,6 +304,16 @@ public class TileLoader {
return false;
}
}
TextureAtlasSprite icon = registerIconToMap(textureMap, resource, name);
map.put(name, icon);
iconMap.put(name, icon);
String extra = (width == height ? "" : ", " + (height / width) + " frames");
subLogger.finer("%s -> %s icon %dx%d%s", name, mapName, width, width, extra);
tilesToRegister.remove(resource);
return true;
}
protected TextureAtlasSprite registerIconToMap(TextureMap textureMap, ResourceLocation resource, String name) {
TextureAtlasSprite icon;
if(generatedTiles.contains(resource)){
icon = new GeneratedCTMAtlasSprite(this, textureMap.anisotropicFiltering > 1, name);
@@ -311,12 +321,7 @@ public class TileLoader {
}else{
icon = (TextureAtlasSprite)textureMap.registerIcon(name);
}
map.put(name, icon);
iconMap.put(name, icon);
String extra = (width == height ? "" : ", " + (height / width) + " frames");
subLogger.finer("%s -> %s icon %dx%d%s", name, mapName, width, width, extra);
tilesToRegister.remove(resource);
return true;
return icon;
}
public void finish() {
@@ -339,4 +344,8 @@ public class TileLoader {
public IIcon getIcon(ResourceLocation resource) {
return resource == null ? null : getIcon(resource.toString());
}
public boolean ignoreMissingTextures() {
return false;
}
}
+3 -1
View File
@@ -174,7 +174,9 @@ public enum Settings {
RenderStars.reloadStarRenderList(Minecraft.getMinecraft().renderGlobal);
}
},
VOID_FOG(new NotFineOptionTickBox(false, OptionImpact.LOW));
VOID_FOG(new NotFineOptionTickBox(false, OptionImpact.LOW)),
ZOOM_SMOOTH(new NotFineOptionTickBox(true, OptionImpact.LOW)),
ZOOM_SMOOTH_SPEED(new NotFineOptionSlider(20, 1, 30, 1, OptionImpact.LOW));
public final NotFineOption<?> option;
@@ -1,6 +1,7 @@
package jss.notfine.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import jss.notfine.config.NotFineConfig;
import jss.notfine.core.Settings;
@@ -169,6 +170,12 @@ public class NotFineGameOptionPages {
.build());
}
if (AngelicaConfig.enableZoom) {
groups.add(OptionGroup.createBuilder()
.add(Settings.ZOOM_SMOOTH.option)
.add(Settings.ZOOM_SMOOTH_SPEED.option)
.build());
}
return new OptionPage(I18n.format("options.video"), ImmutableList.copyOf(groups));
}
@@ -1,8 +1,22 @@
package me.jellysquid.mods.sodium.client.gui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import org.lwjgl.opengl.Display;
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import cpw.mods.fml.common.Optional.Method;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
@@ -22,19 +36,12 @@ import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
import me.jellysquid.mods.sodium.client.gui.options.storage.AngelicaOptionsStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.CubicChunksOptionStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.option.IrisVideoSettings;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode;
import org.lwjgl.opengl.Display;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SodiumGameOptionPages {
private static final SodiumOptionsStorage sodiumOpts = new SodiumOptionsStorage();
@@ -54,7 +61,13 @@ public class SodiumGameOptionPages {
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build());
if (ModStatus.isCubicChunksLoaded) {
firstGroupBuilder.add(getCCVerticalViewDistance());
}
if(Iris.enabled) {
final OptionImpl<GameSettings, Integer> maxShadowDistanceSlider = OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.iris.shadowDistance"))
.setTooltip(I18n.format("options.iris.shadowDistance.sodium_tooltip"))
@@ -499,6 +512,27 @@ public class SodiumGameOptionPages {
return new OptionPage(I18n.format("sodium.options.pages.performance"), ImmutableList.copyOf(groups));
}
@Method(modid = "cubicchunks")
private static OptionImpl<?, ?> getCCVerticalViewDistance() {
CubicChunksOptionStorage storage = new CubicChunksOptionStorage();
return OptionImpl.createBuilder(int.class, storage)
.setName(I18n.format("sodium.options.cc.vertical_view_distance.name"))
.setTooltip(I18n.format("sodium.options.cc.vertical_view_distance.tooltip"))
.setControl(option -> new SliderControl(
option,
CubicChunksVideoSettings.getMinVerticalViewDistance(),
CubicChunksVideoSettings.getMaxVerticalViewDistance(),
1,
ControlValueFormatter.quantity("options.chunks")))
.setBinding(
(options, value) -> options.verticalViewDistance = value,
options -> options.verticalViewDistance)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
}
public static OptionPage appearance() {
final List<OptionGroup> groups = new ArrayList<>();
@@ -532,6 +566,13 @@ public class SodiumGameOptionPages {
.build())
.build());
if (AngelicaConfig.enableZoom) {
groups.add(OptionGroup.createBuilder()
.add(Settings.ZOOM_SMOOTH.option)
.add(Settings.ZOOM_SMOOTH_SPEED.option)
.build());
}
return new OptionPage(I18n.format("sodium.options.pages.appearance"), ImmutableList.copyOf(groups));
}
@@ -0,0 +1,22 @@
package me.jellysquid.mods.sodium.client.gui.options.storage;
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
public class CubicChunksOptionStorage implements OptionStorage<CubicChunksOptionStorage.CubicChunksOptions> {
public static class CubicChunksOptions {
public int verticalViewDistance = CubicChunksVideoSettings.getVerticalViewDistance();
}
private final CubicChunksOptions options = new CubicChunksOptions();
@Override
public CubicChunksOptions getData() {
return options;
}
@Override
public void save() {
CubicChunksVideoSettings.setVerticalViewDistance(options.verticalViewDistance);
}
}
+8 -4
View File
@@ -36,6 +36,7 @@ import net.coderbot.iris.shaderpack.option.Profile;
import net.coderbot.iris.shaderpack.option.values.MutableOptionValues;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
@@ -567,11 +568,11 @@ public class Iris {
// If it's a folder-based shaderpack, just use the shaders subdirectory
shaderPackPath = shaderPackRoot.resolve("shaders");
}
if (!Files.exists(shaderPackPath)) {
logger.error("Could not load the shaderpack \"{}\" because it appears to lack a \"shaders\" directory", name);
return false;
if (!Files.exists(shaderPackPath)) {
logger.error("Could not load the shaderpack \"{}\" because it appears to lack a \"shaders\" directory", name);
return false;
}
}
@SuppressWarnings("unchecked")
@@ -762,6 +763,9 @@ public class Iris {
// allows shaderpacks to be changed at runtime
irisConfig.initialize();
// Reset the texture reload counter
CapturedRenderingState.INSTANCE.resetTextureReloadCount();
// Destroy all allocated resources
destroyEverything();
@@ -16,7 +16,7 @@ public enum FeatureFlags {
CUSTOM_IMAGES(() -> true, RenderSystem::supportsImageLoadStore),
SSBO(() -> true, RenderSystem::supportsSSBO),
HIGHER_SHADOWCOLOR(() -> true, () -> true),
TESSELLATION_SHADERS(() -> true, RenderSystem::supportsTesselation),
TESSELLATION_SHADERS(() -> false, RenderSystem::supportsTesselation),
REVERSED_CULLING(() -> true, () -> true),
CAN_DISABLE_WEATHER(() -> true, () -> true),
UNKNOWN(() -> false, () -> false);
@@ -4,5 +4,7 @@ public enum GlVersion {
GL_11,
GL_12,
GL_30,
GL_31
GL_31,
GL_33,
GL_41
}
@@ -12,7 +12,12 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GlImage extends GlObject {
public static final Map<String, GlImage> BY_NAME = new ConcurrentHashMap<>();
protected final String name;
protected final String samplerName;
protected final TextureType target;
@@ -42,6 +47,8 @@ public class GlImage extends GlObject {
setup(texture, width, height, depth);
GLStateManager.glBindTexture(target.getGlType(), 0);
BY_NAME.put(name, this);
}
protected void setup(int texture, int width, int height, int depth) {
@@ -106,6 +113,7 @@ public class GlImage extends GlObject {
@Override
protected void destroyInternal() {
GLStateManager.glDeleteTextures(handle());
BY_NAME.remove(name, this);
}
public InternalTextureFormat getInternalFormat() {
@@ -132,6 +132,11 @@ public class StandardMacros {
getBiomeCategoryDefines().forEach((category, ordinal) -> define(standardDefines, category, ordinal));
getVanillaBiomeDefines().forEach((biome, id) -> define(standardDefines, biome, id));
// rain type constants
define(standardDefines, "PPT_NONE", "0");
define(standardDefines, "PPT_RAIN", "1");
define(standardDefines, "PPT_SNOW", "2");
return ImmutableList.copyOf(standardDefines);
}
@@ -5,6 +5,8 @@ import net.coderbot.iris.gl.GlVersion;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import java.util.Optional;
@@ -72,10 +74,16 @@ public enum InternalTextureFormat {
RG32UI(GL30.GL_RG32UI, GlVersion.GL_30, PixelFormat.RG_INTEGER),
RGB32UI(GL30.GL_RGB32UI, GlVersion.GL_30, PixelFormat.RGB_INTEGER),
RGBA32UI(GL30.GL_RGBA32UI, GlVersion.GL_30, PixelFormat.RGBA_INTEGER),
// 2-bit normalized
RGBA2(GL11.GL_RGBA2, GlVersion.GL_11, PixelFormat.RGBA),
// 4-bit normalized
RGBA4(GL11.GL_RGBA4, GlVersion.GL_11, PixelFormat.RGBA),
// Mixed
R3_G3_B2(GL11.GL_R3_G3_B2, GlVersion.GL_11, PixelFormat.RGB),
RGB5_A1(GL11.GL_RGB5_A1, GlVersion.GL_11, PixelFormat.RGBA),
RGB565(GL41.GL_RGB565, GlVersion.GL_41, PixelFormat.RGB),
RGB10_A2(GL11.GL_RGB10_A2, GlVersion.GL_11, PixelFormat.RGBA),
RGB10_A2UI(GL33.GL_RGB10_A2UI, GlVersion.GL_33, PixelFormat.RGBA_INTEGER),
R11F_G11F_B10F(GL30.GL_R11F_G11F_B10F, GlVersion.GL_30, PixelFormat.RGB),
RGB9_E5(GL30.GL_RGB9_E5, GlVersion.GL_30, PixelFormat.RGB);
@@ -11,6 +11,7 @@ import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.shaderpack.ShaderPack;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuContainer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import java.util.ArrayList;
@@ -24,6 +25,12 @@ public class ShaderPackOptionList extends IrisGuiSlot {
private OptionMenuContainer container;
private final List<BaseEntry> entries = new ArrayList<>();
@Getter
private boolean searchModeActive = false;
private String typedSearchQuery = "";
private int reservedLeftWidth = 48; // Pixels reserved on the left of the header row for the search/clear button + gap.
private GuiTextField searchBox = null;
public ShaderPackOptionList(ShaderPackScreen screen, NavigationController navigation, ShaderPack pack, Minecraft client, int width, int height, int top, int bottom, int left, int right) {
super(client, width, height, top, bottom, 24);
this.navigation = navigation;
@@ -36,6 +43,74 @@ public class ShaderPackOptionList extends IrisGuiSlot {
this.container = pack.getMenuContainer();
}
/** Applies {@code query} to the option container and rebuilds the list rows. */
public void updateSearchQuery(String query) {
typedSearchQuery = query != null ? query : "";
if (this.container != null) {
this.container.setSearchQuery(query);
}
this.rebuild();
}
/** Resets all search state and clears the active filter without rebuilding entries. */
public void disableSearchMode() {
searchModeActive = false;
typedSearchQuery = "";
searchBox = null;
if (this.container != null) {
this.container.setSearchQuery(null);
}
}
public void disableSearchModeAndRebuild() {
disableSearchMode();
this.rebuild();
}
public void enableSearchModeAndRebuild() {
searchModeActive = true;
this.rebuild(); // First rebuild so that the button width is the current one
int listWidth = getListWidth();
int entryX = this.width / 2 - listWidth / 2;
int boxX = entryX + reservedLeftWidth + 3;
int boxY = this.top + 5; // got figured out by trial and error and works, don't question
int boxW = Math.max(40, listWidth - reservedLeftWidth - 5);
this.searchBox = new GuiTextField(Minecraft.getMinecraft().fontRenderer, boxX, boxY, boxW, 14);
this.searchBox.setMaxStringLength(64);
this.searchBox.setFocused(true);
}
/** True when the navigation stack is non-empty (a sub-screen is open). */
public boolean isOnSubScreen() {
return navigation != null && navigation.getCurrentScreen() != null;
}
public void setReservedLeftWidth(int w) {
reservedLeftWidth = Math.max(0, w);
}
/**
* Routes a keystroke into the live search text field, then propagates any text change
* to the option container and rebuilds the filtered list.
*/
public void handleSearchKeyTyped(char typedChar, int keyCode) {
if (searchBox == null || !searchModeActive) return;
searchBox.textboxKeyTyped(typedChar, keyCode);
String newText = searchBox.getText();
if (!newText.equals(typedSearchQuery)) {
updateSearchQuery(newText);
}
}
/** Draws the search text box overlay above the header row */
public void drawSearchBox() {
if (searchBox == null || !searchModeActive) return;
// Hide when the header has scrolled out of view
if (this.amountScrolled > 0.5f) return;
searchBox.drawTextBox();
}
public void rebuild() {
this.entries.clear();
this.amountScrolled = 0;
@@ -56,7 +131,7 @@ public class ShaderPackOptionList extends IrisGuiSlot {
}
public void addHeader(String text, boolean backButton) {
this.addEntry(new HeaderEntry(this.screen, this.navigation, text, backButton));
this.addEntry(new HeaderEntry(this.screen, this.navigation, text, backButton, this));
}
public void addWidgets(int columns, List<AbstractElementWidget<?>> elements) {
@@ -86,6 +161,15 @@ public class ShaderPackOptionList extends IrisGuiSlot {
return entries.size();
}
@Override
public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) {
// Pass the click to the search box first so it can update its focus state
if (searchModeActive && searchBox != null && amountScrolled <= 0.5f) {
searchBox.mouseClicked(mouseX, mouseY, mouseButton);
}
return super.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
protected boolean elementClicked(int index, boolean doubleClick, int mouseX, int mouseY, int mouseButton) {
final BaseEntry entry = this.entries.get(index);
@@ -14,6 +14,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.MathHelper;
import java.io.IOException;
import java.nio.file.FileSystems;
@@ -142,10 +143,15 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
topButtonRow.allowEnableShadersButton = !names.isEmpty();
int index = 0;
String selectedName = Iris.getIrisConfig().getShaderPackName().orElse(null);
int targetSelectionIndex = -1;
for (String name : names) {
this.addPackEntry(index, name);
if (name.equals(selectedName)) {
targetSelectionIndex = index;
}
index++;
addPackEntry(index, name);
}
// Try not to gaslight users in case they swap back and forth from lwjgl2 and lwjgl3
@@ -153,6 +159,29 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
? "pack.iris.list.label"
: "pack.iris.list.label.lwjgl2";
addLabelEntries(EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format(footerKey));
if (targetSelectionIndex != -1) {
scrollToIndex(targetSelectionIndex);
}
}
/**
* Scrolls the list to center the entry at {@code targetIndex} in the viewport.
*/
public void scrollToIndex(int targetIndex) {
if (targetIndex < 0 || targetIndex >= this.getSize()) {
return;
}
this.elementClicked(targetIndex, false, 0, 0);
this.selectedElement = targetIndex;
int viewportCenterY = (this.bottom - this.top) / 2;
int itemCenterPos = (targetIndex * this.slotHeight) + this.headerPadding + (this.slotHeight / 2);
float targetScroll = (float) (itemCenterPos - viewportCenterY);
float maxScroll = (float) this.func_148135_f();
this.amountScrolled = MathHelper.clamp_float(targetScroll, 0.0F, maxScroll);
}
public void addPackEntry(int index, String name) {
@@ -1,9 +1,11 @@
package net.coderbot.iris.gui.element.shaderoptions;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
import net.coderbot.iris.gui.element.IrisElementRow;
import net.coderbot.iris.gui.element.ShaderPackOptionList;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
@@ -16,58 +18,83 @@ import org.jetbrains.annotations.Nullable;
public class HeaderEntry extends BaseEntry {
public static final String BACK_BUTTON_TEXT = EnumChatFormatting.ITALIC + "< " + I18n.format("options.iris.back");
public static final String RESET_BUTTON_TEXT_INACTIVE = EnumChatFormatting.GRAY + I18n.format("options.iris.reset");
public static final String RESET_BUTTON_TEXT_ACTIVE = EnumChatFormatting.YELLOW + I18n.format("options.iris.reset");
public static final String RESET_HOLD_SHIFT_TOOLTIP = EnumChatFormatting.BOLD + I18n.format("options.iris.reset.tooltip.holdShift");
public static final String RESET_TOOLTIP = EnumChatFormatting.RED + I18n.format("options.iris.reset.tooltip");
public static final String IMPORT_TOOLTIP = I18n.format("options.iris.importSettings.tooltip");
// .withStyle(style -> style.withColor(TextColor.fromRgb(0x4da6ff)));
public static final String EXPORT_TOOLTIP = I18n.format("options.iris.exportSettings.tooltip");
// .withStyle(style -> style.withColor(TextColor.fromRgb(0xfc7d3d)));
private static String backButtonText() { return EnumChatFormatting.ITALIC + "< " + I18n.format("options.iris.back"); }
private static String resetButtonInactive() { return EnumChatFormatting.GRAY + I18n.format("options.iris.reset"); }
private static String resetButtonActive() { return EnumChatFormatting.YELLOW + I18n.format("options.iris.reset"); }
private static String resetHoldShiftTooltip() { return EnumChatFormatting.BOLD + I18n.format("options.iris.reset.tooltip.holdShift"); }
private static String resetTooltip() { return EnumChatFormatting.RED + I18n.format("options.iris.reset.tooltip"); }
private static String importTooltip() { return I18n.format("options.iris.importSettings.tooltip"); }
private static String exportTooltip() { return I18n.format("options.iris.exportSettings.tooltip"); }
private static String searchButtonText() { return I18n.format("options.iris.search.button"); }
private static String clearButtonText() { return I18n.format("options.iris.clear.button"); }
private static String searchTooltip() { return I18n.format("options.iris.search.tooltip"); }
private static String clearTooltip() { return I18n.format("options.iris.clear.tooltip"); }
private static final int MIN_SIDE_BUTTON_WIDTH = 42;
private static final int BUTTON_HEIGHT = 16;
private static final int SEARCH_BOX_GAP = 3;
private final ShaderPackScreen screen;
private final ShaderPackOptionList optionList;
private final @Nullable IrisElementRow backButton;
/** Width of the left-side button, used to clip the title text away from it. */
private final int leftButtonWidth;
private final IrisElementRow utilityButtons = new IrisElementRow();
private final IrisElementRow.TextButtonElement resetButton;
private final IrisElementRow.IconButtonElement importButton;
private final IrisElementRow.IconButtonElement exportButton;
private final String text;
public HeaderEntry(ShaderPackScreen screen, NavigationController navigation, String text, boolean hasBackButton) {
/** Non-null only on the main-screen header (search/clear toggle). */
private final @Nullable IrisElementRow.TextButtonElement searchToggleButton;
public HeaderEntry(ShaderPackScreen screen, NavigationController navigation, String text, boolean hasBackButton, ShaderPackOptionList optionList) {
super(navigation);
this.optionList = optionList;
this.screen = screen;
this.text = text;
IrisElementRow.TextButtonElement toggleRef = null;
if (hasBackButton) {
this.backButton = new IrisElementRow().add(new IrisElementRow.TextButtonElement(BACK_BUTTON_TEXT, this::backButtonClicked),
Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(BACK_BUTTON_TEXT) + 8));
// Sub-screen: navigating here while search is active → force-disable so the search box disappears
if (optionList.isSearchModeActive()) {
optionList.disableSearchMode();
}
String backText = backButtonText();
int w = Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(backText) + 8);
this.backButton = new IrisElementRow().add(new IrisElementRow.TextButtonElement(backText, this::backButtonClicked), w);
this.leftButtonWidth = w;
} else {
this.backButton = null;
// Main screen: left button is the search/clear toggle
String buttonText = optionList.isSearchModeActive() ? clearButtonText() : searchButtonText();
IrisElementRow.TextButtonElement searchBtn = new IrisElementRow.TextButtonElement(buttonText, this::searchButtonClicked);
int w = Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(buttonText) + 8);
this.backButton = new IrisElementRow().add(searchBtn, w);
toggleRef = searchBtn;
this.leftButtonWidth = w;
optionList.setReservedLeftWidth(w + SEARCH_BOX_GAP);
}
this.resetButton = new IrisElementRow.TextButtonElement(RESET_BUTTON_TEXT_INACTIVE, this::resetButtonClicked);
this.searchToggleButton = toggleRef;
this.resetButton = new IrisElementRow.TextButtonElement(resetButtonInactive(), this::resetButtonClicked);
this.importButton = new IrisElementRow.IconButtonElement(GuiUtil.Icon.IMPORT, GuiUtil.Icon.IMPORT_COLORED, this::importSettingsButtonClicked);
this.exportButton = new IrisElementRow.IconButtonElement(GuiUtil.Icon.EXPORT, GuiUtil.Icon.EXPORT_COLORED, this::exportSettingsButtonClicked);
this.utilityButtons.add(this.importButton, 15).add(this.exportButton, 15)
.add(this.resetButton, Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth((RESET_BUTTON_TEXT_INACTIVE) + 8)));
this.screen = screen;
this.text = text;
.add(this.resetButton, Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(resetButtonInactive()) + 8));
}
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int slotWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
Gui.drawRect(x - 3, (y + slotHeight) - 3, x + slotWidth, (y + slotHeight) - 2, 0x66BEBEBE);
// Gui.drawRect leaves GL color set to the rect's color; reset to white so button textures aren't tinted.
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
final int tickDelta = 0;
final FontRenderer font = Minecraft.getMinecraft().fontRenderer;
GuiUtil.drawScrollingText(font, text, x + (int) (slotWidth * 0.5), x + 5,
((x + slotWidth) - 10) - this.utilityButtons.getWidth(), y + 5, y + 15, 0xFFFFFF);
GuiUtil.bindIrisWidgetsTexture();
// Draw back button if present
@@ -75,26 +102,42 @@ public class HeaderEntry extends BaseEntry {
backButton.drawScreen(x, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, isMouseOver);
}
if (optionList.isSearchModeActive() && searchToggleButton != null) {
if (searchToggleButton.isHovered()) {
ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel(font, clearTooltip(), mouseX, mouseY - 16));
}
return;
}
GuiUtil.drawScrollingText(font, text, x + (int) (slotWidth * 0.5),
x + leftButtonWidth + 5,
((x + slotWidth) - 10) - this.utilityButtons.getWidth(),
y + 5, y + 15, 0xFFFFFF);
final boolean shiftDown = GuiScreen.isShiftKeyDown();
// Set the appearance of the reset button
this.resetButton.disabled = !shiftDown;
this.resetButton.text = shiftDown ? RESET_BUTTON_TEXT_ACTIVE : RESET_BUTTON_TEXT_INACTIVE;
this.resetButton.text = shiftDown ? resetButtonActive() : resetButtonInactive();
// Draw the utility buttons
this.utilityButtons.renderRightAligned((x + slotWidth) - 3, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, isMouseOver);
// Draw the reset button's tooltip
if (this.resetButton.isHovered()) {
final String tooltip = shiftDown ? RESET_TOOLTIP : RESET_HOLD_SHIFT_TOOLTIP;
final String tooltip = shiftDown ? resetTooltip() : resetHoldShiftTooltip();
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, tooltip);
}
// Draw the import/export button tooltips
if (this.importButton.isHovered()) {
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, IMPORT_TOOLTIP);
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, importTooltip());
}
if (this.exportButton.isHovered()) {
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, EXPORT_TOOLTIP);
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, exportTooltip());
}
// Search-button tooltip
if (searchToggleButton != null && searchToggleButton.isHovered()) {
ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel(font, searchTooltip(), mouseX, mouseY - 16));
}
}
@@ -104,6 +147,9 @@ public class HeaderEntry extends BaseEntry {
@Override
public boolean mouseClicked(int mouseX, int mouseY, int button) {
if (optionList.isSearchModeActive()) {
return backButton != null && backButton.mouseClicked(mouseX, mouseY, button);
}
final boolean backButtonResult = backButton != null && backButton.mouseClicked(mouseX, mouseY, button);
final boolean utilButtonResult = utilityButtons.mouseClicked(mouseX, mouseY, button);
@@ -122,6 +168,16 @@ public class HeaderEntry extends BaseEntry {
return true;
}
private boolean searchButtonClicked(IrisElementRow.TextButtonElement button) {
GuiUtil.playButtonClickSound();
if (optionList.isSearchModeActive()) {
optionList.disableSearchModeAndRebuild();
} else {
optionList.enableSearchModeAndRebuild();
}
return true;
}
private boolean resetButtonClicked(IrisElementRow.TextButtonElement button) {
if (GuiScreen.isShiftKeyDown()) {
Iris.resetShaderPackOptionsOnNextReload();
@@ -10,9 +10,10 @@ import net.minecraft.client.resources.I18n;
import java.util.function.Function;
public class TopButtonRowEntry extends BaseEntry {
private static final String NONE_PRESENT_LABEL = I18n.format("options.iris.shaders.nonePresent"); //.withStyle(ChatFormatting.GRAY);
private static final String SHADERS_DISABLED_LABEL = I18n.format("options.iris.shaders.disabled");
private static final String SHADERS_ENABLED_LABEL = I18n.format("options.iris.shaders.enabled");
private static String nonePresentLabel() { return I18n.format("options.iris.shaders.nonePresent"); }
private static String shadersDisabledLabel() { return I18n.format("options.iris.shaders.disabled"); }
private static String shadersEnabledLabel() { return I18n.format("options.iris.shaders.enabled"); }
private final ShaderPackSelectionList shaderPackSelectionList;
private final IrisElementRow buttons = new IrisElementRow();
@@ -54,7 +55,7 @@ public class TopButtonRowEntry extends BaseEntry {
}
private String getEnableDisableLabel() {
return this.allowEnableShadersButton ? this.shadersEnabled ? SHADERS_ENABLED_LABEL : SHADERS_DISABLED_LABEL : NONE_PRESENT_LABEL;
return this.allowEnableShadersButton ? this.shadersEnabled ? shadersEnabledLabel() : shadersDisabledLabel() : nonePresentLabel();
}
public boolean mouseClicked(double mouseX, double mouseY, int button) {
@@ -4,16 +4,19 @@ import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuElement;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuOptionElement;
import net.coderbot.iris.shaderpack.option.menu.ShaderSearchEngine;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public abstract class BaseOptionElementWidget<T extends OptionMenuElement> extends CommentedElementWidget<T> {
protected static final String SET_TO_DEFAULT = I18n.format("options.iris.setToDefault");
protected static String setToDefault() { return I18n.format("options.iris.setToDefault"); }
protected static final String DIVIDER = ": ";
protected String unmodifiedLabel;
@@ -101,7 +104,7 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
protected final void tryRenderTooltip(int mouseX, int mouseY, boolean hovered) {
if (GuiScreen.isShiftKeyDown()) {
renderTooltip(SET_TO_DEFAULT, mouseX, mouseY, hovered);
renderTooltip(setToDefault(), mouseX, mouseY, hovered);
} else if (this.isLabelTrimmed && !this.screen.isDisplayingComment()) {
renderTooltip(this.unmodifiedLabel, mouseX, mouseY, hovered);
}
@@ -145,13 +148,39 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
@Override
public Optional<String> getCommentBody() {
final String key = getCommentKey();
if (key == null) {
return Optional.empty();
Optional<String> base = Optional.ofNullable(getCommentKey()).map(I18n::format);
if (this.screen == null || !this.screen.isOptionMenuSearchActive()) {
return base;
}
final String translated = GuiUtil.translateLenient(key);
// Don't show comment boxes without a translation, otherwise you get something like option.{commentname}.comment
return translated.equals(key) ? Optional.empty() : Optional.of(translated);
String pathLabel = buildTranslatedPath();
if (pathLabel == null) {
return base;
}
String breadcrumb = EnumChatFormatting.BOLD.toString() + EnumChatFormatting.ITALIC + pathLabel + EnumChatFormatting.RESET;
if (base.isPresent()) {
return Optional.of(breadcrumb + ". " + base.get());
}
return Optional.of(breadcrumb);
}
@Nullable
private String buildTranslatedPath() {
if (!(this.element instanceof OptionMenuOptionElement)) return null;
OptionMenuOptionElement optEl = (OptionMenuOptionElement) this.element;
String rawPath = optEl.container.getOptionPath(optEl.optionId);
String[] segments = rawPath.split("/");
StringBuilder display = new StringBuilder();
for (String segment : segments) {
if ("root".equals(segment)) continue;
String translated = ShaderSearchEngine.getDisplaySettingsName(segment).replaceAll("\\s+>", "");
String label = translated.isEmpty() ? segment : translated;
if (display.length() > 0) display.append(" > ");
display.append(label);
}
return display.length() > 0 ? display.toString() : null;
}
@Override
@@ -9,10 +9,9 @@ import net.coderbot.iris.shaderpack.option.menu.OptionMenuBooleanOptionElement;
import net.minecraft.client.resources.I18n;
public class BooleanElementWidget extends BaseOptionElementWidget<OptionMenuBooleanOptionElement> {
private static final String TEXT_TRUE = I18n.format("label.iris.true");
private static final String TEXT_FALSE = I18n.format("label.iris.false");
private static final String TEXT_TRUE_DEFAULT = I18n.format("label.iris.true");
private static final String TEXT_FALSE_DEFAULT = I18n.format("label.iris.false");
private static String textTrue() { return I18n.format("label.iris.true"); }
private static String textFalse() { return I18n.format("label.iris.false"); }
private final BooleanOption option;
@@ -63,11 +62,7 @@ public class BooleanElementWidget extends BaseOptionElementWidget<OptionMenuBool
// Compatibility Mode to "On" when not needed. Now we use white for
// default to avoid giving a positive or negative connotation to a
// default value.
if (this.value == this.defaultValue) {
return this.value ? TEXT_TRUE_DEFAULT : TEXT_FALSE_DEFAULT;
}
return this.value ? TEXT_TRUE : TEXT_FALSE;
return this.value ? textTrue() : textFalse();
}
@Override
@@ -16,8 +16,9 @@ import net.minecraft.client.resources.I18n;
import java.util.Optional;
public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProfileElement> {
private static final String PROFILE_LABEL = I18n.format("options.iris.profile");
private static final String PROFILE_CUSTOM = I18n.format("options.iris.profile.custom");
private static String profileLabel() { return I18n.format("options.iris.profile"); }
private static String profileCustom() { return I18n.format("options.iris.profile.custom"); }
private Profile next;
private Profile previous;
@@ -30,7 +31,7 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
@Override
public void init(ShaderPackScreen screen, NavigationController navigation) {
super.init(screen, navigation);
this.setLabel(PROFILE_LABEL);
this.setLabel(profileLabel());
boolean secondProfileSet = ProfileElementTracker.isSecondProfileSet(this.element);
final ProfileSet profiles = this.element.profiles;
@@ -44,12 +45,12 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
final Optional<String> profileName = result.current.map(p -> p.name);
String translationKey = "profile" + (secondProfileSet ? "2." : ".") + profileName.orElse("custom");
this.profileLabel = profileName.map(name -> GuiUtil.translateOrDefault(name, translationKey)).orElse(PROFILE_CUSTOM);
this.profileLabel = profileName.map(name -> GuiUtil.translateOrDefault(name, translationKey)).orElse(profileCustom());
}
@Override
public void drawScreen(int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) {
this.updateRenderParams(width, width - (Minecraft.getMinecraft().fontRenderer.getStringWidth(PROFILE_LABEL) + 16));
this.updateRenderParams(width, width - (Minecraft.getMinecraft().fontRenderer.getStringWidth(profileLabel()) + 16));
this.renderOptionWithValue(x, y, width, height, hovered);
}
@@ -61,12 +62,12 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
@Override
protected int getValueColor() {
return PROFILE_CUSTOM.equals(this.profileLabel) ? 0xFFFF55 : 0xFFFFFF;
return profileCustom().equals(this.profileLabel) ? 0xFFFF55 : 0xFFFFFF;
}
@Override
public Optional<String> getCommentTitle() {
return Optional.of(PROFILE_LABEL);
return Optional.of(profileLabel());
}
@Override
@@ -28,7 +28,7 @@ public class SliderElementWidget extends StringElementWidget {
}
if (GuiScreen.isShiftKeyDown()) {
renderTooltip(SET_TO_DEFAULT, mouseX, mouseY, hovered);
renderTooltip(setToDefault(), mouseX, mouseY, hovered);
} else if (!this.screen.isDisplayingComment()) {
renderTooltip(this.unmodifiedLabel, mouseX, mouseY, hovered);
}
@@ -53,8 +53,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
*/
public static final Set<Runnable> TOP_LAYER_RENDER_QUEUE = new HashSet<>();
private static final String SELECT_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title");
private static final String CONFIGURE_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title");
private static String selectTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title"); }
private static String configureTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title"); }
private static final int COMMENT_PANEL_WIDTH = 314;
private final GuiScreen parent;
@@ -104,6 +104,11 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
refreshForChangedPack();
}
/** Returns true when the option menu is open and the user has an active search query. */
public boolean isOptionMenuSearchActive() {
return this.optionMenuOpen && this.shaderOptionList != null && this.shaderOptionList.isSearchModeActive();
}
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
@@ -124,6 +129,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
if (!this.guiHidden) {
if (optionMenuOpen && this.shaderOptionList != null) {
this.shaderOptionList.drawScreen(mouseX, mouseY, delta);
this.shaderOptionList.drawSearchBox();
} else {
this.shaderPackList.drawScreen(mouseX, mouseY, delta);
}
@@ -144,9 +150,9 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
drawCenteredString(this.fontRendererObj, notificationDialog, (int) (this.width * 0.5), 21, 0xFFFFFF);
} else {
if (optionMenuOpen) {
drawCenteredString(this.fontRendererObj, CONFIGURE_TITLE, (int) (this.width * 0.5), 21, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, configureTitle(), (int) (this.width * 0.5), 21, 0xFFFFFF);
} else {
drawCenteredString(this.fontRendererObj, SELECT_TITLE, (int) (this.width * 0.5), 21, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, selectTitle(), (int) (this.width * 0.5), 21, 0xFFFFFF);
}
}
@@ -207,6 +213,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
if (Iris.getCurrentPack().isPresent() && this.navigation != null) {
final ShaderPack currentPack = Iris.getCurrentPack().get();
if (currentPack.getMenuContainer() != null) {
currentPack.getMenuContainer().setSearchQuery(null);
}
this.shaderOptionList = new ShaderPackOptionList(this, this.navigation, currentPack, this.mc, this.width, this.height, 32, this.height - 58, 0, this.width);
this.navigation.setActiveOptionList(this.shaderOptionList);
@@ -364,6 +374,31 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
@Override
protected void keyTyped(char typedChar, int keyCode) {
// Route all input to the search box while search mode is active
if (optionMenuOpen && this.shaderOptionList != null && this.shaderOptionList.isSearchModeActive()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.shaderOptionList.disableSearchModeAndRebuild();
return;
}
// Ctrl+F while search is open: toggle it closed
if (keyCode == Keyboard.KEY_F && GuiScreen.isCtrlKeyDown()) {
GuiUtil.playButtonClickSound();
this.shaderOptionList.disableSearchModeAndRebuild();
return;
}
this.shaderOptionList.handleSearchKeyTyped(typedChar, keyCode);
return;
}
// Ctrl+F: open search on the main option screen
if (optionMenuOpen && this.shaderOptionList != null
&& keyCode == Keyboard.KEY_F && GuiScreen.isCtrlKeyDown()) {
if (!this.shaderOptionList.isOnSubScreen()) {
GuiUtil.playButtonClickSound();
this.shaderOptionList.enableSearchModeAndRebuild();
return;
}
}
if (GuiScreen.isCtrlKeyDown() && keyCode == Keyboard.KEY_D) {
this.mc.displayGuiScreen(new GuiYesNo((result, id) -> {
Iris.setDebug(result);
@@ -496,6 +531,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
}
public void onClose() {
if (this.shaderOptionList != null) {
this.shaderOptionList.disableSearchModeAndRebuild();
}
if (!dropChanges) {
applyChanges();
} else {
@@ -42,6 +42,7 @@ import net.coderbot.iris.gl.texture.DepthBufferFormat;
import net.coderbot.iris.gl.texture.TextureType;
import net.coderbot.iris.helpers.Tri;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.transform.GlintScrollInjector;
import net.coderbot.iris.pipeline.transform.PatchShaderType;
import net.coderbot.iris.pipeline.transform.TransformPatcher;
import net.coderbot.iris.postprocess.BufferFlipper;
@@ -838,13 +839,16 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
if (transformed == null) {
// Fallback to synchronous transform if not pre-computed
String vertex = source.getVertexSource().orElseThrow(NullPointerException::new);
final boolean scrollGlint = GlintScrollInjector.shouldInject(id, source);
transformed = TransformPatcher.patchAttributes(
source.getVertexSource().orElseThrow(NullPointerException::new),
vertex,
source.getGeometrySource().orElse(null),
source.getTessControlSource().orElse(null),
source.getTessEvalSource().orElse(null),
source.getFragmentSource().orElseThrow(NullPointerException::new),
availability);
availability,
scrollGlint);
}
String vertex = transformed.get(PatchShaderType.VERTEX);
@@ -1784,9 +1788,12 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
ProgramSource source = resolver.resolveNullable(id);
if (source != null && !processedSourceNames.contains(source.getName())) {
processedSourceNames.add(source.getName());
final ProgramSource src = source;
final String vertexSource = src.getVertexSource().orElse(null);
final boolean scrollGlint = GlintScrollInjector.shouldInject(id, src);
for (InputAvailability avail : INPUT_AVAILABILITIES) {
Pair<String, InputAvailability> key = Pair.of(source.getName(), avail);
futures.put(key, Iris.ShaderTransformExecutor.submitTracked(() -> TransformPatcher.patchAttributes(source.getVertexSource().orElse(null), source.getGeometrySource().orElse(null), source.getTessControlSource().orElse(null), source.getTessEvalSource().orElse(null), source.getFragmentSource().orElse(null), avail)));
Pair<String, InputAvailability> key = Pair.of(src.getName(), avail);
futures.put(key, Iris.ShaderTransformExecutor.submitTracked(() -> TransformPatcher.patchAttributes(vertexSource, src.getGeometrySource().orElse(null), src.getTessControlSource().orElse(null), src.getTessEvalSource().orElse(null), src.getFragmentSource().orElse(null), avail, scrollGlint)));
}
}
}
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.compat.toremove.MatrixStack;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.rendering.PlayerReflectionCapture;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.coderbot.iris.Iris;
@@ -468,11 +469,13 @@ public class ShadowRenderer {
profiler.endStartSection("build geometry");
setupEntityShadowState(modelView, cameraX, cameraY, cameraZ);
PlayerReflectionCapture.begin(player);
try {
for (Entity entity : renderedEntitiesList) {
RenderManager.instance.renderEntitySimple(entity, tickDelta);
}
} finally {
PlayerReflectionCapture.end();
teardownEntityShadowState();
}
@@ -516,7 +519,7 @@ public class ShadowRenderer {
private void renderPlayerEntity(EntityRenderer levelRenderer, Frustrum frustum, Object bufferSource, MatrixStack modelView, double cameraX, double cameraY, double cameraZ, float tickDelta) {
profiler.startSection("cull");
Entity player = Minecraft.getMinecraft().thePlayer;
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
// Skip if spectating or outside frustum
if (GameModeUtil.isSpectator()) {
@@ -547,7 +550,12 @@ public class ShadowRenderer {
shadowEntities++;
}
RenderManager.instance.renderEntitySimple(player, tickDelta);
PlayerReflectionCapture.begin(player);
try {
RenderManager.instance.renderEntitySimple(player, tickDelta);
} finally {
PlayerReflectionCapture.end();
}
shadowEntities++;
} finally {
teardownEntityShadowState();
@@ -684,7 +692,7 @@ public class ShadowRenderer {
// Mark the shadow graph as needing update before terrain setup
// Modern Celeritas does this to ensure the shadow render lists get populated
com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer.getInstance()
.getRenderSectionManager().markGraphDirty();
.getRenderSectionManager().markShadowGraphDirty();
// Execute the vanilla terrain setup / culling routines using our shadow frustum.
mc.renderGlobal.clipRenderersByFrustum(terrainFrustumHolder.getFrustum(), playerCamera.getPartialTicks());
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.HashMap;
import java.util.Map;
@@ -13,6 +14,10 @@ import java.util.Map;
*/
class AttributeTransformer {
public static void transform(Transformer transformer, AttributeParameters parameters, int version) {
if (parameters.scrollGlint && parameters.type == ShaderType.VERTEX) {
transformer.replaceExpression("gl_MultiTexCoord0", "(gl_TextureMatrix[0] * gl_MultiTexCoord0)", GLSLParser::postfix_expression);
}
// Always core profile — minimum GLSL version is 330 (see ShaderTransformer.getStageMinimumVersion)
CommonTransformer.transform(transformer, parameters, true, version);
@@ -46,6 +46,10 @@ class CeleritasTransformer {
vertexReplacements.put("gl_MultiTexCoord2", "iris_LightTexCoord");
vertexReplacements.forEach(transformer::replaceExpression);
if (transformer.hasVariable("chunkOffset")) {
transformer.removeVariable("chunkOffset");
}
final Map<String, String> vertexRenames = new HashMap<>();
vertexRenames.put("gl_Color", "_vert_color");
vertexRenames.put("ftransform", "iris_ftransform");
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.HashSet;
import java.util.Set;
@@ -29,6 +30,16 @@ public class CommonTransformer {
root.rename("gl_Color", "iris_FrontColor");
}
if (root.hasVariable("gl_TexCoord")) {
if (parameters.type == ShaderType.VERTEX) {
root.rename("gl_TexCoord", "irs_texCoords");
root.injectVariable("out vec4 irs_texCoords[3];");
} else if (parameters.type == ShaderType.FRAGMENT) {
root.rename("gl_TexCoord", "irs_texCoords");
root.injectVariable("in vec4 irs_texCoords[3];");
}
}
if (parameters.type == ShaderType.FRAGMENT) {
if (root.containsCall("gl_FragColor")) {
root.replaceExpression("gl_FragColor", "gl_FragData[0]");
@@ -91,6 +102,27 @@ public class CommonTransformer {
root.renameAndWrapShadow("shadow2D", "texture");
root.renameAndWrapShadow("shadow2DLod", "textureLod");
if (root.containsCall("textureLodOffset")) {
root.injectFunction("vec4 iris_textureLodOffset(sampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("float iris_textureLodOffset(sampler2DShadow iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, vec3(iris_tlo_c.xy + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_c.z), iris_tlo_l); }");
root.injectFunction("vec4 iris_textureLodOffset(sampler3D iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec3 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec3(iris_tlo_o) / vec3(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("vec4 iris_textureLodOffset(sampler2DArray iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, vec3(iris_tlo_c.xy + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l)).xy), iris_tlo_c.z), iris_tlo_l); }");
root.injectFunction("ivec4 iris_textureLodOffset(isampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("uvec4 iris_textureLodOffset(usampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.renameFunctionCall("textureLodOffset", "iris_textureLodOffset");
}
if (parameters.type == ShaderType.VERTEX) {
root.mutateTree(tree -> {
if (tree.children != null) {
tree.children.removeIf(child ->
child instanceof GLSLParser.External_declarationContext
&& ((GLSLParser.External_declarationContext) child).declaration() != null
&& child.getText().contains("gl_PerVertex"));
}
});
}
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
import org.antlr.v4.runtime.tree.ParseTree;
import org.taumc.glsl.ShaderPrinter;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
@@ -109,7 +110,10 @@ public class CompatibilityTransformer {
continue;
}
prevTransformer.makeOutDeclaration(inDec.get(in), in);
String outDeclaration = ShaderPrinter.getFormattedShader(inDec.get(in).fully_specified_type())
+ " " + in + ";";
prevTransformer.variable = null;
prevTransformer.injectVariable(outDeclaration.replaceFirst("in", "out"));
if (!prevTransformer.hasAssigment(in)) {
prevTransformer.initialize(inDec.get(in), in);
@@ -126,6 +130,23 @@ public class CompatibilityTransformer {
if (!prevTransformer.hasAssigment(in)) {
prevTransformer.initialize(inDec.get(in), in);
}
} else {
int outComp = vecComponents(outType.getText());
int inComp = vecComponents(inType.getText());
if (outComp < 0 || inComp < 0) {
continue;
}
String temp = "iris_template_" + in;
prevTransformer.rename(in, temp);
String newOut = ShaderPrinter.getFormattedShader(inDec.get(in).fully_specified_type())
+ " " + in + ";";
prevTransformer.variable = null;
prevTransformer.injectVariable(newOut.replaceFirst("in", "out"));
String cast = outComp < inComp
? in + " = " + inType.getText() + "(" + temp + ", vec4(0));"
: in + " = " + inType.getText() + "(" + temp + ");";
prevTransformer.appendMain(cast);
}
}
}
@@ -133,4 +154,13 @@ public class CompatibilityTransformer {
prevType = type;
}
}
private static int vecComponents(String type) {
return switch (type) {
case "vec2" -> 2;
case "vec3" -> 3;
case "vec4" -> 4;
default -> -1;
};
}
}
@@ -18,7 +18,7 @@ class CompositeDepthTransformer {
final int type = transformer.findType("centerDepthSmooth");
if (type != 0) {
transformer.injectVariable("uniform sampler2D iris_centerDepthSmooth;");
transformer.replaceExpression("centerDepthSmooth", "texture2D(iris_centerDepthSmooth, vec2(0.5)).r");
transformer.replaceExpression("centerDepthSmooth", "texture(iris_centerDepthSmooth, vec2(0.5)).r");
}
}
}
@@ -51,6 +51,13 @@ class CoreTransformHelper {
transformer.rename("gl_Vertex", "iris_Vertex");
transformer.rename("gl_MultiTexCoord0", "iris_MultiTexCoord0");
for (int i = 1; i <= 7; i++) {
transformer.replaceExpression("gl_MultiTexCoord" + i, "vec4(0.0, 0.0, 0.0, 1.0)");
}
transformer.replaceExpression("gl_Color", "vec4(1.0, 1.0, 1.0, 1.0)");
transformer.replaceExpression("gl_Normal", "vec3(0.0, 0.0, 1.0)");
transformer.renameFunctionCall("ftransform", "iris_ftransform");
transformer.injectFunction("vec4 iris_ftransform() { return (iris_ProjectionMatrix * iris_ModelViewMatrix) * iris_Vertex; }");
@@ -0,0 +1,24 @@
package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.shaderpack.ProgramSource;
import net.coderbot.iris.shaderpack.loading.ProgramId;
/**
* For some reason not all shaders read from gl_MultiTexCoord0 through the texture matrix to scroll the enchant glint?
* Maybe a modern thing but a few shaders just look wrong without it. Technically not in spec, but it looks better.
*/
public final class GlintScrollInjector {
private GlintScrollInjector() {}
public static boolean shouldInject(ProgramId id, ProgramSource source) {
if (id != ProgramId.ArmorGlint || source == null) return false;
if (!ProgramId.ArmorGlint.getSourceName().equals(source.getName())) return false;
final String vertex = source.getVertexSource().orElse(null);
if (vertex == null) return false;
// Already scrolls through the texture matrix itself
if (vertex.contains("gl_TextureMatrix[0]")) return false;
return vertex.contains("gl_MultiTexCoord0");
}
}
@@ -104,8 +104,12 @@ public class TransformPatcher {
return result;
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint) {
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, inputs, scrollGlint));
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, inputs));
return patchAttributes(vertex, geometry, tessControl, tessEval, fragment, inputs, false);
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String fragment, InputAvailability inputs) {
@@ -7,12 +7,14 @@ import net.coderbot.iris.shaderpack.texture.TextureStage;
public class AttributeParameters extends Parameters {
public final boolean hasGeometry;
public final InputAvailability inputs;
public final boolean scrollGlint;
// WARNING: adding new fields requires updating hashCode and equals methods!
public AttributeParameters(Patch patch, boolean hasGeometry, InputAvailability inputs) {
public AttributeParameters(Patch patch, boolean hasGeometry, InputAvailability inputs, boolean scrollGlint) {
super(patch, null);
this.hasGeometry = hasGeometry;
this.inputs = inputs;
this.scrollGlint = scrollGlint;
}
@Override
@@ -25,6 +27,7 @@ public class AttributeParameters extends Parameters {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (hasGeometry ? 1231 : 1237);
result = prime * result + (scrollGlint ? 1231 : 1237);
result = prime * result + ((inputs == null) ? 0 : inputs.hashCode());
return result;
}
@@ -40,6 +43,8 @@ public class AttributeParameters extends Parameters {
AttributeParameters other = (AttributeParameters) obj;
if (hasGeometry != other.hasGeometry)
return false;
if (scrollGlint != other.scrollGlint)
return false;
if (inputs == null) {
if (other.inputs != null)
return false;
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more