mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc1353b73a | ||
|
|
d1e6dc8c94 | ||
|
|
ecbf8fea2f | ||
|
|
610209605f | ||
|
|
112aae3735 | ||
|
|
5844b1b5c1 | ||
|
|
05f01c17e6 | ||
|
|
f07d96316a | ||
|
|
5b704d1bc2 | ||
|
|
26135124f4 | ||
|
|
3abc89b7b2 | ||
|
|
03d908d658 |
No files matched your search
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
@@ -180,4 +180,6 @@ dependencies {
|
||||
}
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,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;
|
||||
@@ -120,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();
|
||||
|
||||
@@ -2248,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) {
|
||||
@@ -2332,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) {
|
||||
@@ -2432,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));
|
||||
}
|
||||
@@ -2441,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) {
|
||||
@@ -2494,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) {
|
||||
@@ -2542,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) {
|
||||
@@ -5792,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+53
-33
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
+28
@@ -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");
|
||||
}
|
||||
}
|
||||
+154
-1
@@ -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++) {
|
||||
|
||||
@@ -61,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()
|
||||
|
||||
@@ -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,);\
|
||||
|
||||
@@ -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,6 +30,7 @@ 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;
|
||||
|
||||
@@ -50,6 +52,7 @@ 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");
|
||||
@@ -71,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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -83,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)
|
||||
@@ -231,8 +238,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"
|
||||
@@ -299,6 +305,8 @@ public enum Mixins implements IMixins {
|
||||
, "shaders.MixinTileEntityRendererDispatcher"
|
||||
, "shaders.MixinGlProgram"
|
||||
, "shaders.MixinTextureManager_ReloadCount"
|
||||
, "shaders.AccessorModelBox"
|
||||
, "shaders.MixinModelBiped"
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
+4
@@ -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;
|
||||
}
|
||||
}
|
||||
+17
-104
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
-1
@@ -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());
|
||||
|
||||
+28
-16
@@ -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();
|
||||
}
|
||||
}
|
||||
+30
-20
@@ -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;
|
||||
|
||||
+39
-29
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -44,7 +45,7 @@ final class BlockOrientation extends RenderBlockState {
|
||||
private int textureFace;
|
||||
private int textureFaceOrig;
|
||||
private int rotateUV;
|
||||
private boolean flipBottom;
|
||||
private boolean flipped;
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
@@ -57,7 +58,7 @@ final class BlockOrientation extends RenderBlockState {
|
||||
offsetsComputed = false;
|
||||
haveOffsets = false;
|
||||
dx = dy = dz = 0;
|
||||
flipBottom = !fixedBottomFaceUV;
|
||||
flipped = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +103,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,8 +150,8 @@ final class BlockOrientation extends RenderBlockState {
|
||||
}
|
||||
|
||||
|
||||
public void setFlipBottom(){
|
||||
flipBottom = true;
|
||||
public void flipFace() {
|
||||
flipped = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,7 +179,7 @@ final class BlockOrientation extends RenderBlockState {
|
||||
copy.textureFace = textureFace;
|
||||
copy.textureFaceOrig = textureFaceOrig;
|
||||
copy.rotateUV = rotateUV;
|
||||
copy.flipBottom = flipBottom;
|
||||
copy.flipped = flipped;
|
||||
|
||||
copy.blockAccess = blockAccess;
|
||||
copy.block = block;
|
||||
@@ -208,7 +212,7 @@ final class BlockOrientation extends RenderBlockState {
|
||||
rotateUV = 0;
|
||||
textureFace = blockFaceToTextureFace(blockFace);
|
||||
metadataBits = (1 << metadata) | (1 << altMetadata);
|
||||
flipBottom = !fixedBottomFaceUV;
|
||||
if(!fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal()) flipped = true;
|
||||
}
|
||||
|
||||
void setBlockMetadata(Block block, int metadata, int face) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -10,9 +10,12 @@ import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class OptionMenuContainer {
|
||||
public final OptionMenuElementScreen mainScreen;
|
||||
@@ -24,6 +27,8 @@ public class OptionMenuContainer {
|
||||
private final Map<List<OptionMenuElement>, Integer> unusedOptionDumpQueue = new HashMap<>(); // Used by screens with "*" element
|
||||
@Getter private final ProfileSet profiles;
|
||||
@Getter private final ProfileSet profiles2;
|
||||
private final List<OptionMenuElement> originalMainElements = new ArrayList<>(); // Saved snapshot of mainScreen.elements before any search filter is applied.
|
||||
private final Map<String, String> cachedOptionPaths = new HashMap<>(); // Full "root/SCREEN1/SCREEN2" path for every option ID, built once after construction
|
||||
|
||||
public OptionMenuContainer(ShaderProperties shaderProperties, ShaderPackOptions shaderPackOptions, ProfileSet profiles) {
|
||||
this.profiles = profiles;
|
||||
@@ -69,6 +74,10 @@ public class OptionMenuContainer {
|
||||
|
||||
entry.getKey().addAll(entry.getValue(), elementsToInsert);
|
||||
}
|
||||
|
||||
// Save the original layout and build the path cache once the full tree is constructed
|
||||
this.originalMainElements.addAll(this.mainScreen.elements);
|
||||
generateAllPaths();
|
||||
}
|
||||
|
||||
// Screens will call this when they contain a "*" element, so that the list of
|
||||
@@ -85,4 +94,81 @@ public class OptionMenuContainer {
|
||||
|
||||
unusedOptions.remove(optionId);
|
||||
}
|
||||
|
||||
private void generateAllPaths() {
|
||||
cachedOptionPaths.clear();
|
||||
traverseScreen(this.mainScreen, "root", new HashSet<String>());
|
||||
}
|
||||
|
||||
private void traverseScreen(OptionMenuElementScreen screen, String currentPath, Set<String> visited) {
|
||||
if (screen == null) return;
|
||||
for (OptionMenuElement element : screen.elements) {
|
||||
if (element instanceof OptionMenuOptionElement optEl) {
|
||||
if (optEl.optionId != null) {
|
||||
cachedOptionPaths.putIfAbsent(optEl.optionId, currentPath);
|
||||
}
|
||||
} else if (element instanceof OptionMenuLinkElement link) {
|
||||
if (link.targetScreenId != null) {
|
||||
String targetId = link.targetScreenId;
|
||||
if (visited.add(targetId)) {
|
||||
OptionMenuElementScreen next = this.subScreens.get(targetId);
|
||||
if (next != null) {
|
||||
traverseScreen(next, currentPath + "/" + targetId, visited);
|
||||
}
|
||||
visited.remove(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getOptionPath(String optionId) {
|
||||
if (optionId == null) return "root";
|
||||
return cachedOptionPaths.getOrDefault(optionId, "root");
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters and re-orders the main-screen options to match {@code query}, or restores the
|
||||
* original layout when {@code query} is null or blank.
|
||||
*/
|
||||
public void setSearchQuery(String query) {
|
||||
if (query == null || query.trim().isEmpty()) {
|
||||
restoreOriginalLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
String normalizedQuery = query.toLowerCase(Locale.ROOT).trim();
|
||||
|
||||
List<OptionMenuOptionElement> flatOptions = ShaderSearchEngine.getAllOptionsFlattened(usedOptionElements);
|
||||
List<ShaderSearchEngine.ScoredOptionElement> scored = new ArrayList<>();
|
||||
|
||||
for (OptionMenuOptionElement el : flatOptions) {
|
||||
int score = ShaderSearchEngine.computeMatchTier(el.optionId, normalizedQuery);
|
||||
if (score > 0) {
|
||||
scored.add(new ShaderSearchEngine.ScoredOptionElement(
|
||||
el,
|
||||
ShaderSearchEngine.getReadableTranslatedName(el.optionId),
|
||||
ShaderSearchEngine.getReadableDefaultName(el.optionId),
|
||||
getOptionPath(el.optionId),
|
||||
score,
|
||||
normalizedQuery
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(scored);
|
||||
applyFilteredLayout(scored);
|
||||
}
|
||||
|
||||
private void applyFilteredLayout(List<ShaderSearchEngine.ScoredOptionElement> sortedElements) {
|
||||
this.mainScreen.elements.clear();
|
||||
for (ShaderSearchEngine.ScoredOptionElement scored : sortedElements) {
|
||||
this.mainScreen.elements.add(scored.getElement());
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreOriginalLayout() {
|
||||
this.mainScreen.elements.clear();
|
||||
this.mainScreen.elements.addAll(this.originalMainElements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package net.coderbot.iris.shaderpack.option.menu;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.shaderpack.ShaderPack;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility class for searching and scoring shader option elements based on a query string.
|
||||
* <br>By SpacEagle17
|
||||
*/
|
||||
public class ShaderSearchEngine {
|
||||
private static final String WHOLE_WORD_REGEX = "(?<=^|[^a-zA-Z0-9])%s(?=$|[^a-zA-Z0-9])";
|
||||
private static final String STARTS_WITH_REGEX = "(?<=^|[^a-zA-Z0-9])%s";
|
||||
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§.");
|
||||
|
||||
/**
|
||||
* Bitmask of bits from readable-name (translated + default) comparisons.
|
||||
* Used by the comparator to separate name-match quality from rawId/comment noise.
|
||||
*/
|
||||
private static final int READABLE_NAME_BITS =
|
||||
(1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 3) | (1 << 2);
|
||||
|
||||
|
||||
private static String strippedLanguageString(String key) {
|
||||
String translated = I18n.format(key);
|
||||
if (translated.equals(key)) return "";
|
||||
return COLOR_CODE_PATTERN.matcher(translated).replaceAll("");
|
||||
}
|
||||
|
||||
/** Translated display name for a sub-screen ID (looks up {@code "screen.<screenId>"}). */
|
||||
public static String getDisplaySettingsName(String screenId) {
|
||||
return strippedLanguageString("screen." + screenId);
|
||||
}
|
||||
|
||||
/** Lowercase Minecraft-locale translation of an option's human-readable name. */
|
||||
public static String getReadableTranslatedName(String optionId) {
|
||||
return strippedLanguageString("option." + optionId).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowercase default (en_us from the shader pack's lang folder) translation.
|
||||
* Returns {@code ""} when the current pack has no en_us lang file or the key is missing.
|
||||
*/
|
||||
public static String getReadableDefaultName(String optionId) {
|
||||
try {
|
||||
Optional<ShaderPack> packOpt = Iris.getCurrentPack();
|
||||
if (packOpt.isEmpty()) return "";
|
||||
Map<String, String> translations = packOpt.get().getLanguageMap().getTranslations("en_us");
|
||||
if (translations == null) return "";
|
||||
String value = translations.get("option." + optionId);
|
||||
if (value == null) return "";
|
||||
return COLOR_CODE_PATTERN.matcher(value).replaceAll("").toLowerCase(Locale.ROOT);
|
||||
} catch (Throwable t) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isOnlyAscii(String s) {
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (s.charAt(i) > 127) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a 14-bit bitmask score for how well {@code optionId} matches {@code query}.
|
||||
* Returns 0 when there is no match at all.
|
||||
* <br> Read code to know what each bit means lmao
|
||||
*/
|
||||
public static int computeMatchTier(String optionId, String query) {
|
||||
try {
|
||||
if (optionId == null || query == null) return 0;
|
||||
String q = query.toLowerCase(Locale.ROOT).trim();
|
||||
if (q.isEmpty()) return 0;
|
||||
|
||||
String translatedName = getReadableTranslatedName(optionId);
|
||||
String defaultName = getReadableDefaultName(optionId);
|
||||
|
||||
// Single ASCII char: only match when a readable name begins with it
|
||||
if (q.length() == 1 && isOnlyAscii(q)) {
|
||||
return (!translatedName.isEmpty() && translatedName.startsWith(q)) ? 1 : 0;
|
||||
}
|
||||
|
||||
String escaped = Pattern.quote(q);
|
||||
Pattern wholeWord = Pattern.compile(String.format(WHOLE_WORD_REGEX, escaped));
|
||||
Pattern startsWith = Pattern.compile(String.format(STARTS_WITH_REGEX, escaped));
|
||||
|
||||
String rawId = optionId.toLowerCase(Locale.ROOT);
|
||||
String commentText = strippedLanguageString("option." + optionId + ".comment").toLowerCase(Locale.ROOT);
|
||||
|
||||
int score = 0;
|
||||
if (!translatedName.isEmpty() && translatedName.equals(q)) score |= (1 << 13);
|
||||
if (!defaultName.isEmpty() && defaultName.equals(q)) score |= (1 << 12);
|
||||
if (!translatedName.isEmpty() && wholeWord.matcher(translatedName).find()) score |= (1 << 11);
|
||||
if (!defaultName.isEmpty() && wholeWord.matcher(defaultName).find()) score |= (1 << 10);
|
||||
if (!translatedName.isEmpty() && startsWith.matcher(translatedName).find()) score |= (1 << 9);
|
||||
if (!defaultName.isEmpty() && startsWith.matcher(defaultName).find()) score |= (1 << 8);
|
||||
if (wholeWord.matcher(rawId).find()) score |= (1 << 7);
|
||||
if (!commentText.isEmpty() && wholeWord.matcher(commentText).find()) score |= (1 << 6);
|
||||
if (startsWith.matcher(rawId).find()) score |= (1 << 5);
|
||||
if (!commentText.isEmpty() && startsWith.matcher(commentText).find()) score |= (1 << 4);
|
||||
if (!translatedName.isEmpty() && translatedName.contains(q)) score |= (1 << 3);
|
||||
if (!defaultName.isEmpty() && defaultName.contains(q)) score |= (1 << 2);
|
||||
if (rawId.contains(q)) score |= (1 << 1);
|
||||
if (!commentText.isEmpty() && commentText.contains(q)) score |= 1;
|
||||
|
||||
return score;
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** De-duplicates and flattens the pool of registered option elements. */
|
||||
public static List<OptionMenuOptionElement> getAllOptionsFlattened(List<OptionMenuOptionElement> usedOptionElements) {
|
||||
List<OptionMenuOptionElement> flat = new ArrayList<>();
|
||||
List<String> seen = new ArrayList<>();
|
||||
for (OptionMenuOptionElement el : usedOptionElements) {
|
||||
if (el == null) continue;
|
||||
String id = el.optionId != null ? el.optionId : el.toString();
|
||||
if (!seen.contains(id)) {
|
||||
seen.add(id);
|
||||
flat.add(el);
|
||||
}
|
||||
}
|
||||
return flat;
|
||||
}
|
||||
|
||||
/**
|
||||
* A scored search result carrying the original element and all data required for sorting.
|
||||
*/
|
||||
public static final class ScoredOptionElement implements Comparable<ScoredOptionElement> {
|
||||
@Getter
|
||||
private final OptionMenuOptionElement element;
|
||||
public final String readableTranslatedName;
|
||||
public final String readableDefaultName;
|
||||
public final String path;
|
||||
public final int score;
|
||||
public final String query;
|
||||
|
||||
public ScoredOptionElement(OptionMenuOptionElement element,
|
||||
String readableTranslatedName,
|
||||
String readableDefaultName,
|
||||
String path,
|
||||
int score,
|
||||
String query) {
|
||||
this.element = element;
|
||||
this.readableTranslatedName = readableTranslatedName;
|
||||
this.readableDefaultName = readableDefaultName;
|
||||
this.path = path;
|
||||
this.score = score;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull ScoredOptionElement other) {
|
||||
String q = this.query != null ? this.query.toLowerCase(Locale.ROOT).trim() : "";
|
||||
int r;
|
||||
if ((r = compareByReadableNameQuality(this, other)) != 0) return r;
|
||||
if ((r = compareByMatchedWordLength(this, other, q)) != 0) return r;
|
||||
if ((r = compareByWordCount(this, other)) != 0) return r;
|
||||
if ((r = compareByFullScore(this, other)) != 0) return r;
|
||||
if ((r = compareByPrefixBoost(this, other, q)) != 0) return r;
|
||||
if ((r = compareByPathDepth(this, other)) != 0) return r;
|
||||
return compareByAlphabetical(this, other);
|
||||
}
|
||||
|
||||
// 1. How well does the readable name (translated + default) match?
|
||||
private static int compareByReadableNameQuality(ScoredOptionElement a, ScoredOptionElement b) {
|
||||
return Integer.compare(b.score & READABLE_NAME_BITS, a.score & READABLE_NAME_BITS);
|
||||
}
|
||||
|
||||
// 2. Shorter matched word = query covers a higher fraction of that word.
|
||||
private static int compareByMatchedWordLength(ScoredOptionElement a, ScoredOptionElement b, String q) {
|
||||
if (q.isEmpty()) return 0;
|
||||
String aw = findMatchingWord(a.readableTranslatedName, q);
|
||||
if (aw == null) aw = findMatchingWord(a.readableDefaultName, q);
|
||||
String bw = findMatchingWord(b.readableTranslatedName, q);
|
||||
if (bw == null) bw = findMatchingWord(b.readableDefaultName, q);
|
||||
int aLen = aw != null ? aw.length() : Integer.MAX_VALUE;
|
||||
int bLen = bw != null ? bw.length() : Integer.MAX_VALUE;
|
||||
return Integer.compare(aLen, bLen);
|
||||
}
|
||||
|
||||
// 3. Fewer words = more precise ("Bloom" before "Bloom Strength").
|
||||
private static int compareByWordCount(ScoredOptionElement a, ScoredOptionElement b) {
|
||||
String ae = !a.readableTranslatedName.isEmpty() ? a.readableTranslatedName : a.readableDefaultName;
|
||||
String be = !b.readableTranslatedName.isEmpty() ? b.readableTranslatedName : b.readableDefaultName;
|
||||
return Integer.compare(countWords(ae), countWords(be));
|
||||
}
|
||||
|
||||
// 4. Higher raw score wins.
|
||||
private static int compareByFullScore(ScoredOptionElement a, ScoredOptionElement b) {
|
||||
return Integer.compare(b.score, a.score);
|
||||
}
|
||||
|
||||
// 5. Prefix boost: readable name starts with the exact query string.
|
||||
private static int compareByPrefixBoost(ScoredOptionElement a, ScoredOptionElement b, String q) {
|
||||
if (q.isEmpty()) return 0;
|
||||
boolean ap = (a.readableTranslatedName != null && a.readableTranslatedName.startsWith(q))
|
||||
|| (a.readableDefaultName != null && a.readableDefaultName.startsWith(q));
|
||||
boolean bp = (b.readableTranslatedName != null && b.readableTranslatedName.startsWith(q))
|
||||
|| (b.readableDefaultName != null && b.readableDefaultName.startsWith(q));
|
||||
if (ap == bp) return 0;
|
||||
return ap ? -1 : 1;
|
||||
}
|
||||
|
||||
// 6. Shallower option path wins (fewer slashes = closer to root menu).
|
||||
private static int compareByPathDepth(ScoredOptionElement a, ScoredOptionElement b) {
|
||||
return Integer.compare(countSlashes(a.path), countSlashes(b.path));
|
||||
}
|
||||
|
||||
// 7. Alphabetical tie-breaker.
|
||||
private static int compareByAlphabetical(ScoredOptionElement a, ScoredOptionElement b) {
|
||||
String aId = a.element != null ? a.element.optionId : null;
|
||||
String bId = b.element != null ? b.element.optionId : null;
|
||||
if (aId != null && bId != null) return aId.compareTo(bId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String findMatchingWord(String name, String query) {
|
||||
if (name == null || query.isEmpty()) return null;
|
||||
for (String word : name.split("\\s+")) {
|
||||
if (word.startsWith(query)) return word;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int countWords(String s) {
|
||||
if (s == null || s.trim().isEmpty()) return 0;
|
||||
return s.trim().split("\\s+").length;
|
||||
}
|
||||
|
||||
private static int countSlashes(String path) {
|
||||
if (path == null) return 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < path.length(); i++) {
|
||||
if (path.charAt(i) == '/') count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,3 +145,7 @@ pack.iris.select.title=Auswählen
|
||||
pack.iris.configure.title=Konfigurieren
|
||||
label.iris.true=An
|
||||
label.iris.false=Aus
|
||||
options.iris.search.button=Suchen
|
||||
options.iris.clear.button=Schließen
|
||||
options.iris.search.tooltip=Shader-Einstellungen durchsuchen (Strg + F)
|
||||
options.iris.clear.tooltip=Shader-Suche beenden (Esc)
|
||||
@@ -17,6 +17,8 @@ sodium.options.pages.appearance=Appearance
|
||||
sodium.options.pages.text=Text Effects
|
||||
sodium.options.view_distance.tooltip=The render distance controls how far away terrain will be rendered. Shorter distances mean that less terrain will be rendered, improving frame rates.
|
||||
sodium.options.simulation_distance.tooltip=The simulation distance controls how far away terrain and entities will be loaded and ticked. Shorter distances can reduce the internal server's load and may improve frame rates.
|
||||
sodium.options.cc.vertical_view_distance.name=Vertical View Distance
|
||||
sodium.options.cc.vertical_view_distance.tooltip=The vertical view distance controls the vertical range within which cubes will be loaded, centered on the player. Shorter distances mean that less terrain will be rendered, improving frame rates.
|
||||
sodium.options.brightness.tooltip=Controls the brightness (gamma) of the game.
|
||||
sodium.options.clouds.name=Clouds
|
||||
sodium.options.clouds.tooltip=Controls whether or not clouds will be visible.
|
||||
@@ -139,6 +141,10 @@ options.iris.shadowDistance.enabled=Allows you to change the maximum distance fo
|
||||
options.iris.shadowDistance.disabled=Your current shader pack has already set a render distance for shadows; you cannot change it.
|
||||
options.iris.gui.hide=Hide GUI
|
||||
options.iris.gui.show=Show GUI
|
||||
options.iris.search.button=Search
|
||||
options.iris.clear.button=Clear
|
||||
options.iris.search.tooltip=Search Shader Settings (Ctrl + F)
|
||||
options.iris.clear.tooltip=Exit Shader Search (Esc)
|
||||
|
||||
options.dynamic_lights=Dynamic Lights
|
||||
options.dynamic_lights.tooltip=Enables dynamic lights. Modes: OFF - off, FASTEST - 500ms delay, FAST - 250ms delay, FANCY - 50ms delay, REALTIME - no delay.
|
||||
|
||||
@@ -132,3 +132,7 @@ pack.iris.select.title=Выбрать
|
||||
pack.iris.configure.title=Настроить
|
||||
label.iris.true=Вкл
|
||||
label.iris.false=Выкл
|
||||
options.iris.search.button=Поиск
|
||||
options.iris.clear.button=Очистить
|
||||
options.iris.search.tooltip=Поиск настроек шейдеров (Ctrl + F)
|
||||
options.iris.clear.tooltip=Выход из поиска шейдеров (Esc)
|
||||
@@ -138,6 +138,10 @@ options.iris.shadowDistance.enabled=更改渲染阴影的最远距离。超出
|
||||
options.iris.shadowDistance.disabled=当前光影包已重设阴影渲染距离,无法更改
|
||||
options.iris.gui.hide=隐藏 GUI
|
||||
options.iris.gui.show=显示 GUI
|
||||
options.iris.search.button=搜索
|
||||
options.iris.clear.button=清除搜索栏
|
||||
options.iris.search.tooltip=搜索光影包设定 (Ctrl + F)
|
||||
options.iris.clear.tooltip=退出搜索 (Esc)
|
||||
|
||||
options.dynamic_lights=动态光源
|
||||
options.dynamic_lights.tooltip=启用动态光源,模式选择:OFF-关闭,FASTEST-500ms延迟,FAST-250ms延迟,FANCY-50ms延迟,REALTIME-无延迟
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#version 330 core
|
||||
|
||||
in vec2 v_UV;
|
||||
in float v_FogCoord;
|
||||
in vec3 v_EyePos;
|
||||
in float v_Factor;
|
||||
|
||||
uniform sampler2D u_Tex;
|
||||
uniform vec4 u_ColorMult;
|
||||
uniform vec4 u_FogParams; // x: -1/(end-start), y: end/(end-start)
|
||||
uniform vec4 u_FogParams; // linear: x=-1/(end-start), y=end/(end-start); exp/exp2: z=density; w=mode (0=linear, 1=exp, 2=exp2)
|
||||
uniform vec4 u_FogColor;
|
||||
uniform bool u_FogEnabled;
|
||||
|
||||
@@ -19,8 +19,17 @@ void main() {
|
||||
color.rgb *= v_Factor;
|
||||
|
||||
if (u_FogEnabled) {
|
||||
float f = clamp(v_FogCoord * u_FogParams.x + u_FogParams.y, 0.0, 1.0);
|
||||
color.rgb = mix(u_FogColor.rgb, color.rgb, f);
|
||||
float fogCoord = length(v_EyePos);
|
||||
float f;
|
||||
if (u_FogParams.w == 0.0) {
|
||||
f = fogCoord * u_FogParams.x + u_FogParams.y;
|
||||
} else if (u_FogParams.w == 1.0) {
|
||||
f = exp(-u_FogParams.z * fogCoord);
|
||||
} else {
|
||||
float d = u_FogParams.z * fogCoord;
|
||||
f = exp(-d * d);
|
||||
}
|
||||
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(f, 0.0, 1.0));
|
||||
}
|
||||
|
||||
fragColor = color;
|
||||
|
||||
@@ -5,14 +5,15 @@ layout(location = 2) in vec2 a_UV;
|
||||
layout(location = 5) in float a_Factor;
|
||||
|
||||
uniform mat4 u_MVPMatrix;
|
||||
uniform mat4 u_MVMatrix;
|
||||
|
||||
out vec2 v_UV;
|
||||
out float v_FogCoord;
|
||||
out vec3 v_EyePos;
|
||||
out float v_Factor;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
|
||||
v_UV = a_UV;
|
||||
v_FogCoord = length(a_Position.xz);
|
||||
v_EyePos = (u_MVMatrix * vec4(a_Position, 1.0)).xyz;
|
||||
v_Factor = a_Factor;
|
||||
}
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -20,6 +21,7 @@ public class MixinMinecraft_FrameHook {
|
||||
|
||||
@Inject(method = "runGameLoop", at = @At("HEAD"))
|
||||
private void angelica$onFrameBegin(CallbackInfo ci) {
|
||||
DisplayListManager.abortIfLeaked();
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
}
|
||||
|
||||
|
||||
+56
-12
@@ -13,6 +13,7 @@ import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -22,7 +23,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Single-pass damage-overlay tint, modeled on modern Minecraft's OverlayTexture.
|
||||
* The tint is applied only while the main body model renders.
|
||||
*/
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public class MixinRendererLivingEntity_OverlayTint {
|
||||
@@ -62,13 +62,20 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderModel(Lnet/minecraft/entity/EntityLivingBase;FFFFFF)V",
|
||||
shift = At.Shift.AFTER),
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V"),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$clearBodyTint(EntityLivingBase entity, double x, double y, double z,
|
||||
float yaw, float partialTick, CallbackInfo ci) {
|
||||
angelica$clearOverlayColor();
|
||||
private void angelica$teardownOverlay(EntityLivingBase entity, double x, double y, double z,
|
||||
float yaw, float partialTick, CallbackInfo ci) {
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$ffpOverlayActive = false;
|
||||
}
|
||||
if (angelica$shaderEntityColorActive) {
|
||||
// Don't tint held items
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$shaderEntityColorActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,17 +98,45 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the overlay on additive passes (emissive eyes, enchantment glint).
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
|
||||
to = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$untintAdditivePass(ModelBase model, Entity entity,
|
||||
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
|
||||
if (!angelica$skipReRender || !angelica$isAdditivePass()) {
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
return;
|
||||
}
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else if (angelica$shaderEntityColorActive) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else {
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void angelica$reset(CallbackInfo ci) {
|
||||
angelica$clearOverlayColor();
|
||||
angelica$skipReRender = false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void angelica$clearOverlayColor() {
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$ffpOverlayActive = false;
|
||||
@@ -110,6 +145,15 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$shaderEntityColorActive = false;
|
||||
}
|
||||
angelica$skipReRender = false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$isAdditivePass() {
|
||||
if (!GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
|
||||
if (GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB) != GL11.GL_ONE) return false;
|
||||
final int src = GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB);
|
||||
return src == GL11.GL_ONE || src == GL11.GL_SRC_COLOR;
|
||||
}
|
||||
|
||||
@Unique
|
||||
|
||||
+9
-1
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.mixins.interfaces.ClippingHelperExt;
|
||||
import net.minecraft.client.renderer.culling.ClippingHelper;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -18,17 +19,24 @@ public class MixinClippingHelper implements ClippingHelperExt {
|
||||
@Unique private final FrustumIntersection celeritas$frustumIntersection = new FrustumIntersection();
|
||||
@Unique private final Matrix4f celeritas$projMatrix = new Matrix4f();
|
||||
@Unique private final Matrix4f celeritas$mvMatrix = new Matrix4f();
|
||||
@Unique private final Matrix4f celeritas$combined = new Matrix4f();
|
||||
|
||||
@Override
|
||||
public FrustumIntersection celeritas$getFrustumIntersection() {
|
||||
return this.celeritas$frustumIntersection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix4fc celeritas$getCombinedMatrix() {
|
||||
return this.celeritas$combined;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void celeritas$updateFrustumIntersection() {
|
||||
this.celeritas$projMatrix.set(this.projectionMatrix);
|
||||
this.celeritas$mvMatrix.set(this.modelviewMatrix);
|
||||
this.celeritas$frustumIntersection.set(this.celeritas$projMatrix.mul(this.celeritas$mvMatrix));
|
||||
this.celeritas$combined.set(this.celeritas$projMatrix).mul(this.celeritas$mvMatrix);
|
||||
this.celeritas$frustumIntersection.set(this.celeritas$combined, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+27
-2
@@ -5,23 +5,48 @@ import net.minecraft.client.renderer.culling.ClippingHelper;
|
||||
import net.minecraft.client.renderer.culling.Frustrum;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.ViewportProvider;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Frustrum.class)
|
||||
public abstract class MixinFrustrum implements ViewportProvider {
|
||||
@Shadow @Final private ClippingHelper clippingHelper;
|
||||
@Shadow private double xPosition, yPosition, zPosition;
|
||||
|
||||
@Unique private final FrustumIntersection celeritas$snapshot = new FrustumIntersection();
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void celeritas$snapshotFrustum(CallbackInfo ci) {
|
||||
this.celeritas$snapshot.set(((ClippingHelperExt) this.clippingHelper).celeritas$getCombinedMatrix(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Viewport sodium$createViewport() {
|
||||
// Shadow frustum subclasses override this via their own ViewportProvider impl.
|
||||
// This default handles vanilla Frustrum only.
|
||||
// This default handles vanilla Frustrum only, using the per-instance snapshot
|
||||
return new Viewport(
|
||||
((ClippingHelperExt) this.clippingHelper).celeritas$getFrustumIntersection()::testAab,
|
||||
this.celeritas$snapshot::testAab,
|
||||
new Vector3d(this.xPosition, this.yPosition, this.zPosition)
|
||||
);
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public boolean isBoxInFrustum(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
|
||||
if (Double.isInfinite(minX) || Double.isInfinite(minY) || Double.isInfinite(minZ)
|
||||
|| Double.isInfinite(maxX) || Double.isInfinite(maxY) || Double.isInfinite(maxZ)) {
|
||||
return true;
|
||||
}
|
||||
return this.celeritas$snapshot.testAab(
|
||||
(float) (minX - this.xPosition), (float) (minY - this.yPosition), (float) (minZ - this.zPosition),
|
||||
(float) (maxX - this.xPosition), (float) (maxY - this.yPosition), (float) (maxZ - this.zPosition)
|
||||
);
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ChunkTracker.class)
|
||||
public interface ChunkTrackerAccessor extends com.gtnewhorizons.angelica.mixins.interfaces.ChunkTrackerAccessor {
|
||||
@Accessor("chunkStatus")
|
||||
Long2IntOpenHashMap angelica$getChunkStatus();
|
||||
|
||||
@Accessor("chunkReady")
|
||||
LongOpenHashSet angelica$getChunkReady();
|
||||
|
||||
@Accessor("unloadQueue")
|
||||
LongSet angelica$getUnloadQueue();
|
||||
|
||||
@Accessor("loadQueue")
|
||||
LongSet angelica$getLoadQueue();
|
||||
}
|
||||
+18
-6
@@ -2,11 +2,6 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.hash.Fnv1a64;
|
||||
import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
|
||||
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
@@ -25,10 +20,17 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.hash.Fnv1a64;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
|
||||
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
|
||||
|
||||
@Mixin(Chunk.class)
|
||||
public abstract class MixinChunk implements IChunkTileEntityMapHolder {
|
||||
|
||||
@Shadow public Map<ChunkPosition, TileEntity> chunkTileEntityMap = new ConcurrentTileEntityMap();
|
||||
@Shadow public Map<ChunkPosition, TileEntity> chunkTileEntityMap;
|
||||
@Shadow public World worldObj;
|
||||
@Shadow private ExtendedBlockStorage[] storageArrays;
|
||||
@Final @Shadow public int xPosition;
|
||||
@@ -41,6 +43,16 @@ public abstract class MixinChunk implements IChunkTileEntityMapHolder {
|
||||
@Unique private long angelica$biomeHash;
|
||||
@Unique private boolean angelica$biomeHashValid;
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void angelica$wrapTEMapIfNeeded(CallbackInfo ci) {
|
||||
// Wrap the cubic chunks map, since it uses a custom map
|
||||
if (ModStatus.isCubicChunksLoaded) {
|
||||
this.chunkTileEntityMap = new ConcurrentTileEntityMap(this.chunkTileEntityMap);
|
||||
} else {
|
||||
this.chunkTileEntityMap = new ConcurrentTileEntityMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrentTileEntityMap angelica$getConcurrentTEMap() {
|
||||
return (ConcurrentTileEntityMap) this.chunkTileEntityMap;
|
||||
|
||||
+7
-4
@@ -1,19 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaChunkTracker;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaChunkTracker;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusTracker;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
|
||||
|
||||
@Mixin(WorldClient.class)
|
||||
public class MixinWorldClient implements ChunkTrackerHolder, WorldClientExtension {
|
||||
@Unique private final ChunkTracker celeritas$tracker = new AngelicaChunkTracker();
|
||||
@Unique private final ChunkTracker celeritas$tracker = ModStatus.isCubicChunksLoaded ? new CubeStatusTracker() : new AngelicaChunkTracker();
|
||||
@Unique private final SmoothBiomeColorCache celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this);
|
||||
|
||||
@Override
|
||||
|
||||
+7
-1
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.notfine.clouds;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.render.CloudRenderer;
|
||||
import jss.notfine.core.SettingsManager;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -12,7 +14,11 @@ import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
public abstract class MixinEntityRenderer {
|
||||
@ModifyArg(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false), index = 3)
|
||||
private float notFine$modifyFarPlane(float original) {
|
||||
return Math.max(original, SettingsManager.minimumFarPlaneDistance);
|
||||
float required = SettingsManager.minimumFarPlaneDistance;
|
||||
if (AngelicaConfig.enableVBOClouds) {
|
||||
required = Math.max(required, CloudRenderer.getCloudRenderer().getRequiredFarPlaneDistance());
|
||||
}
|
||||
return Math.max(original, required);
|
||||
}
|
||||
|
||||
}
|
||||
+9
-8
@@ -21,6 +21,7 @@ import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -185,7 +186,7 @@ public abstract class MixinRenderBlocks {
|
||||
public IBlockAccess blockAccess;
|
||||
|
||||
@Unique
|
||||
private void angelica$handleCompactCtmFace(IIcon icon, CallbackInfo ci) {
|
||||
private void angelica$handleCompactCtmFace(IIcon icon, ForgeDirection direction, CallbackInfo ci) {
|
||||
CTMUtils.CTMCompactContext ctx = CTMUtils.getCurrentCompact();
|
||||
if (ctx == null) {
|
||||
return;
|
||||
@@ -197,39 +198,39 @@ public abstract class MixinRenderBlocks {
|
||||
return;
|
||||
}
|
||||
|
||||
if (processor.processFace((RenderBlocks)(Object)this, renderBlockState, icon)) {
|
||||
if (processor.processFace((RenderBlocks)(Object)this, renderBlockState, icon, direction.ordinal())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceYNeg", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceYNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.DOWN, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceYPos", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceYPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.UP, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceZNeg", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceZNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.NORTH, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceZPos", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceZPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.SOUTH, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceXNeg", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceXNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.WEST, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderFaceXPos", at = @At("HEAD"), cancellable = true)
|
||||
private void compactCtm_onRenderFaceXPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
|
||||
angelica$handleCompactCtmFace(icon, ci);
|
||||
angelica$handleCompactCtmFace(icon, ForgeDirection.EAST, ci);
|
||||
}
|
||||
|
||||
@Inject(method = "renderStandardBlock(Lnet/minecraft/block/Block;III)Z", at = @At("RETURN"))
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import net.minecraft.client.model.ModelBox;
|
||||
import net.minecraft.client.model.TexturedQuad;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ModelBox.class)
|
||||
public interface AccessorModelBox {
|
||||
|
||||
@Accessor("quadList")
|
||||
TexturedQuad[] angelica$getQuadList();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.PlayerReflectionCapture;
|
||||
import net.minecraft.client.model.ModelBiped;
|
||||
import net.minecraft.client.model.ModelBox;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
import net.minecraft.client.model.PositionTextureVertex;
|
||||
import net.minecraft.client.model.TexturedQuad;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(ModelBiped.class)
|
||||
public abstract class MixinModelBiped {
|
||||
|
||||
@Unique private static final Matrix4f angelica$mat = new Matrix4f();
|
||||
@Unique private static final Vector3f angelica$vec = new Vector3f();
|
||||
|
||||
@Inject(method = "render(Lnet/minecraft/entity/Entity;FFFFFF)V", at = @At("RETURN"))
|
||||
private void angelica$capturePlayerReflection(Entity entity, float limbSwing, float limbSwingAmount,
|
||||
float ageInTicks, float netHeadYaw, float headPitch, float scale, CallbackInfo ci) {
|
||||
|
||||
if (!PlayerReflectionCapture.shouldCapture()) return;
|
||||
if ((Object) this != PlayerReflectionCapture.getTarget()) return;
|
||||
if (entity != PlayerReflectionCapture.getTargetEntity()) return;
|
||||
|
||||
final ModelBiped model = (ModelBiped) (Object) this;
|
||||
final float[] data = PlayerReflectionCapture.vertexScratch();
|
||||
|
||||
// Six 48-vertex blocks = body parts, each base box + overlay box
|
||||
int o = 0;
|
||||
o = angelica$emitBox(model.bipedHead, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedHeadwear, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedRightArm, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedRightArm, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedLeftLeg, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedLeftLeg, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedLeftArm, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedLeftArm, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedRightLeg, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedRightLeg, data, o, scale);
|
||||
o = angelica$emitBox(model.bipedBody, data, o, scale);
|
||||
angelica$emitBox(model.bipedBody, data, o, scale);
|
||||
|
||||
PlayerReflectionCapture.drawPlayerCapture(data);
|
||||
}
|
||||
|
||||
private static int angelica$emitBox(ModelRenderer part, float[] out, int o, float scale) {
|
||||
if (part == null || part.cubeList == null || part.cubeList.isEmpty()) {
|
||||
final int end = o + 24 * PlayerReflectionCapture.FLOATS_PER_VERTEX;
|
||||
Arrays.fill(out, o, end, 0.0f);
|
||||
return end;
|
||||
}
|
||||
|
||||
final Matrix4f m = angelica$mat.identity();
|
||||
m.translate(part.offsetX, part.offsetY, part.offsetZ);
|
||||
m.translate(part.rotationPointX * scale, part.rotationPointY * scale, part.rotationPointZ * scale);
|
||||
if (part.rotateAngleZ != 0.0f) m.rotateZ(part.rotateAngleZ);
|
||||
if (part.rotateAngleY != 0.0f) m.rotateY(part.rotateAngleY);
|
||||
if (part.rotateAngleX != 0.0f) m.rotateX(part.rotateAngleX);
|
||||
|
||||
final ModelBox box = part.cubeList.getFirst();
|
||||
final TexturedQuad[] quads = ((AccessorModelBox) (Object) box).angelica$getQuadList();
|
||||
final Vector3f v = angelica$vec;
|
||||
|
||||
for (int q = 0; q < 6; q++) {
|
||||
final PositionTextureVertex[] corners = quads[q].vertexPositions;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
final PositionTextureVertex corner = corners[c];
|
||||
v.set((float) corner.vector3D.xCoord * scale,
|
||||
(float) corner.vector3D.yCoord * scale,
|
||||
(float) corner.vector3D.zCoord * scale);
|
||||
m.transformPosition(v);
|
||||
out[o++] = v.x;
|
||||
out[o++] = v.y;
|
||||
out[o++] = v.z;
|
||||
out[o++] = corner.texturePositionX;
|
||||
out[o++] = corner.texturePositionY * 0.5f;
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ dependencies {
|
||||
}
|
||||
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
|
||||
|
||||
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 }
|
||||
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
|
||||
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
|
||||
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
|
||||
}
|
||||
Reference in new issue
Block a user