Compare commits

..
22 Commits
Author SHA1 Message Date
Jason Mitchell 5b704d1bc2 Fix space elevator cable (#1896) 2026-06-28 16:33:58 -07:00
Jason Mitchell 26135124f4 Support more glDrawElement overloads in display lists (#1895) 2026-06-28 15:07:24 -07:00
Jason Mitchell 3abc89b7b2 Betweenlands fix (#1894) 2026-06-28 13:46:10 -07:00
Jason Mitchell 03d908d658 Update readme and version detectors (#1893) 2026-06-27 15:47:32 -07:00
Eclipse-Sol 76bf19c8f6 Majority shader fixes (#1868) 2026-06-27 14:48:51 -07:00
Eclipse-Sol b27834cbd9 Narrow and make damage overlay a config option (#1886) 2026-06-27 10:12:39 -07:00
sisyphussyandJason Mitchell eb0ecb0483 Fix deps (#1888)
Co-authored-by: Jason Mitchell <[email protected]>
2026-06-27 10:11:30 -07:00
Caedis 9acb0e67a7 feat: add smooth zoom option (#1890) 2026-06-27 09:57:21 -07:00
Jason Mitchell f4f9b56b17 End portal fix (#1891) 2026-06-27 09:56:21 -07:00
Midnight145 2196439e6c ClientEvent framework (primarily written for LiteloaderLoader) (#1843) 2026-06-25 20:13:18 -07:00
RedNicStone dd68c64239 Fix map rendering issue in xaero's map mods (#1880) 2026-06-21 14:13:11 -07:00
Jason Mitchell 0decc515e4 Fix universium shader (#1877) 2026-06-18 23:42:21 -07:00
tiffit b3e1e89c88 Add new CTM mode "compact_expanded" to generate textures on runtime (#1840)
* Rewrite compact CTM to generate the textures ahead of time

* fix animated textures not working

* only interpolate if notfine is enabled

* some bugfixes

* bring back old compact ctm and make a new compact_expanded

* bug fixes
2026-06-18 12:04:45 +03:00
Jason Mitchell 32bff85526 Handle glDrawArraysInstanced and variants, as well as dynamic TE bounds. (#1874) 2026-06-17 20:18:07 -07:00
Auynonymous 02fc95f239 Make Glass CTM Compact, Add Stained Glass Compact CTM (#1873) 2026-06-17 16:00:58 -07:00
MalTeeez 07fd6d78a0 Separate out entity overlay render mixins (#1872) 2026-06-17 19:55:12 +00:00
Jason Mitchell 87aa853f81 Attempt to fix deferred particle batching (#1871) 2026-06-16 22:16:26 -07:00
Jason Mitchell ab69eea654 Announce releases (#1867) 2026-06-15 14:50:47 -07:00
Jason Mitchell edccf63b66 Bump glsl-transformation-lib and buildscript (#1865) 2026-06-14 18:09:05 -07:00
MalTeeez 80413fb3de Add policy for AI contributions & a PR template (#1864) 2026-06-15 01:40:03 +02:00
Jason Mitchell f3ed9aa82b Force invalidate dh depth tex when reconnecting DH Textures (#1862) 2026-06-14 14:59:01 -07:00
Jason Mitchell 307c563b6d Rework DH matrix handling (#1861) 2026-06-14 14:20:19 -07:00
268 changed files with 2989 additions and 411 deletions

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# Description or your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- Add a screenshot or a video demonstration for new features -->
<details open><summary>Template</summary> <!-- OPEN is intentional -->
### Before submitting a *pull request* make sure you have:
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
</details>
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
: "${REPO:?REPO required}"
: "${TAG:?TAG required}"
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
echo "DISCORD_WEBHOOK_URL not set, skipping"
exit 0
fi
if ! release_json=$(gh release view "$TAG" --repo "$REPO" --json body,url,publishedAt 2>&1); then
echo "Could not read release $TAG in $REPO; skipping:"
printf '%s\n' "$release_json"
exit 0
fi
body=$(jq -r '.body // ""' <<<"$release_json")
url=$(jq -r '.url' <<<"$release_json")
ts=$(jq -r '.publishedAt // ""' <<<"$release_json")
[ -n "$ts" ] || ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
payload=$(jq -n \
--arg title "$TAG" \
--arg url "$url" \
--arg desc "$body" \
--arg ts "$ts" \
'{
embeds: [{
title: ("Angelica " + $title + " released"),
url: $url,
description: (if ($desc | length) > 3900
then $desc[0:3900] + "\n\n...[full changelog](" + $url + ")"
else $desc end),
color: 3066993,
footer: { text: "GitHub Releases" },
timestamp: $ts
}]
}')
curl -fsS -X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
+19
View File
@@ -12,3 +12,22 @@ jobs:
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
notify-discord:
needs: [release-tags]
runs-on: ubuntu-24.04
if: >-
${{ !endsWith(github.ref_name, '-snapshot')
&& !contains(github.event.head_commit.message, '[snapshot]')
&& !endsWith(github.ref_name, '-pre') }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Announce release to Discord
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/announce-release.sh
+55
View File
@@ -0,0 +1,55 @@
# AI Usage Policy
This project has strict rules for AI usage:
* **The human-in-the-loop must fully understand all code.** If you
can't explain what your changes do and how they interact with the
greater system without the aid of AI tools, do not contribute
those changes.
* **Issues and discussions can use AI assistance but must have a full
human-in-the-loop.** This means that any content generated with AI
must have been reviewed *and edited* by a human before submission.
The author must do their research and trim this down.
* **No AI-generated media is allowed (textures, videos, audio, etc.).**
Text and code are the only acceptable AI-generated content, per the
other rules in this policy.
* **Commit messages, PR descriptions must read in your own voice.**
No AI-authored or AI co-authored commits, including Co-authored-by AI tags.
* **Only contribute fixes for bugs you have personally hit in-game.**
If you haven’t experienced or reproduced a bug firsthand, and don’t
already know the relevant code well, don’t throw AI at it and forward it to us.
Patches written for problems the author never actually experienced themselves
tend to fix the wrong thing and/or produce another.
* **The voice and intent of written content must be yours.**
For documentation and especially quests, the tone and the point you're trying to make
has to come from you, not an AI. Use AI to help with phrasing or cleanup if you like,
but if the substance and voice aren't yours, don't contribute it.
* **Bad AI use may be blocked.** People who produce bad contributions
that are clearly AI (slop) may be blocked from making future contributions.
We love to help junior developers learn and grow, but if you're interested
in that, don't use AI and we'll help you. We're sorry that bad AI
users have ruined this for you.
## There are Humans Here
Please remember that this project is maintained by humans.
Every discussion, issue, and pull request is read and reviewed by
humans (and sometimes machines, too). It is a boundary point at which
people interact with each other and work is done. It is rude and
disrespectful to approach this boundary with low-effort, unqualified
work, since it puts the burden of validation on the maintainer.
In a perfect world, AI would produce high-quality, accurate work
every time. But today, that reality depends on the user of the AI.
And today, many users of AI do not use it well. So, until that
changes, we need to have strict rules to protect maintainers.
Based on https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
+1 -1
View File
@@ -23,7 +23,7 @@ Targets GL 3.3+ core profile
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
+5 -2
View File
@@ -76,14 +76,14 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.11.4:dev")
api("com.github.GTNewHorizons:GTNHLib:0.11.19:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-32.g7dd88a4-GTNH") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-33.g3ec381e") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
@@ -173,6 +173,9 @@ dependencies {
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
testRuntimeOnly("it.unimi.dsi:fastutil:8.5.18")
testRuntimeOnly("org.joml:joml:1.10.8")
}
}
+2
View File
@@ -96,6 +96,8 @@ dependencies {
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}")
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false }
// Test
testImplementation(sourceSets["stubs"].output)
+3 -1
View File
@@ -4,7 +4,9 @@ celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.10.0
gtnhlibVersion = 0.11.19
fastutilVersion = 8.5.18
jomlVersion = 1.10.8
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
@@ -1,5 +1,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -8,9 +13,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -182,8 +189,9 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
final FFPInputs ffp = FFPInputs.detect(source);
assignVertexInputLocations(transformer, ffp);
transformVertexAttributes(transformer, source, ffp);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
@@ -310,25 +318,25 @@ public class CompatShaderTransformer {
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
if (ffp.position()) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
if (ffp.color()) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
if (ffp.uv0()) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
if (ffp.uv1()) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
if (ffp.normal()) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
@@ -337,6 +345,145 @@ public class CompatShaderTransformer {
}
}
private static final int MAX_VERTEX_ATTRIBS = 16;
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
static FFPInputs detect(String source) {
return new FFPInputs(
source.contains("gl_Vertex") || source.contains("ftransform"),
source.contains("gl_Color"),
source.contains("gl_MultiTexCoord0"),
source.contains("gl_MultiTexCoord1"),
source.contains("gl_Normal"));
}
}
/** Adaptation of Mesa's assign_attribute_or_color_locations */
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
int used = 0;
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
final List<Input> toAssign = new ArrayList<>();
int index = 0;
for (TerminalNode storage : transformer.collectStorage()) {
final int tokenType = storage.getSymbol().getType();
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
ParseTree node = storage.getParent();
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
node = node.getParent();
}
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
if (decl.typeless_declaration() == null) continue;
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
final String typeArray = arraySuffix(typeSpec.array_specifier());
if (hasLayoutQualifier(decl)) {
final int loc = explicitLocation(decl);
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
if (loc >= 0) used |= slotMask(slots) << loc;
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
}
}
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
for (Input in : toAssign) {
final int loc = findAvailableSlots(used, in.slots());
if (loc < 0) continue;
used |= slotMask(in.slots()) << loc;
transformer.removeVariable(in.name());
transformer.variable = null;
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
}
}
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
}
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
if (arraySpec == null) return 1;
int product = 1;
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
if (dim.constant_expression() == null) continue;
try {
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {}
}
return product;
}
private static int baseSlots(String type) {
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
return dims.charAt(0) - '0';
}
return 1;
}
private static int slotMask(int slots) {
return (slots >= 32) ? -1 : (1 << slots) - 1;
}
private static int findAvailableSlots(int used, int needed) {
if (needed <= 0) return -1;
final int mask = slotMask(needed);
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
if (((mask << i) & ~used) == (mask << i)) return i;
}
return -1;
}
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return -1;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
if (layout == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
try {
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {
return -1;
}
}
}
}
return -1;
}
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
if (single.layout_qualifier() != null) return true;
}
return false;
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
@@ -59,7 +59,10 @@ public class CompatUniformManager {
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -89,6 +92,8 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
@@ -229,10 +234,15 @@ public class CompatUniformManager {
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
}
}
}
@@ -266,10 +276,15 @@ public class CompatUniformManager {
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
if (locs[LOC_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
}
}
}
@@ -133,7 +133,10 @@ public class DisplayListManager {
}
public static UnsupportedOperationException unsupportedInList(String what) {
return new UnsupportedOperationException(what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
final UnsupportedOperationException e = new UnsupportedOperationException( what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
GLStateManager.warnOnce("unsupported:" + what, "Aborting display list {} compilation: {} unsupported during recording", glListId, what);
abortCompilation();
return e;
}
public static String describeCurrentCompilation() {
@@ -798,7 +801,8 @@ public class DisplayListManager {
*/
public static void glEndList() {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
try {
finishCurrentList();
@@ -906,17 +910,56 @@ public class DisplayListManager {
// Parent's COMPILING callback is now active again
} else {
// Not nested - clear all compilation state
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
glListId = -1;
glListMode = 0;
resetCompilationState();
}
}
/** Clear all root-level compilation state back to "not recording". */
private static void resetCompilationState() {
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
glListId = -1;
glListMode = 0;
}
public static void abortCompilation() {
if (glListMode == 0 && compilationStack.isEmpty()) {
return;
}
final int levels = compilationStack.size() + (glListMode != 0 ? 1 : 0);
for (int i = 0; i < levels; i++) {
try {
TessellatorManager.stopCapturingDirect();
} catch (IllegalStateException ignored) {
break;
}
}
if (currentRecorder != null) {
currentRecorder.delete();
}
for (CompilationContext ctx : compilationStack) {
if (ctx.recorder() != null) {
ctx.recorder().delete();
}
}
compilationStack.clear();
resetCompilationState();
}
public static void abortIfLeaked() {
if (glListMode != 0 || !compilationStack.isEmpty()) {
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
abortCompilation();
}
}
@@ -16,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
@@ -56,6 +57,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.joml.Vector3f;
@@ -93,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;
@@ -118,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();
@@ -186,6 +195,14 @@ public class GLStateManager {
private static boolean dirtyTexCoordAttrib;
private static boolean dirtyLightmapAttrib = true;
private static boolean unit23TexCoordSetDuringDraw = false;
public static boolean consumeUnit23TexCoordSetDuringDraw() {
boolean v = unit23TexCoordSetDuringDraw;
unit23TexCoordSetDuringDraw = false;
return v;
}
// vertexFlags bits mark attribs the VBO already supplies; FFP supplies the rest via u_Current* uniforms.
// Skip the backend vertexAttrib call in either case.
public static void flushDeferredVertexAttribs(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
@@ -637,6 +654,9 @@ public class GLStateManager {
RENDER_BACKEND.bindVertexArray(defaultVAO);
boundVAO = defaultVAO;
VAOManager.init(defaultVAO);
if (defaultVAO != 0) {
RENDER_BACKEND.provokingVertex(GL32.GL_LAST_VERTEX_CONVENTION);
}
if (initCallback != null) {
initCallback.run();
}
@@ -1601,6 +1621,11 @@ public class GLStateManager {
ImmediateModeRecorder.setColor(red, green, blue, alpha);
}
dirtyColorAttrib = true;
// Only set while an enchantment-glint span is active
final GlintColorHandler glintHandler = GLSMHooks.glintColorHandler;
if (glintHandler != null) {
glintHandler.onColorChanged(red, green, blue, alpha);
}
return true;
}
return false;
@@ -2154,6 +2179,7 @@ public class GLStateManager {
}
public static void glBegin(int mode) {
unit23TexCoordSetDuringDraw = false;
if (DisplayListManager.isRecording()) {
ImmediateModeRecorder.begin(mode);
return;
@@ -2229,66 +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;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
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);
}
}
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) {
@@ -2309,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) {
@@ -2349,10 +2410,29 @@ public class GLStateManager {
}
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTrianglesInstanced(count, type, indices, primcount);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElementsInstanced(mode, count, type, indices, primcount);
}
public static void glDrawArraysInstanced(int mode, int first, int count, int primcount) {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTrianglesInstanced(first, count, primcount);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArraysInstanced(GL11.GL_TRIANGLE_STRIP, first, count & ~1, primcount);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArraysInstanced(GL11.GL_TRIANGLE_FAN, first, count, primcount);
} else {
preDraw(mode);
RENDER_BACKEND.drawArraysInstanced(mode, first, count, primcount);
}
}
private static void prepareClientArrays() {
if (VAOManager.hasAnyClientSideEnabledAttrib()) {
VAOManager.uploadClientArraysToVBO();
@@ -2390,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));
}
@@ -2399,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) {
@@ -2452,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) {
@@ -2500,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) {
@@ -3437,6 +3530,21 @@ public class GLStateManager {
}
}
public static void setModelViewMatrix(Matrix4fc m) {
if (isCachingEnabled()) {
modelViewMatrix.set(m);
mvGeneration++;
mvLinearGeneration++;
}
}
public static void setProjectionMatrix(Matrix4fc m) {
if (isCachingEnabled()) {
projectionMatrix.set(m);
projGeneration++;
}
}
public static Matrix4fStack getMatrixStack() {
switch (matrixMode.getMode()) {
case GL11.GL_MODELVIEW -> {
@@ -5283,6 +5391,9 @@ public class GLStateManager {
} else {
final int unit = target - GL13.GL_TEXTURE0;
if (unit >= 2 && unit < 4) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
unit23TexCoordSetDuringDraw = true;
}
ShaderManager.setCurrentTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
@@ -5732,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);
}
@@ -5988,15 +6102,15 @@ public class GLStateManager {
}
public static void glUniform1(int location, IntBuffer values) {
if (values.remaining() >= 1) RENDER_BACKEND.uniform1i(location, values.get(values.position()));
RENDER_BACKEND.uniform1iv(location, values);
}
public static void glUniform2(int location, FloatBuffer values) {
if (values.remaining() >= 2) RENDER_BACKEND.uniform2f(location, values.get(values.position()), values.get(values.position() + 1));
RENDER_BACKEND.uniform2(location, values);
}
public static void glUniform2(int location, IntBuffer values) {
if (values.remaining() >= 2) RENDER_BACKEND.uniform2i(location, values.get(values.position()), values.get(values.position() + 1));
RENDER_BACKEND.uniform2iv(location, values);
}
public static void glUniform3(int location, FloatBuffer values) {
@@ -6004,7 +6118,7 @@ public class GLStateManager {
}
public static void glUniform3(int location, IntBuffer values) {
if (values.remaining() >= 3) RENDER_BACKEND.uniform3i(location, values.get(values.position()), values.get(values.position() + 1), values.get(values.position() + 2));
RENDER_BACKEND.uniform3iv(location, values);
}
public static void glUniform4(int location, FloatBuffer values) {
@@ -6012,7 +6126,7 @@ public class GLStateManager {
}
public static void glUniform4(int location, IntBuffer values) {
if (values.remaining() >= 4) RENDER_BACKEND.uniform4i(location, values.get(values.position()), values.get(values.position() + 1), values.get(values.position() + 2), values.get(values.position() + 3));
RENDER_BACKEND.uniform4iv(location, values);
}
public static void glUniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {
@@ -83,6 +83,14 @@ public final class QuadConverter {
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
drawSharedQuadEbo(first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
}
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
GLStateManager.preDraw();
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
@@ -92,7 +100,11 @@ public final class QuadConverter {
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
@@ -107,6 +119,10 @@ public final class QuadConverter {
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
@@ -126,7 +142,11 @@ public final class QuadConverter {
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
@@ -141,6 +161,14 @@ public final class QuadConverter {
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
@@ -155,7 +183,7 @@ public final class QuadConverter {
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
@@ -310,6 +310,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL31.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
GL31.glDrawArraysInstanced(mode, first, count, primcount);
}
@Override
public void provokingVertex(int provokeMode) {
GL32.glProvokingVertex(provokeMode);
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
@@ -812,6 +822,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20.glUniform3(location, value);
@@ -822,6 +837,26 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20.glUniform1(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20.glUniform3(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20.glUniform4(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix3(location, transpose, value);
@@ -87,6 +87,8 @@ public abstract class RenderBackend {
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawArraysInstanced(int mode, int first, int count, int primcount);
public void provokingVertex(int provokeMode) {}
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public abstract void drawBuffer(int mode);
@@ -199,8 +201,13 @@ public abstract class RenderBackend {
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform2(int location, FloatBuffer value);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniform1iv(int location, IntBuffer value);
public abstract void uniform2iv(int location, IntBuffer value);
public abstract void uniform3iv(int location, IntBuffer value);
public abstract void uniform4iv(int location, IntBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
@@ -135,7 +135,8 @@ public final class FragmentKey {
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
@@ -78,7 +78,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", v_TexCoord").append(i).append(".st);\n");
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
}
}
@@ -85,6 +85,7 @@ public final class VAOManager {
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
@@ -101,11 +102,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);
@@ -239,6 +253,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 +268,7 @@ public final class VAOManager {
offset = 0;
vboId = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
@@ -49,6 +49,7 @@ public final class VertexKey {
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -85,6 +86,7 @@ public final class VertexKey {
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
public boolean unit23UvFromUnit0() { return bit(BIT_UNIT23_UV_FROM_UNIT0); }
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
@@ -213,6 +215,10 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
@@ -106,8 +106,8 @@ public final class VertexShaderGenerator {
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.unitTexCoordEnabled(2) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
@@ -363,8 +363,10 @@ public final class VertexShaderGenerator {
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = "u_CurrentTexCoord" + i;
if (key.unitTexMatEnabled(i)) {
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
@@ -383,7 +385,7 @@ public final class VertexShaderGenerator {
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
emitTexGenComponent(sb, key.texGenModeR(), "p", "R", key);
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
@@ -8,6 +8,7 @@ public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface GlintColorHandler {
void onColorChanged(float red, float green, float blue, float alpha);
}
@@ -9,8 +9,6 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -52,20 +50,14 @@ public final class IndexedDrawCapture {
return freed;
}
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
private static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
}
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
if (indicesCount == 0) return null;
if (eboId == 0) {
warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
GLStateManager.warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
return null;
}
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
@@ -74,29 +66,53 @@ public final class IndexedDrawCapture {
final int prevEBO = GLStateManager.getBoundEBO();
final int prevVBO = GLStateManager.getBoundVBO();
ByteBuffer eboSrc = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
GLStateManager.warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
GLStateManager.warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
return bake(mode, indicesCount, srcIndexType, MemoryUtilities.memAddress0(eboSrc));
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
public static IndexedDrawCapture createFromClientIndices(int mode, int indicesCount, int srcIndexType, long indexSrcAddr, long srcByteLength) {
if (indicesCount == 0) return null;
if (indexSrcAddr == 0L) return null;
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 - skipping", indicesCount);
return null;
}
final int idxSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (idxSize <= 0) return null;
if ((long) indicesCount * idxSize > srcByteLength) {
GLStateManager.warnOnce("client-idx-range", "[IndexedDrawCapture] index count {} ({} bytes) exceeds client buffer {} bytes - skipping", indicesCount, (long) indicesCount * idxSize, srcByteLength);
return null;
}
return bake(mode, indicesCount, srcIndexType, indexSrcAddr);
}
private static IndexedDrawCapture bake(int mode, int indicesCount, int srcIndexType, long indexSrcAddr) {
AttribSnapshot snap = null;
ByteBuffer vertexData = null;
ByteBuffer indexData = null;
boolean success = false;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
final long eboSrcAddr = MemoryUtilities.memAddress0(eboSrc);
final long minMax = QuadConverter.scanMinMaxIndex(eboSrcAddr, srcIndexType, indicesCount);
final long minMax = QuadConverter.scanMinMaxIndex(indexSrcAddr, srcIndexType, indicesCount);
if (minMax == -1L) {
warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
GLStateManager.warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
return null;
}
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
@@ -110,7 +126,7 @@ public final class IndexedDrawCapture {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
GLStateManager.warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
return null;
}
final int[] locations = new int[enabledCount];
@@ -137,6 +153,13 @@ public final class IndexedDrawCapture {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
final long readStart = relOffset + (long) minVtx * d.effectiveStride();
final long readEnd = relOffset + (long) maxVtx * d.effectiveStride() + (long) d.size() * d.typeSizeBytes();
if (readStart < 0 || readEnd > d.readBuffer().capacity()) {
GLStateManager.warnOnce("vtx-oob", "[IndexedDrawCapture] attrib {} read extent {} exceeds snapshot {} bytes - skipping", d.location(), readEnd, d.readBuffer().capacity());
return null;
}
if (a == 0) {
sharedSrcBuffer = d.readBuffer();
sharedSrcStride = d.effectiveStride();
@@ -169,12 +192,12 @@ public final class IndexedDrawCapture {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
QuadConverter.triangulateQuads(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
bakedDrawMode = GL11.GL_TRIANGLES;
} else {
bakedIndexCount = indicesCount;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
QuadConverter.widenIndices(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
@@ -184,14 +207,11 @@ public final class IndexedDrawCapture {
success = true;
return out;
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
@@ -46,6 +46,8 @@ public class GLSMRedirector {
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
private static final String ARBDrawInstanced = "org/lwjgl/opengl/ARBDrawInstanced";
private static final String EXTDrawInstanced = "org/lwjgl/opengl/EXTDrawInstanced";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -356,7 +358,8 @@ public class GLSMRedirector {
.add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced");
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced");
final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture");
final var gl33 = RedirectMap.newMap()
@@ -459,6 +462,14 @@ public class GLSMRedirector {
methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
.add("glVertexAttribDivisorARB")
);
methodRedirects.put(ARBDrawInstanced, RedirectMap.newMap()
.add("glDrawArraysInstancedARB", "glDrawArraysInstanced")
.add("glDrawElementsInstancedARB", "glDrawElementsInstanced")
);
methodRedirects.put(EXTDrawInstanced, RedirectMap.newMap()
.add("glDrawArraysInstancedEXT", "glDrawArraysInstanced")
.add("glDrawElementsInstancedEXT", "glDrawElementsInstanced")
);
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
@@ -0,0 +1,103 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class ArrayUniformUploadGLTest {
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static int link(String vs, String fs) {
final int v = compile(GL20.GL_VERTEX_SHADER, vs);
final int f = compile(GL20.GL_FRAGMENT_SHADER, fs);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
private static final String VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
@Test
void glUniform2FloatBufferUploadsWholeArray() {
// Runtime index keeps the whole array active so element locations stay queryable.
final String fs = "#version 330 core\n"
+ "uniform vec2 cosmicuvs[10];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(cosmicuvs[idx], cosmicuvs[(idx+1)%10]); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final FloatBuffer buf = BufferUtils.createFloatBuffer(20);
for (int i = 0; i < 20; i++) buf.put(i + 1.0f); // 1..20, all distinct, all non-zero
buf.flip();
GLStateManager.glUniform2(GL20.glGetUniformLocation(p, "cosmicuvs"), buf);
final FloatBuffer read = BufferUtils.createFloatBuffer(2);
for (int i = 0; i < 10; i++) {
final int loc = GL20.glGetUniformLocation(p, "cosmicuvs[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(2 * i + 1.0f, read.get(0), 0.0f, "cosmicuvs[" + i + "].x");
assertEquals(2 * i + 2.0f, read.get(1), 0.0f, "cosmicuvs[" + i + "].y");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
@Test
void glUniform1IntBufferUploadsWholeArray() {
final String fs = "#version 330 core\n"
+ "uniform int vals[4];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(float(vals[idx])); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final IntBuffer buf = BufferUtils.createIntBuffer(4);
buf.put(11).put(22).put(33).put(44).flip();
GLStateManager.glUniform1(GL20.glGetUniformLocation(p, "vals"), buf);
final IntBuffer read = BufferUtils.createIntBuffer(1);
final int[] expect = {11, 22, 33, 44};
for (int i = 0; i < 4; i++) {
final int loc = GL20.glGetUniformLocation(p, "vals[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(expect[i], read.get(0), "vals[" + i + "]");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -613,4 +615,239 @@ class CompatShaderTransformerTest {
assertFalse(result.matches("(?s).*\\bnew\\b(?!\\s*\\().*"), "'new' as identifier must be renamed in core profile output\n\n" + result);
assertTrue(result.contains("angelica_renamed_new"), "'new' should be renamed to angelica_renamed_new\n\n" + result);
}
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static String core(String src) {
return compact(CompatShaderTransformer.transform(src, false));
}
private static void assertLoc(String c, int loc, String decl) {
assertTrue(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " expected at location " + loc + "\n\n" + c);
}
private static void assertNoLoc(String c, int loc, String decl) {
assertFalse(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " must not be at location " + loc + "\n\n" + c);
}
@Test
void testCustomPositionAttributePinnedToLocationZero() {
// Betweenlands blshader fix
String src = """
#version 120
attribute vec4 a_position;
uniform mat4 u_projMat;
uniform vec2 u_outSize;
varying vec2 v_texCoord;
void main(){
gl_Position = u_projMat * vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy / u_outSize;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertLoc(compact(result), 0, "vec4 a_position");
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultipleLocationlessInputsGetSequentialLocations() {
String c = core("""
#version 120
attribute vec4 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_pos");
assertLoc(c, 1, "vec2 a_uv");
}
@Test
void testCustomInputReservesAroundGlVertex() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = ftransform() + a_foo;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertLoc(c, 1, "vec4 a_foo");
}
@Test
void testCustomInputSkipsFfpSlots() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = vec4(a_foo.xy, 0.0, 1.0) + gl_Color;
}
""");
assertLoc(c, 0, "vec4 a_foo");
assertLoc(c, 1, "vec4 angelica_Color");
}
@Test
void testExplicitLowLocationReserved() {
String c = core("""
#version 120
layout(location = 0) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertLoc(c, 0, "vec4 a_x");
assertLoc(c, 1, "vec4 a_y");
}
@Test
void testMatrixInputSortedFirst() {
String c = core("""
#version 120
attribute vec4 a_a;
attribute mat4 a_m;
void main() {
gl_Position = a_m * a_a;
}
""");
assertLoc(c, 0, "mat4 a_m"); // 4-slot matrix sorted first
assertLoc(c, 4, "vec4 a_a");
}
@Test
void testInputNamedLocationStillPinned() {
assertLoc(core("""
#version 120
attribute vec4 a_location;
void main() {
gl_Position = vec4(a_location.xy, 0.0, 1.0);
}
"""), 0, "vec4 a_location");
}
@Test
void testInputWithExplicitLayoutLeftAlone() {
String c = core("""
#version 120
layout(location = 3) in vec4 a_data;
attribute vec4 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_data.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_position"); // sole location-less input
assertLoc(c, 3, "vec4 a_data"); // explicit location preserved
assertNoLoc(c, 0, "vec4 a_data");
}
@Test
void testPinnedShaderRemainsWellFormed() {
String src = """
#version 120
attribute vec4 a_position;
void main() {
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewMatrix * vec4(a_position.xyz, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
String c = compact(result);
assertLoc(c, 0, "vec4 a_position");
assertLoc(c, 1, "vec4 angelica_Color");
assertTrue(c.contains("uniformmat4angelica_ModelViewMatrix"), "matrix uniform injected\n\n" + result);
assertFalse(c.contains("gl_ModelViewMatrix"), "builtin rewritten (not fallback)\n\n" + result);
final int mainIdx = result.indexOf("void main");
assertTrue(mainIdx > 0, result);
assertTrue(result.indexOf("angelica_ModelViewMatrix") < mainIdx, "uniform before main\n\n" + result);
assertTrue(result.indexOf("a_position") < mainIdx, "pinned input before main\n\n" + result);
}
@Test
void testGlVertexShaderDoesNotPinCustomAttribute() {
String c = core("""
#version 120
attribute float activelights;
void main() {
gl_Position = ftransform();
float x = activelights;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertNoLoc(c, 0, "float activelights");
}
@ParameterizedTest // array on the declarator (vec4 a[2]) and on the type (vec4[2] a) must both round-trip
@ValueSource(strings = {"attribute vec4 positions[2];", "attribute vec4[2] positions;"})
void testArrayAttributePreservedEitherSyntax(String decl) {
String c = core("""
#version 120
%s
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""".formatted(decl));
assertLoc(c, 0, "vec4 positions[2]"); // 2-slot array pinned to 0..1
assertLoc(c, 2, "vec4 a_tail");
}
@Test
void testMultiDeclaratorSplitIntoSeparateLocations() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b;
void main() {
gl_Position = a + b;
}
""", false);
String c = compact(result);
assertLoc(c, 0, "vec4 a");
assertLoc(c, 1, "vec4 b");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultiDeclaratorWithMixedArray() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b[2];
void main() {
gl_Position = a + b[0] + b[1];
}
""", false);
String c = compact(result);
// b takes 2 slots; sorted before the 1-slot a, so b -> 0..1, a -> 2.
assertLoc(c, 0, "vec4 b[2]");
assertLoc(c, 2, "vec4 a");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
}
@Test
void testHexExplicitLocationReserved() {
String c = core("""
#version 120
layout(location = 0x1) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertTrue(c.contains("layout(location=0x1)invec4a_x"), "hex explicit location preserved\n\n" + c);
assertNoLoc(c, 1, "vec4 a_y"); // must not collide with the hex-reserved slot
assertLoc(c, 0, "vec4 a_y");
}
}
@@ -21,7 +21,7 @@ class CompatUniformManagerTest {
void cleanup() {
if (program != 0) {
GL20.glUseProgram(0);
GL20.glDeleteProgram(program);
GLStateManager.glDeleteProgram(program);
program = 0;
}
}
@@ -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");
}
}
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
@@ -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,105 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ExtendWith(GLSMCoreExtension.class)
class InstancedQuadDrawGLTest {
private static final String VS = "#version 330 core\nlayout(location=0) in vec2 aPos;\nvoid main(){ gl_Position = vec4(aPos, 0.0, 1.0); }\n";
private static final String FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
private int program;
private int vbo;
private int ebo;
@BeforeEach
void setup() {
program = buildProgram();
GLStateManager.glUseProgram(program);
final FloatBuffer verts = BufferUtils.createFloatBuffer(8);
verts.put(new float[] {0, 0, 1, 0, 1, 1, 0, 1}).flip();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, verts, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[] {0, 1, 2, 3}).flip();
ebo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, idx, GL15.GL_STATIC_DRAW);
drainErrors();
}
@AfterEach
void cleanup() {
GLStateManager.glUseProgram(0);
if (program != 0) GL20.glDeleteProgram(program);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (ebo != 0) GLStateManager.glDeleteBuffers(ebo);
drainErrors();
}
@Test
void drawArraysInstanced_quads_noInvalidEnum() {
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced array draw must be emulated, not rejected");
}
@Test
void drawArraysInstanced_triangles_fastPathClean() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "non-quad instanced array draw passes through cleanly");
}
@Test
void drawElementsInstanced_quads_noInvalidEnum() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElementsInstanced(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_INT, 0L, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced element draw must be emulated, not rejected");
}
private static int buildProgram() {
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, vs);
GL20.glAttachShader(p, fs);
GL20.glLinkProgram(p);
assertNotEquals(0, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), "test shader must link: " + GL20.glGetProgramInfoLog(p, 1024));
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return p;
}
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertNotEquals(0, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), "test shader must compile: " + GL20.glGetShaderInfoLog(s, 1024));
return s;
}
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) { /* clear */ }
}
}
@@ -28,6 +28,7 @@ class FFPCombineShaderTest {
private final List<Integer> shadersToDelete = new ArrayList<>();
private final List<Integer> programsToDelete = new ArrayList<>();
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
@@ -37,15 +38,25 @@ class FFPCombineShaderTest {
programsToDelete.clear();
shadersToDelete.clear();
// Reset texture units to defaults
// Reset texture units to defaults (unbind so a later test does not see a stale binding)
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.getTextures().getTexEnvState(i).reset();
if (i > 0) {
GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private int bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
return tex;
}
@Test
@@ -113,6 +124,7 @@ class FFPCombineShaderTest {
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -169,6 +181,7 @@ class FFPCombineShaderTest {
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -202,6 +215,7 @@ class FFPCombineShaderTest {
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -258,6 +272,7 @@ class FFPCombineShaderTest {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -273,6 +288,7 @@ class FFPCombineShaderTest {
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -286,6 +302,7 @@ class FFPCombineShaderTest {
// Unit 3: simple MODULATE
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
// Restore active unit
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class FFPUnboundTextureUnitTest {
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (i > 0) GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private void bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
}
@Test
void enabledUnitWithoutTextureIsExcluded() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertEquals(1, fk.nrEnabledUnits(), "units 2/3 have no texture bound -> excluded -> only unit 0");
assertEquals(0b0001, fk.enabledUnitMask(), "only unit 0 in the key; enabled-but-unbound units 2/3 excluded");
assertTrue(fk.unitEnabled(0), "unit 0 (texture bound) stays enabled");
}
@Test
void enabledUnitsWithTextureAreCounted() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertTrue(fk.unitEnabled(0));
assertTrue(fk.unitEnabled(2), "unit 2 with a bound texture must be counted");
assertEquals(3, fk.nrEnabledUnits(), "highest enabled is unit 2 -> nrEnabledUnits=3");
}
}
@@ -9,6 +9,9 @@ import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -17,7 +20,10 @@ import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -25,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
@@ -521,7 +528,153 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
"listBA: batch[1] must match the second-recorded layout");
}
// === local helpers ===
private static Stream<Arguments> clientIndexedDrawCases() {
return Stream.of(
arguments("byte indices", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3);
idx.put(new byte[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("short indices", GL11.GL_COMPILE, (Runnable) () -> {
final ShortBuffer idx = BufferUtils.createShortBuffer(3);
idx.put(new short[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("int indices", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("explicit count+type", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("quads triangulate at record", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_QUADS, idx);
}, GL11.GL_TRIANGLES, 6),
arguments("compile-and-execute fires live and bakes", GL11.GL_COMPILE_AND_EXECUTE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("clientIndexedDrawCases")
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount) {
final int list = recordClientIndexDraw(listMode, draw);
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(expectedMode, cmd.getDrawMode());
assertEquals(expectedCount, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "client indices always widen to UNSIGNED_INT");
}
@Test
void clientVerticesAndIndices_pointersSetDuringRecording_bake() {
final float[][] verts = {{0f, 0f, 0f}, {1f, 0f, 0f}, {1f, 1f, 0f}, {0f, 1f, 0f}};
final ByteBuffer clientVerts = BufferUtils.createByteBuffer(verts.length * 12).order(ByteOrder.nativeOrder());
for (float[] v : verts) {
clientVerts.putFloat(v[0]);
clientVerts.putFloat(v[1]);
clientVerts.putFloat(v[2]);
}
clientVerts.flip();
final IntBuffer indices = BufferUtils.createIntBuffer(6);
indices.put(new int[]{0, 1, 2, 2, 3, 0}).flip();
final int vao = newVao();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(GL11.GL_TRIANGLES, cmd.getDrawMode());
assertEquals(6, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType());
final ByteBuffer owned = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), verts.length * 12);
for (int i = 0; i < verts.length; i++) {
assertEquals(verts[i][0], owned.getFloat(i * 12), 0f, "pos.x v=" + i);
assertEquals(verts[i][1], owned.getFloat(i * 12 + 4), 0f, "pos.y v=" + i);
assertEquals(verts[i][2], owned.getFloat(i * 12 + 8), 0f, "pos.z v=" + i);
}
MemoryUtilities.memFree(owned);
}
@Test
void quadOffsetDraw_compileAndExecute_doesNotPauseRecordingForRestOfList() {
final int vao = setupPosColorVao(defaultQuadVbo());
final int quadEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int triEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, quadEbo);
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
assertTrue(DisplayListManager.isRecording(), "recording must resume after a GL_QUADS offset draw");
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, triEbo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertEquals(2, allBatchedCmds(list).size(), "both the QUADS draw and the following triangle draw must be recorded");
}
private enum MalformedClientDraw { OVERSIZED_COUNT, INDEX_PAST_VERTEX_BUFFER }
@ParameterizedTest(name = "{0}")
@EnumSource(MalformedClientDraw.class)
void malformedClientDraw_skippedAndRecordingSurvives(MalformedClientDraw scenario) {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
switch (scenario) {
case OVERSIZED_COUNT -> {
GLStateManager.glBindVertexArray(setupPosColorVao(defaultQuadVbo()));
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 100, GL11.GL_UNSIGNED_SHORT, idx);
}
case INDEX_PAST_VERTEX_BUFFER -> {
final int vbo = uploadPosColorVbo(new float[]{0f, 0f, 1f, 0f}, new byte[][]{{1, 1, 1, 1}, {2, 2, 2, 2}});
GLStateManager.glBindVertexArray(setupPosColorVao(vbo));
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}
}
assertTrue(DisplayListManager.isRecording(), "recording survives a skipped malformed draw");
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertNull(firstBatchedCmd(list), "malformed draw must be skipped, not captured or over-read");
}
private int recordClientIndexDraw(int listMode, Runnable draw) {
final int vao = setupPosColorVao(defaultQuadVbo());
final int list = newList();
GLStateManager.glNewList(list, listMode);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
draw.run();
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
return list;
}
private static void assertUintIndices(ByteBuffer ib, int... expected) {
for (int i = 0; i < expected.length; i++) {
+4
View File
@@ -34,6 +34,10 @@ repositories {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Minecraft Libraries"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
@@ -348,6 +348,16 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL31C.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
GL31C.glDrawArraysInstanced(mode, first, count, primcount);
}
@Override
public void provokingVertex(int provokeMode) {
GL32C.glProvokingVertex(provokeMode);
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GL43C.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
@@ -856,6 +866,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20C.glUniform2fv(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20C.glUniform3fv(location, value);
@@ -866,6 +881,26 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4fv(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20C.glUniform1iv(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20C.glUniform2iv(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20C.glUniform3iv(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20C.glUniform4iv(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20C.glUniformMatrix3fv(location, transpose, value);
+1 -1
View File
@@ -16,7 +16,7 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.25")
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.26")
}
include("glsm")
@@ -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,);\
@@ -25,6 +25,7 @@ public class DeferredDrawBatcher {
@Getter private static boolean active = false;
private static DeferredBatchTessellator batchTessellator;
private static long entryStateKey;
private DeferredDrawBatcher() {}
@@ -41,13 +42,14 @@ public class DeferredDrawBatcher {
batchTessellator.clearRanges();
batchTessellator.setParentTessellator(Tessellator.instance);
batchTessellator.snapshotDefaultModelview();
entryStateKey = captureStateKey();
TessellatorManager.startCapturingDirect(batchTessellator);
}
/**
* Exit deferred mode and flush all captured batches. Groups entries by state key
* and vertex format, issues one draw per unique group. Restores the GL state that
* was active for the last group so vanilla sees consistent state.
* and vertex format, issues one draw per unique group. Restores the bracket's entry
* state so the trailing vanilla Tessellator.draw() renders with the layer atlas.
*/
public static void exitAndFlush() {
active = false;
@@ -56,6 +58,7 @@ public class DeferredDrawBatcher {
if (!ranges.isEmpty()) {
ranges.sort(Comparator.comparingLong(DeferredBatchTessellator.DrawRange::stateKey));
flushSorted(ranges);
applyStateKey(entryStateKey);
}
TessellatorManager.stopCapturingDirect();
@@ -147,7 +150,8 @@ public class DeferredDrawBatcher {
* - fog state (mode, color, start/end — constant for the frame)
*/
static long captureStateKey() {
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(0).getBinding();
final int activeUnit = GLStateManager.getActiveTextureUnit();
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(activeUnit).getBinding();
final int srcRgb = GLStateManager.getBlendState().getSrcRgb();
final int dstRgb = GLStateManager.getBlendState().getDstRgb();
final boolean blendEnabled = GLStateManager.getBlendMode().isEnabled();
@@ -30,6 +30,7 @@ public class ModStatus {
public static boolean isCosmeticArmorReworkedLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isBOPLoaded;
public static boolean isEtFuturumLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
@@ -49,6 +50,7 @@ public class ModStatus {
isBaublesLoaded = Loader.isModLoaded("Baubles");
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isEtFuturumLoaded = Loader.isModLoaded("etfuturum");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBOPLoaded = Loader.isModLoaded("BiomesOPlenty");
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.compat.etfuturum;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.relauncher.ReflectionHelper;
import lombok.Getter;
import net.minecraft.entity.player.EntityPlayer;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public final class EtFuturumCompat {
private static final String IELYTRA_PLAYER = "ganymedes01.etfuturum.api.elytra.IElytraPlayer";
@Getter
private static boolean loaded;
private static Class<?> elytraPlayerClass;
private static final MethodHandle isElytraFlyingHandle;
static {
MethodHandle handle = null;
try {
if (Loader.isModLoaded("etfuturum")) {
elytraPlayerClass = ReflectionHelper.getClass(EtFuturumCompat.class.getClassLoader(), IELYTRA_PLAYER);
handle = MethodHandles.lookup().findVirtual(elytraPlayerClass, "etfu$isElytraFlying",
MethodType.methodType(boolean.class));
loaded = true;
}
} catch (Throwable t) {
LOGGER.error("Failed to load Et Futurum Requiem elytra compat", t);
loaded = false;
}
isElytraFlyingHandle = handle;
}
private EtFuturumCompat() {
}
public static boolean isElytraFlying(EntityPlayer player) {
if (!loaded || isElytraFlyingHandle == null || !elytraPlayerClass.isInstance(player)) {
return false;
}
try {
return (boolean) isElytraFlyingHandle.invoke(player);
} catch (Throwable t) {
return false;
}
}
}
@@ -269,6 +269,13 @@ public class AngelicaConfig {
@Config.DefaultStringList({"jewelrycraft2:blockCrystal"})
public static String[] alwaysTranslucentSprites;
@Config.Comment({"TileEntity classnames whose render bounds change at runtime (e.g. OpenBlocks Guide).",
"These are always rendered and frustum-tested against their live bounds instead of the cached."})
@Config.DefaultStringList({"openblocks.common.tileentity.TileEntityGuide",
"openblocks.common.tileentity.TileEntityBuilderGuide"})
@Config.RequiresMcRestart
public static String[] dynamicBoundsTileEntities;
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
@@ -350,4 +357,13 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean disableErrorChecks;
@Config.Comment("Fixes various issues with entity overlays, such as z-fighting and eyes.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean entityOverlayFixes;
@Config.Comment("Swap the vanilla damage overlay with one similar to modern. Fixes specific issues with z-fighting.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean entityModernDamageOverlay;
}
@@ -0,0 +1,36 @@
package com.gtnewhorizons.angelica.event;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public class ClientEvent extends MutableEvent {
public static final EventBus<ClientEvent> BUS = EventBus.create(ClientEvent.class);
public final ClientEventType event;
public final EntityRenderer renderer;
public final float partialTicks;
private ClientEvent(ClientEventType event) {
this.event = event;
this.renderer = null;
this.partialTicks = 0;
}
private ClientEvent(ClientEventType event, EntityRenderer renderer, float partialTicks) {
this.event = event;
this.renderer = renderer;
this.partialTicks = partialTicks;
}
public static void post(ClientEventType event, EntityRenderer renderer, float partialTicks) {
if (!BUS.hasListeners()) return;
BUS.post(new ClientEvent(event, renderer, partialTicks));
}
public static void post(ClientEventType event) {
if (!BUS.hasListeners()) return;
BUS.post(new ClientEvent(event));
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.event;
public enum ClientEventType {
PRE_RENDER_GUI,
PRE_RENDER_HUD,
POST_RENDER_HUD,
ON_TICK,
}
@@ -6,11 +6,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.CustomFramebuffer
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.SharedDepthFramebuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.holoinventory.HoloInventoryReflectionCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.event.ClientEvent;
import com.gtnewhorizons.angelica.event.ClientEventType;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.GuiIngameAccessor;
@@ -65,6 +66,8 @@ public class HUDCaching {
}
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
if (ModStatus.isXaerosMinimapLoaded && ingame instanceof GuiIngameForge) {
// this used to be called by asming into renderGameOverlay, but we removed it
XaeroMinimapCore.beforeIngameGuiRender(partialTicks);
@@ -78,6 +81,7 @@ public class HUDCaching {
if (!AngelicaConfig.hudCachingActive || mc.displayWidth < 16 || mc.displayHeight < 16) {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
ClientEvent.post(ClientEventType.POST_RENDER_HUD, renderer, partialTicks);
return;
}
@@ -170,6 +174,8 @@ public class HUDCaching {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
mc.getTextureManager().bindTexture(Gui.icons);
ClientEvent.post(ClientEventType.POST_RENDER_HUD, renderer, partialTicks);
}
/**
@@ -120,7 +120,10 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib")
));
} catch (RuntimeException ex) {
fatalDependencyError(ex.getMessage());
@@ -47,9 +47,7 @@ public enum Mixins implements IMixins {
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
, "angelica.bugfixes.MixinRenderMooshroom_MushroomTint"
, "angelica.bugfixes.MixinRendererLivingEntity_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
, "angelica.bugfixes.MixinRenderWither_ArmorCentering"
, "angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
, "angelica.debug.MixinMinecraft_FPSCap"
, "angelica.ffp.MixinTessellator_CoreProfile"
, "angelica.glsm.MixinSplashProgressCaching"
@@ -60,6 +58,24 @@ public enum Mixins implements IMixins {
)
),
ANGELICA_ENTITY_OVERLAYS(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.entityOverlayFixes)
.addExcludedMod(TargetedMod.CUSTOM_PLAYER_MODELS)
.addClientMixins(
"angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
)
),
ANGELICA_DAMAGE_OVERLAY(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.entityModernDamageOverlay)
.addExcludedMod(TargetedMod.CUSTOM_PLAYER_MODELS)
.addClientMixins(
"angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
)
),
ANGELICA_VBO_CLOUDS(
new MixinBuilder()
.setApplyIf(() -> AngelicaConfig.enableVBOClouds)
@@ -282,6 +298,7 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderEndPortal"
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
, "shaders.MixinTextureManager_ReloadCount"
)
),
@@ -28,6 +28,7 @@ public enum TargetedMod implements ITargetMod {
TINKERS_CONSTRUCT(null, "TConstruct"),
TWILIGHT_FOREST(null, "TwilightForest"),
BIOMES_O_PLENTY(null, "BiomesOPlenty"),
CUSTOM_PLAYER_MODELS("com.tom.cpmcore.CPMLoadingPlugin", "customplayermodels"),
WITCHERY(null, "witchery");
private final TargetModBuilder builder;
@@ -5,5 +5,5 @@ import net.minecraft.util.AxisAlignedBB;
public interface ITileEntityBoundingBoxCache {
AxisAlignedBB angelica$getCachedRenderBoundingBox();
boolean angelica$isInfiniteExtent();
byte angelica$boundsClass();
}
@@ -9,6 +9,7 @@ import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.debug.F3Direction;
@@ -102,6 +103,7 @@ public final class ClientProxy extends CommonProxy {
@Override
public void preInit(FMLPreInitializationEvent event) {
ModStatus.preInit();
for (String name : AngelicaConfig.dynamicBoundsTileEntities) TileEntityRenderBoundsRegistry.registerDynamicClass(name);
super.preInit(event);
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
@@ -4,48 +4,60 @@ import it.unimi.dsi.fastutil.objects.Reference2ByteOpenHashMap;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Registry for tracking which TileEntity classes always return INFINITE_EXTENT_AABB.
* Registry for tracking which TileEntity classes always return INFINITE_EXTENT_AABB or are known to change at runtime
*/
public final class TileEntityRenderBoundsRegistry {
private static final byte UNKNOWN = 0;
private static final byte FINITE = 1;
private static final byte INFINITE = 2;
public static final byte STATIC = 1;
public static final byte INFINITE = 2;
public static final byte DYNAMIC = 3;
private static final Reference2ByteOpenHashMap<Class<? extends TileEntity>> classRegistry = new Reference2ByteOpenHashMap<>();
private static final Set<String> dynamicClassNames = ConcurrentHashMap.newKeySet();
static {
classRegistry.defaultReturnValue(UNKNOWN);
}
private TileEntityRenderBoundsRegistry() {}
public static void registerDynamicClass(String className) {
if (className != null && !className.isEmpty()) dynamicClassNames.add(className);
}
public static boolean isInfiniteExtentsBox(AxisAlignedBB box) {
return box == null || Double.isInfinite(box.minX) || Double.isInfinite(box.minY) || Double.isInfinite(box.minZ) || Double.isInfinite(box.maxX) || Double.isInfinite(box.maxY) || Double.isInfinite(box.maxZ);
}
public static boolean isAlwaysInfiniteExtent(TileEntity te) {
public static byte classify(TileEntity te) {
final Class<? extends TileEntity> clazz = te.getClass();
synchronized (classRegistry) {
final byte result = classRegistry.getByte(clazz);
if (result != UNKNOWN) return result == INFINITE;
final byte cached = classRegistry.getByte(clazz);
if (cached != UNKNOWN) return cached;
}
return probeAndCache(te, clazz);
}
private static boolean probeAndCache(TileEntity te, Class<? extends TileEntity> clazz) {
boolean isInfinite;
try {
final AxisAlignedBB aabb = te.getRenderBoundingBox();
isInfinite = isInfiniteExtentsBox(aabb);
} catch (Throwable t) {
isInfinite = true;
final byte result;
if (dynamicClassNames.contains(clazz.getName())) {
result = DYNAMIC;
} else {
boolean isInfinite;
try {
isInfinite = isInfiniteExtentsBox(te.getRenderBoundingBox());
} catch (Throwable t) {
isInfinite = true;
}
result = isInfinite ? INFINITE : STATIC;
}
synchronized (classRegistry) {
classRegistry.put(clazz, isInfinite ? INFINITE : FINITE);
classRegistry.put(clazz, result);
}
return isInfinite;
return result;
}
public static void clear() {
@@ -151,7 +151,8 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final TileEntity tileEntity = region.getTileEntity(x, y, z);
if (tileEntity != null && TileEntityRendererDispatcher.instance.hasSpecialRenderer(tileEntity)) {
final boolean isGlobal;
if (TileEntityRenderBoundsRegistry.isAlwaysInfiniteExtent(tileEntity)) {
final byte boundsClass = TileEntityRenderBoundsRegistry.classify(tileEntity);
if (boundsClass == TileEntityRenderBoundsRegistry.INFINITE || boundsClass == TileEntityRenderBoundsRegistry.DYNAMIC) {
isGlobal = true;
} else {
AxisAlignedBB aabb;
@@ -346,8 +346,9 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
final ITileEntityBoundingBoxCache teCache = (ITileEntityBoundingBoxCache) tileEntity;
if (!teCache.angelica$isInfiniteExtent()) {
final AxisAlignedBB aabb = teCache.angelica$getCachedRenderBoundingBox();
final byte boundsClass = teCache.angelica$boundsClass();
if (boundsClass != TileEntityRenderBoundsRegistry.INFINITE) {
final AxisAlignedBB aabb = boundsClass == TileEntityRenderBoundsRegistry.DYNAMIC ? tileEntity.getRenderBoundingBox() : teCache.angelica$getCachedRenderBoundingBox();
if (aabb != null && !this.currentViewport.isBoxVisible(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ)) {
return;
}
@@ -0,0 +1,166 @@
package com.gtnewhorizons.angelica.shadercompat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Maybe overcomplicated, but it's required to bridge the gap between the modern glint texture and white texture 1.7.10
* has. Some shaders are able to render the glint just fine as they seem to have some amount of support for older
* Minecraft, but others like AstraLex do *not* render correctly. It expects the color to be within the texture.
* This might introduce some oddities that I'm not seeing, but for now this appears to fix the issue.
*/
public final class ShaderGlint {
private static final ResourceLocation GLINT_MASK = new ResourceLocation("textures/misc/enchanted_item_glint.png");
private static final int MAX_CACHE = 64;
private static boolean maskLoaded;
private static boolean maskFailed;
private static int maskW;
private static int maskH;
private static int[] maskLum;
private static final Int2IntLinkedOpenHashMap colorToTexture = new Int2IntLinkedOpenHashMap();
static {
colorToTexture.defaultReturnValue(0);
}
private static final GlintColorHandler COLOR_HANDLER = ShaderGlint::onColorChanged;
private static boolean injecting;
private static boolean swapped;
private static int depth;
private static int prevTexture;
private static ByteBuffer tintBuffer;
private ShaderGlint() {}
public static void beginGlint() {
if (ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
// If a glint span is already active, keep the outer span's saved state and
// let the nested render ride on its handler.
if (depth++ > 0) return;
swapped = false;
injecting = false;
// Save the texture bound before the glint section so endGlint can put it back if we swap
prevTexture = GLStateManager.getBoundTextureForServerState(0);
// Observe vertex-color changes for the rest of the glint section
GLSMHooks.glintColorHandler = COLOR_HANDLER;
}
public static void endGlint() {
if (depth == 0 || --depth > 0) return;
if (GLSMHooks.glintColorHandler != COLOR_HANDLER) return;
GLSMHooks.glintColorHandler = null;
if (swapped) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prevTexture);
}
swapped = false;
injecting = false;
}
private static void onColorChanged(float red, float green, float blue, float alpha) {
if (injecting) return;
final int texture = getTintedTexture(red, green, blue);
if (texture <= 0) return;
injecting = true;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, alpha);
injecting = false;
swapped = true;
}
private static int getTintedTexture(float r, float g, float b) {
ensureMaskLoaded();
if (!maskLoaded) return -1;
final int ri = clamp8(r);
final int gi = clamp8(g);
final int bi = clamp8(b);
final int key = (ri << 16) | (gi << 8) | bi;
final int cached = colorToTexture.getAndMoveToLast(key);
if (cached != 0) return cached;
if (colorToTexture.size() >= MAX_CACHE) {
GLStateManager.glDeleteTextures(colorToTexture.remove(colorToTexture.firstIntKey()));
}
final int texture = uploadTinted(ri / 255.0F, gi / 255.0F, bi / 255.0F);
colorToTexture.putAndMoveToLast(key, texture);
return texture;
}
private static int uploadTinted(float r, float g, float b) {
final ByteBuffer pixels = tintBuffer;
pixels.clear();
for (final int l : maskLum) {
pixels.put((byte) Math.round(l * r));
pixels.put((byte) Math.round(l * g));
pixels.put((byte) Math.round(l * b));
pixels.put((byte) 0xFF);
}
pixels.flip();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
// Match the vanilla glint texture
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, maskW, maskH, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return texture;
}
private static void ensureMaskLoaded() {
if (maskLoaded || maskFailed) return;
try (final InputStream in = Minecraft.getMinecraft().getResourceManager().getResource(GLINT_MASK).getInputStream()) {
final BufferedImage img = ImageIO.read(in);
maskW = img.getWidth();
maskH = img.getHeight();
maskLum = new int[maskW * maskH];
final int[] argb = img.getRGB(0, 0, maskW, maskH, null, 0, maskW);
for (int i = 0; i < argb.length; i++) {
final int p = argb[i];
final int rr = (p >> 16) & 0xFF;
final int gg = (p >> 8) & 0xFF;
final int bb = p & 0xFF;
maskLum[i] = (rr + gg + bb) / 3;
}
tintBuffer = BufferUtils.createByteBuffer(maskW * maskH * 4);
maskLoaded = true;
} catch (final Exception e) {
maskFailed = true;
}
}
private static int clamp8(float v) {
final int i = Math.round(v * 255.0F);
if (i < 0) return 0;
return Math.min(i, 255);
}
}
@@ -4,6 +4,8 @@ import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import jss.notfine.core.Settings;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
@@ -19,6 +21,13 @@ public class Zoom {
@Getter
private static float zoom = ZOOM_DEFAULT;
public static final float ZOOM_SMOOTH_SNAP_EPSILON = 0.001F;
@Getter
private static float currentZoom = 1.0F;
private static long zoomLerpLastNano = 0L;
@Getter
private static final KeyBinding zoomKey = new KeyBinding("Zoom", 0, "key.categories.misc");
@@ -38,6 +47,29 @@ public class Zoom {
zoom = MathHelper.clamp_float((float) (zoom * Math.pow(ZOOM_STEP, Integer.signum(eventDWheel))), ZOOM_MIN, ZOOM_MAX);
}
public static void updateZoomLerp() {
final long now = System.nanoTime();
float dt = zoomLerpLastNano == 0L ? 0.0F : (now - zoomLerpLastNano) / 1.0e9F;
zoomLerpLastNano = now;
final float target = zoomEnabled ? zoom : 1.0F;
if (dt <= 0.0F || !(boolean) Settings.ZOOM_SMOOTH.option.getStore()) {
currentZoom = target;
return;
}
// Clamp dt so a long frame (alt-tab, GC pause) does not snap the zoom.
dt = Math.min(dt, 0.1F);
final int rate = (int) Settings.ZOOM_SMOOTH_SPEED.option.getStore();
currentZoom += (target - currentZoom) * (1.0F - (float) Math.exp(-dt * rate));
if (Math.abs(target - currentZoom) < ZOOM_SMOOTH_SNAP_EPSILON) {
currentZoom = target;
}
}
private static void resetMouseFilters(Minecraft mc) {
if (mc.entityRenderer != null) {
((IMouseFilterExt) mc.entityRenderer.mouseFilterXAxis).angelica$reset();
@@ -79,4 +111,10 @@ public class Zoom {
handleKeyEvent();
}
}
@SubscribeEvent
public void onRenderTick(TickEvent.RenderTickEvent e) {
if (e.phase != TickEvent.Phase.START) return;
updateZoomLerp();
}
}
@@ -0,0 +1,99 @@
package com.prupe.mcpatcher.ctm;
import com.prupe.mcpatcher.ctm.TileOverrideImpl.CTMCompactExpanded;
import com.prupe.mcpatcher.mal.tile.TileLoader;
import net.minecraft.util.ResourceLocation;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
public class CTMTextureGenerator {
public static void generateTextures(BufferedImage[] compactIcons, CTMCompactExpanded override,
ResourceLocation propsLoc, ResourceLocation blankResource){
BufferedImage cFull = compactIcons[0]; // Full
BufferedImage cNone = compactIcons[1]; // None
BufferedImage cVert = compactIcons[2]; // Vertical
BufferedImage cHori = compactIcons[3]; // Horizontal
BufferedImage cCorn = compactIcons[4]; // Corner
BufferedImage[] expanded = new BufferedImage[47];
// Generate all 47 textures to match normal CTM
expanded[0] = cFull;
expanded[1] = generate(cFull, cHori, cFull, cHori);
expanded[2] = cHori;
expanded[3] = generate(cHori, cFull, cHori, cFull);
expanded[4] = generate(cFull, cHori, cVert, cCorn);
expanded[5] = generate(cHori, cFull, cCorn, cVert);
expanded[6] = generate(cVert, cCorn, cVert, cCorn);
expanded[7] = generate(cHori, cHori, cCorn, cCorn);
expanded[8] = generate(cCorn, cNone, cCorn, cCorn);
expanded[9] = generate(cCorn, cCorn, cCorn, cNone);
expanded[10] = generate(cNone, cCorn, cNone, cCorn);
expanded[11] = generate(cNone, cNone, cCorn, cCorn);
expanded[12] = generate(cFull, cFull, cVert, cVert);
expanded[13] = generate(cFull, cHori, cVert, cNone);
expanded[14] = generate(cHori, cHori, cNone, cNone);
expanded[15] = generate(cHori, cFull, cNone, cVert);
expanded[16] = generate(cVert, cCorn, cFull, cHori);
expanded[17] = generate(cCorn, cVert, cHori, cFull);
expanded[18] = generate(cCorn, cCorn, cHori, cHori);
expanded[19] = generate(cCorn, cVert, cCorn, cVert);
expanded[20] = generate(cNone, cCorn, cCorn, cCorn);
expanded[21] = generate(cCorn, cCorn, cNone, cCorn);
expanded[22] = generate(cCorn, cCorn, cNone, cNone);
expanded[23] = generate(cCorn, cNone, cCorn, cNone);
expanded[24] = cVert;
expanded[25] = generate(cVert, cNone, cVert, cNone);
expanded[26] = cNone;
expanded[27] = generate(cNone, cVert, cNone, cVert);
expanded[28] = generate(cVert, cCorn, cVert, cNone);
expanded[29] = generate(cHori, cHori, cNone, cCorn);
expanded[30] = generate(cVert, cNone, cVert, cCorn);
expanded[31] = generate(cHori, cHori, cCorn, cNone);
expanded[32] = generate(cNone, cNone, cNone, cCorn);
expanded[33] = generate(cNone, cNone, cCorn, cNone);
expanded[34] = generate(cCorn, cNone, cNone, cCorn);
expanded[35] = generate(cNone, cCorn, cCorn, cNone);
expanded[36] = generate(cVert, cVert, cFull, cFull);
expanded[37] = generate(cVert, cNone, cFull, cHori);
expanded[38] = generate(cNone, cNone, cHori, cHori);
expanded[39] = generate(cNone, cVert, cHori, cFull);
expanded[40] = generate(cCorn, cNone, cHori, cHori);
expanded[41] = generate(cNone, cVert, cNone, cVert);
expanded[42] = generate(cNone, cCorn, cHori, cHori);
expanded[43] = generate(cCorn, cVert, cNone, cVert);
expanded[44] = generate(cNone, cCorn, cNone, cNone);
expanded[45] = generate(cCorn, cNone, cNone, cNone);
expanded[46] = cCorn;
for (int i = 0; i < expanded.length; i++){
ResourceLocation resource =
TileLoader.parseTileAddress(propsLoc, String.valueOf(i), blankResource);
override.addIcon(resource, expanded[i]);
}
}
private static BufferedImage generate(BufferedImage tl, BufferedImage tr, BufferedImage bl, BufferedImage br){
int w = tl.getWidth();
int hw = w/2;
BufferedImage img = new BufferedImage(tl.getWidth(), tl.getHeight(), BufferedImage.TYPE_INT_ARGB);
int numFrames = tl.getHeight() / tl.getWidth();
Graphics2D g = img.createGraphics();
for (int i = 0; i < numFrames; i++){
int yOff = w * i;
copy(tl, g, 0, yOff, hw);
copy(tr, g, hw, yOff, hw);
copy(bl, g, 0, yOff + hw, hw);
copy(br, g, hw, yOff + hw, hw);
}
g.dispose();
return img;
}
private static void copy(BufferedImage src, Graphics2D g, int x, int y, int s){
g.drawImage(src, x, y, x+s, y+s, x, y, x+s, y+s, null);
}
}
@@ -10,12 +10,12 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import com.github.bsideup.jabel.Desugar;
import net.minecraft.block.Block;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import com.github.bsideup.jabel.Desugar;
import com.prupe.mcpatcher.MCLogger;
import com.prupe.mcpatcher.MCPatcherUtils;
import com.prupe.mcpatcher.mal.block.BlockAPI;
@@ -32,7 +32,7 @@ import jss.notfine.config.MCPatcherForgeConfig;
public class CTMUtils {
private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
public static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
private static class Overrides {
private final List<TileOverride> all = new ArrayList<>();
@@ -0,0 +1,77 @@
package com.prupe.mcpatcher.ctm;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.prupe.mcpatcher.mal.tile.TileLoader;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import jss.notfine.render.InterpolatedIcon;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.SimpleResource;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import net.minecraft.util.ResourceLocation;
import java.awt.image.BufferedImage;
@SideOnly(Side.CLIENT)
public class GeneratedCTMAtlasSprite extends InterpolatedIcon {
private final TileLoader loader;
private final boolean aniFiltering;
private boolean interpolated;
public GeneratedCTMAtlasSprite(TileLoader loader, boolean aniFiltering, String name) {
super(name);
this.loader = loader;
this.aniFiltering = aniFiltering;
}
@Override
protected void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException {
if(interpolated) super.updateAnimationInterpolated();
}
@Override
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) {
return true;
}
@Override
public boolean load(IResourceManager manager, ResourceLocation location) {
BufferedImage img = loader.getImageForPath(location);
if(img == null) return true;
try {
ResourceLocation metaLoc = getMetaLoc(location);
IResource iresource = manager.getResource(metaLoc);
AnimationMetadataSection anim = (AnimationMetadataSection)iresource.getMetadata("animation");
interpolated = anim != null && AngelicaConfig.enableNotFineFeatures && shouldInterpolate(iresource);
loadSprite(new BufferedImage[]{img, null, null, null, null}, anim, aniFiltering);
}catch (Exception exception){
CTMUtils.logger.error("Error loading sprite for " + location);
exception.printStackTrace();
return true;
}
return false;
}
private static boolean shouldInterpolate(IResource resource){
try {
if (resource instanceof SimpleResource simple) {
return simple.mcmetaJson.getAsJsonObject("animation")
.getAsJsonPrimitive("interpolate").getAsBoolean();
}
}catch (Exception _){}
return false;
}
/**
* Find the mcmeta file associated with 0.png
*/
private static ResourceLocation getMetaLoc(ResourceLocation location) {
String path = location.getResourcePath();
int index = path.lastIndexOf('/');
if(index == -1) return new ResourceLocation(location.getResourceDomain(), "0.png");
path = path.substring(0, index) + "/0.png";
return new ResourceLocation(location.getResourceDomain(), path);
}
}
@@ -5,6 +5,7 @@ import static com.prupe.mcpatcher.ctm.RenderBlockState.CONNECT_BY_MATERIAL;
import static com.prupe.mcpatcher.ctm.RenderBlockState.CONNECT_BY_TILE;
import static com.prupe.mcpatcher.ctm.RenderBlockState.NORMALS;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
@@ -32,10 +33,10 @@ public abstract class TileOverride implements Comparable<TileOverride> {
private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
private final PropertiesFile properties;
protected final PropertiesFile properties;
private final String baseFilename;
private final TileLoader tileLoader;
private final int renderPass;
protected final TileLoader tileLoader;
protected final int renderPass;
private final int weight;
private final List<BlockStateMatcher> matchBlocks;
private final Set<String> matchTiles;
@@ -45,7 +46,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
private final BitSet biomes;
private final BitSet height;
private final List<ResourceLocation> tileNames = new ArrayList<>();
protected final List<ResourceLocation> tileNames = new ArrayList<>();
protected IIcon[] icons;
// Index into this array is formed from these bit values:
@@ -103,6 +104,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
case "sandstone", "top" -> override = new TileOverrideImpl.Top(properties, tileLoader);
case "repeat", "pattern" -> override = new TileOverrideImpl.Repeat(properties, tileLoader);
case "ctm_compact", "compact" -> override = new TileOverrideImpl.CTMCompact(properties, tileLoader);
case "compact_expanded" -> override = new TileOverrideImpl.CTMCompactExpanded(properties, tileLoader);
default -> properties.error("unknown method \"%s\"", method);
}
@@ -196,7 +198,12 @@ public abstract class TileOverride implements Comparable<TileOverride> {
return tileLoader.preloadTile(resource, renderPass > RenderPassAPI.MAX_BASE_RENDER_PASS);
}
private void loadIcons() {
public void addIcon(ResourceLocation resource, BufferedImage img){
tileNames.add(resource);
tileLoader.addTile(resource, img, null, true);
}
protected void loadIcons() {
tileNames.clear();
String tileList = properties.getString("tiles", "");
ResourceLocation blankResource = RenderPassAPI.instance.getBlankResource(renderPass);
@@ -221,16 +228,7 @@ public abstract class TileOverride implements Comparable<TileOverride> {
try {
int from = Integer.parseInt(matcher.group(1));
int to = Integer.parseInt(matcher.group(2));
for (int i = from; i <= to; i++) {
ResourceLocation resource = TileLoader
.parseTileAddress(properties.getResource(), String.valueOf(i), blankResource);
if (TexturePackAPI.hasResource(resource)) {
addIcon(resource);
} else {
properties.warning("could not find image %s", resource);
tileNames.add(null);
}
}
loadOverrideIcons(from, to, blankResource);
} catch (NumberFormatException e) {
e.printStackTrace();
}
@@ -250,6 +248,19 @@ public abstract class TileOverride implements Comparable<TileOverride> {
}
}
protected void loadOverrideIcons(int from, int to, ResourceLocation blankResource){
for (int i = from; i <= to; i++) {
ResourceLocation resource = TileLoader
.parseTileAddress(properties.getResource(), String.valueOf(i), blankResource);
if (TexturePackAPI.hasResource(resource)) {
addIcon(resource);
} else {
properties.warning("could not find image %s", resource);
tileNames.add(null);
}
}
}
private List<BlockStateMatcher> getBlockList(String property, String defaultMetadata) {
List<BlockStateMatcher> blocks = new ArrayList<>();
if (!MCPatcherUtils.isNullOrEmpty(defaultMetadata)) {
@@ -12,15 +12,22 @@ import static com.prupe.mcpatcher.ctm.RenderBlockState.REL_UL;
import static com.prupe.mcpatcher.ctm.RenderBlockState.REL_UR;
import static com.prupe.mcpatcher.ctm.RenderBlockState.TOP_FACE;
import com.prupe.mcpatcher.mal.block.RenderPassAPI;
import com.prupe.mcpatcher.mal.resource.TexturePackAPI;
import net.minecraft.util.IIcon;
import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import com.prupe.mcpatcher.mal.tile.TileLoader;
import com.prupe.mcpatcher.mal.util.WeightedIndex;
import net.minecraft.util.ResourceLocation;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class TileOverrideImpl {
final static class CTM extends TileOverride {
static class CTM extends TileOverride {
CTM(PropertiesFile properties, TileLoader tileLoader) {
super(properties, tileLoader);
@@ -505,4 +512,48 @@ public class TileOverrideImpl {
return p;
}
}
final public static class CTMCompactExpanded extends CTM {
CTMCompactExpanded(PropertiesFile propertiesFile, TileLoader tileLoader) {
super(propertiesFile, tileLoader);
}
@Override
String checkTileMap() {
return null;
}
@Override
String getMethod() {
return "compact_expanded";
}
@Override
protected void loadOverrideIcons(int from, int to, ResourceLocation blankResource) {
List<BufferedImage> compactIcons = new ArrayList<>();
if(from != 0){
properties.error("expanded compact CTM requires range to start at 0, starts at %d", from);
}
if(to - from + 1 != 5){
properties.error("expanded compact CTM requires exactly 5 icons, got range %d - %d", from, to);
}
for (int i = from; i <= to; i++) {
ResourceLocation resource = TileLoader
.parseTileAddress(properties.getResource(), String.valueOf(i), blankResource);
if (TexturePackAPI.hasResource(resource)) {
compactIcons.add(tileLoader.loadResourceImage(resource,
renderPass > RenderPassAPI.MAX_BASE_RENDER_PASS));
} else {
// Promote from warning to error for compact CTM
properties.error("could not find image %s, required for expanded compact CTM", resource);
tileNames.add(null);
}
}
if(compactIcons.size() == 5){
CTMTextureGenerator.generateTextures(compactIcons.toArray(new BufferedImage[0]),
this, properties.getResource(), blankResource);
}
}
}
}
@@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.prupe.mcpatcher.ctm.GeneratedCTMAtlasSprite;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureMap;
@@ -44,6 +45,7 @@ public class TileLoader {
private TextureMap baseTextureMap;
private final Map<String, TextureAtlasSprite> baseTexturesByName = new HashMap<>();
private final Set<ResourceLocation> tilesToRegister = new HashSet<>();
private final Set<ResourceLocation> generatedTiles = new HashSet<>();
private final Map<ResourceLocation, BufferedImage> tileImages = new HashMap<>();
private final Map<String, IIcon> iconMap = new HashMap<>();
@@ -119,6 +121,10 @@ public class TileLoader {
logger.fine("after registerIcons(%s) %d icons", mapName, map.size());
}
public BufferedImage getImageForPath(ResourceLocation rl) {
return tileImages.get(rl);
}
public static String getOverridePath(String prefix, String basePath, String name, String ext) {
String path;
if (name.endsWith(".png")) {
@@ -218,6 +224,22 @@ public class TileLoader {
if (tileImages.containsKey(resource)) {
return true;
}
return addTile(resource, loadResourceImage(resource, alternate), special, false);
}
public boolean addTile(ResourceLocation resource, BufferedImage image, String special, boolean generated) {
tilesToRegister.add(resource);
tileImages.put(resource, image);
if(generated){
generatedTiles.add(resource);
}
if (special != null) {
specialTextures.put(resource.toString(), special);
}
return true;
}
public BufferedImage loadResourceImage(ResourceLocation resource, boolean alternate){
BufferedImage image = null;
if (!MCPatcherForgeConfig.ConnectedTextures.debugTextures) {
image = TexturePackAPI.getImage(resource);
@@ -228,12 +250,7 @@ public class TileLoader {
if (image == null) {
image = generateDebugTexture(resource.getResourcePath(), 64, 64, alternate);
}
tilesToRegister.add(resource);
tileImages.put(resource, image);
if (special != null) {
specialTextures.put(resource.toString(), special);
}
return true;
return image;
}
public boolean preloadTile(ResourceLocation resource, boolean alternate) {
@@ -287,8 +304,14 @@ public class TileLoader {
return false;
}
}
IIcon icon = textureMap.registerIcon(name);
map.put(name, (TextureAtlasSprite) icon);
TextureAtlasSprite icon;
if(generatedTiles.contains(resource)){
icon = new GeneratedCTMAtlasSprite(this, textureMap.anisotropicFiltering > 1, name);
textureMap.setTextureEntry(name, icon);
}else{
icon = (TextureAtlasSprite)textureMap.registerIcon(name);
}
map.put(name, icon);
iconMap.put(name, icon);
String extra = (width == height ? "" : ", " + (height / width) + " frames");
subLogger.finer("%s -> %s icon %dx%d%s", name, mapName, width, width, extra);
@@ -299,6 +322,7 @@ public class TileLoader {
public void finish() {
tilesToRegister.clear();
tileImages.clear();
generatedTiles.clear();
}
public IIcon getIcon(String name) {
+3 -1
View File
@@ -174,7 +174,9 @@ public enum Settings {
RenderStars.reloadStarRenderList(Minecraft.getMinecraft().renderGlobal);
}
},
VOID_FOG(new NotFineOptionTickBox(false, OptionImpact.LOW));
VOID_FOG(new NotFineOptionTickBox(false, OptionImpact.LOW)),
ZOOM_SMOOTH(new NotFineOptionTickBox(true, OptionImpact.LOW)),
ZOOM_SMOOTH_SPEED(new NotFineOptionSlider(14, 1, 30, 1, OptionImpact.LOW));
public final NotFineOption<?> option;
@@ -1,6 +1,7 @@
package jss.notfine.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import jss.notfine.config.NotFineConfig;
import jss.notfine.core.Settings;
@@ -169,6 +170,12 @@ public class NotFineGameOptionPages {
.build());
}
if (AngelicaConfig.enableZoom) {
groups.add(OptionGroup.createBuilder()
.add(Settings.ZOOM_SMOOTH.option)
.add(Settings.ZOOM_SMOOTH_SPEED.option)
.build());
}
return new OptionPage(I18n.format("options.video"), ImmutableList.copyOf(groups));
}
@@ -29,7 +29,7 @@ public class InterpolatedIcon extends TextureAtlasSprite {
}
}
private void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException {
protected void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException {
double d0 = 1.0D - tickCounter / (double) animationMetadata.getFrameTimeSingle(frameCounter);
int i = animationMetadata.getFrameIndex(frameCounter);
@@ -532,6 +532,13 @@ public class SodiumGameOptionPages {
.build())
.build());
if (AngelicaConfig.enableZoom) {
groups.add(OptionGroup.createBuilder()
.add(Settings.ZOOM_SMOOTH.option)
.add(Settings.ZOOM_SMOOTH_SPEED.option)
.build());
}
return new OptionPage(I18n.format("sodium.options.pages.appearance"), ImmutableList.copyOf(groups));
}
+8 -4
View File
@@ -36,6 +36,7 @@ import net.coderbot.iris.shaderpack.option.Profile;
import net.coderbot.iris.shaderpack.option.values.MutableOptionValues;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
@@ -567,11 +568,11 @@ public class Iris {
// If it's a folder-based shaderpack, just use the shaders subdirectory
shaderPackPath = shaderPackRoot.resolve("shaders");
}
if (!Files.exists(shaderPackPath)) {
logger.error("Could not load the shaderpack \"{}\" because it appears to lack a \"shaders\" directory", name);
return false;
if (!Files.exists(shaderPackPath)) {
logger.error("Could not load the shaderpack \"{}\" because it appears to lack a \"shaders\" directory", name);
return false;
}
}
@SuppressWarnings("unchecked")
@@ -762,6 +763,9 @@ public class Iris {
// allows shaderpacks to be changed at runtime
irisConfig.initialize();
// Reset the texture reload counter
CapturedRenderingState.INSTANCE.resetTextureReloadCount();
// Destroy all allocated resources
destroyEverything();
@@ -161,6 +161,7 @@ public class DHCompatInternal {
if (((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion() != cachedVersion) {
cachedVersion = ((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion();
createDepthTex(Minecraft.getMinecraft().getFramebuffer().framebufferWidth, Minecraft.getMinecraft().getFramebuffer().framebufferHeight);
storedDepthTex = -1;
}
if (storedDepthTex != depthTex && dhTerrainFramebuffer != null) {
storedDepthTex = depthTex;
@@ -31,7 +31,6 @@ import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
@@ -46,12 +45,6 @@ import java.util.Map;
public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgram {
// Uniforms
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
private final int id;
private final ProgramUniforms uniforms;
private final CustomUniforms customUniforms;
@@ -62,7 +55,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
private final Matrix4f tempModel = new Matrix4f();
private final Matrix4f tempProj = new Matrix4f();
private final Matrix4f tempMat4 = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
private final int instancedShaderOffsetChunkUniform;
private final int instancedShaderOffsetSubChunkUniform;
@@ -143,12 +135,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GLStateManager.glEnableVertexAttribArray(0);
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
this.instancedShaderOffsetChunkUniform = this.tryGetUniformLocation2("uOffsetChunk");
this.instancedShaderOffsetSubChunkUniform = this.tryGetUniformLocation2("uOffsetSubChunk");
this.instancedShaderCameraChunkPosUniform = this.tryGetUniformLocation2("uCameraPosChunk");
@@ -213,18 +199,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind(DhApiRenderParam renderParam) {
GLStateManager.glBindVertexArray(va);
@@ -238,14 +212,8 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
toJOML(tempModel, renderParam.dhModelViewMatrix);
toJOML(tempProj, renderParam.dhProjectionMatrix);
setUniform(modelViewUniform, tempModel);
setUniform(projectionUniform, tempProj);
this.setUniform(this.instancedShaderProjectionModelViewMatrixUniform, tempMat4.set(tempProj).mul(tempModel));
tempModel.invert();
tempProj.invert();
setUniform(modelViewInverseUniform, tempModel);
setUniform(projectionInverseUniform, tempProj);
setUniform(normalMatrix3fUniform, tempModel.transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(tempProj, tempModel);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
@@ -259,6 +227,7 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
public void unbind() {
LodRendererEvents.popDhMatrices();
GLStateManager.glBindVertexArray(0);
GLStateManager.glUseProgram(0);
ProgramUniforms.clearActiveUniforms();
@@ -3,7 +3,6 @@ package net.coderbot.iris.compat.dh;
import com.google.common.primitives.Ints;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import com.mitchej123.lwjgl.MemoryStack;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import net.coderbot.iris.gl.blending.BlendModeOverride;
@@ -26,15 +25,12 @@ import net.coderbot.iris.uniforms.builtin.BuiltinReplacementUniforms;
import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -44,11 +40,6 @@ public class IrisLodRenderProgram {
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int mircoOffsetUniform;
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
public final int clipDistanceUniform;
private final int id;
@@ -58,9 +49,6 @@ public class IrisLodRenderProgram {
private final ProgramImages images;
private final BlendModeOverride blend;
private final BufferBlendOverride[] bufferBlendOverrides;
private final Matrix4f tempMat4a = new Matrix4f();
private final Matrix4f tempMat4b = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
// This will bind AbstractVertexAttribute
private IrisLodRenderProgram(String name, boolean isShadowPass, boolean translucent, BlendModeOverride override, BufferBlendOverride[] bufferBlendOverrides, String vertex, String tessControl, String tessEval, String geometry, String fragment, CustomUniforms customUniforms, DeferredWorldRenderingPipeline pipeline) {
@@ -132,11 +120,6 @@ public class IrisLodRenderProgram {
modelOffsetUniform = tryGetUniformLocation2("modelOffset");
worldYOffsetUniform = tryGetUniformLocation2("worldYOffset");
mircoOffsetUniform = tryGetUniformLocation2("mircoOffset");
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
// Fog/Clip Uniforms
clipDistanceUniform = tryGetUniformLocation2("clipDistance");
@@ -176,30 +159,6 @@ public class IrisLodRenderProgram {
return GLStateManager.glGetUniformLocation(this.id, name);
}
public void setUniform(int index, Matrix4fc matrix) {
if (index == -1 || matrix == null) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(16);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix4(index, false, buffer);
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind() {
GLStateManager.glUseProgram(id);
@@ -227,11 +186,8 @@ public class IrisLodRenderProgram {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
setUniform(modelViewUniform, modelView);
setUniform(modelViewInverseUniform, modelView.invert(tempMat4a));
setUniform(projectionUniform, projection);
setUniform(projectionInverseUniform, projection.invert(tempMat4b));
setUniform(normalMatrix3fUniform, tempMat4a.set(modelView).invert().transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(projection, modelView);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
@@ -33,6 +33,7 @@ import net.coderbot.iris.shadows.ShadowRenderingState;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.irisshaders.iris.api.v0.IrisApi;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
public class LodRendererEvents {
@@ -46,6 +47,28 @@ public class LodRendererEvents {
private static int textureHeight;
private static final Matrix4f tempMat4 = new Matrix4f();
private static boolean dhMatricesActive = false;
private static final Matrix4f savedModelView = new Matrix4f();
private static final Matrix4f savedProjection = new Matrix4f();
static void pushDhMatrices(Matrix4fc projection, Matrix4fc modelView) {
if (!dhMatricesActive) {
savedModelView.set(GLStateManager.getModelViewMatrix());
savedProjection.set(GLStateManager.getProjectionMatrix());
dhMatricesActive = true;
}
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.setProjectionMatrix(projection);
}
static void popDhMatrices() {
if (dhMatricesActive) {
GLStateManager.setModelViewMatrix(savedModelView);
GLStateManager.setProjectionMatrix(savedProjection);
dhMatricesActive = false;
}
}
// constructor //
@@ -181,6 +204,7 @@ public class LodRendererEvents {
DhApiBeforeRenderCleanupEvent beforeCleanupEvent = new DhApiBeforeRenderCleanupEvent() {
@Override
public void beforeCleanup(DhApiEventParam<DhApiRenderParam> event) {
popDhMatrices();
if (getInstance().shouldOverride) {
if (ShadowRenderingState.areShadowsCurrentlyBeingRendered()) {
getInstance().getShadowShader().unbind();
@@ -10,13 +10,13 @@ import java.util.function.BooleanSupplier;
public enum FeatureFlags {
SEPARATE_HARDWARE_SAMPLERS(() -> true, () -> true),
PER_BUFFER_BLENDING(() -> true, RenderSystem::supportsBufferBlending),
COMPUTE_SHADERS(() -> true, RenderSystem::supportsCompute),
COMPUTE_SHADERS(() -> false, RenderSystem::supportsCompute),
ENTITY_TRANSLUCENT(() -> true, () -> true),
BLOCK_EMISSION_ATTRIBUTE(() -> true, () -> true),
CUSTOM_IMAGES(() -> true, RenderSystem::supportsImageLoadStore),
SSBO(() -> true, RenderSystem::supportsSSBO),
HIGHER_SHADOWCOLOR(() -> true, () -> true),
TESSELLATION_SHADERS(() -> true, RenderSystem::supportsTesselation),
TESSELLATION_SHADERS(() -> false, RenderSystem::supportsTesselation),
REVERSED_CULLING(() -> true, () -> true),
CAN_DISABLE_WEATHER(() -> true, () -> true),
UNKNOWN(() -> false, () -> false);
@@ -4,5 +4,7 @@ public enum GlVersion {
GL_11,
GL_12,
GL_30,
GL_31
GL_31,
GL_33,
GL_41
}
@@ -132,6 +132,11 @@ public class StandardMacros {
getBiomeCategoryDefines().forEach((category, ordinal) -> define(standardDefines, category, ordinal));
getVanillaBiomeDefines().forEach((biome, id) -> define(standardDefines, biome, id));
// rain type constants
define(standardDefines, "PPT_NONE", "0");
define(standardDefines, "PPT_RAIN", "1");
define(standardDefines, "PPT_SNOW", "2");
return ImmutableList.copyOf(standardDefines);
}
@@ -5,6 +5,8 @@ import net.coderbot.iris.gl.GlVersion;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import java.util.Optional;
@@ -72,10 +74,16 @@ public enum InternalTextureFormat {
RG32UI(GL30.GL_RG32UI, GlVersion.GL_30, PixelFormat.RG_INTEGER),
RGB32UI(GL30.GL_RGB32UI, GlVersion.GL_30, PixelFormat.RGB_INTEGER),
RGBA32UI(GL30.GL_RGBA32UI, GlVersion.GL_30, PixelFormat.RGBA_INTEGER),
// 2-bit normalized
RGBA2(GL11.GL_RGBA2, GlVersion.GL_11, PixelFormat.RGBA),
// 4-bit normalized
RGBA4(GL11.GL_RGBA4, GlVersion.GL_11, PixelFormat.RGBA),
// Mixed
R3_G3_B2(GL11.GL_R3_G3_B2, GlVersion.GL_11, PixelFormat.RGB),
RGB5_A1(GL11.GL_RGB5_A1, GlVersion.GL_11, PixelFormat.RGBA),
RGB565(GL41.GL_RGB565, GlVersion.GL_41, PixelFormat.RGB),
RGB10_A2(GL11.GL_RGB10_A2, GlVersion.GL_11, PixelFormat.RGBA),
RGB10_A2UI(GL33.GL_RGB10_A2UI, GlVersion.GL_33, PixelFormat.RGBA_INTEGER),
R11F_G11F_B10F(GL30.GL_R11F_G11F_B10F, GlVersion.GL_30, PixelFormat.RGB),
RGB9_E5(GL30.GL_RGB9_E5, GlVersion.GL_30, PixelFormat.RGB);
@@ -42,6 +42,7 @@ import net.coderbot.iris.gl.texture.DepthBufferFormat;
import net.coderbot.iris.gl.texture.TextureType;
import net.coderbot.iris.helpers.Tri;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.transform.GlintScrollInjector;
import net.coderbot.iris.pipeline.transform.PatchShaderType;
import net.coderbot.iris.pipeline.transform.TransformPatcher;
import net.coderbot.iris.postprocess.BufferFlipper;
@@ -838,13 +839,16 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
if (transformed == null) {
// Fallback to synchronous transform if not pre-computed
String vertex = source.getVertexSource().orElseThrow(NullPointerException::new);
final boolean scrollGlint = GlintScrollInjector.shouldInject(id, source);
transformed = TransformPatcher.patchAttributes(
source.getVertexSource().orElseThrow(NullPointerException::new),
vertex,
source.getGeometrySource().orElse(null),
source.getTessControlSource().orElse(null),
source.getTessEvalSource().orElse(null),
source.getFragmentSource().orElseThrow(NullPointerException::new),
availability);
availability,
scrollGlint);
}
String vertex = transformed.get(PatchShaderType.VERTEX);
@@ -1784,9 +1788,12 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
ProgramSource source = resolver.resolveNullable(id);
if (source != null && !processedSourceNames.contains(source.getName())) {
processedSourceNames.add(source.getName());
final ProgramSource src = source;
final String vertexSource = src.getVertexSource().orElse(null);
final boolean scrollGlint = GlintScrollInjector.shouldInject(id, src);
for (InputAvailability avail : INPUT_AVAILABILITIES) {
Pair<String, InputAvailability> key = Pair.of(source.getName(), avail);
futures.put(key, Iris.ShaderTransformExecutor.submitTracked(() -> TransformPatcher.patchAttributes(source.getVertexSource().orElse(null), source.getGeometrySource().orElse(null), source.getTessControlSource().orElse(null), source.getTessEvalSource().orElse(null), source.getFragmentSource().orElse(null), avail)));
Pair<String, InputAvailability> key = Pair.of(src.getName(), avail);
futures.put(key, Iris.ShaderTransformExecutor.submitTracked(() -> TransformPatcher.patchAttributes(vertexSource, src.getGeometrySource().orElse(null), src.getTessControlSource().orElse(null), src.getTessEvalSource().orElse(null), src.getFragmentSource().orElse(null), avail, scrollGlint)));
}
}
}
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.HashMap;
import java.util.Map;
@@ -13,6 +14,10 @@ import java.util.Map;
*/
class AttributeTransformer {
public static void transform(Transformer transformer, AttributeParameters parameters, int version) {
if (parameters.scrollGlint && parameters.type == ShaderType.VERTEX) {
transformer.replaceExpression("gl_MultiTexCoord0", "(gl_TextureMatrix[0] * gl_MultiTexCoord0)", GLSLParser::postfix_expression);
}
// Always core profile — minimum GLSL version is 330 (see ShaderTransformer.getStageMinimumVersion)
CommonTransformer.transform(transformer, parameters, true, version);
@@ -46,6 +46,10 @@ class CeleritasTransformer {
vertexReplacements.put("gl_MultiTexCoord2", "iris_LightTexCoord");
vertexReplacements.forEach(transformer::replaceExpression);
if (transformer.hasVariable("chunkOffset")) {
transformer.removeVariable("chunkOffset");
}
final Map<String, String> vertexRenames = new HashMap<>();
vertexRenames.put("gl_Color", "_vert_color");
vertexRenames.put("ftransform", "iris_ftransform");
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.HashSet;
import java.util.Set;
@@ -29,6 +30,16 @@ public class CommonTransformer {
root.rename("gl_Color", "iris_FrontColor");
}
if (root.hasVariable("gl_TexCoord")) {
if (parameters.type == ShaderType.VERTEX) {
root.rename("gl_TexCoord", "irs_texCoords");
root.injectVariable("out vec4 irs_texCoords[3];");
} else if (parameters.type == ShaderType.FRAGMENT) {
root.rename("gl_TexCoord", "irs_texCoords");
root.injectVariable("in vec4 irs_texCoords[3];");
}
}
if (parameters.type == ShaderType.FRAGMENT) {
if (root.containsCall("gl_FragColor")) {
root.replaceExpression("gl_FragColor", "gl_FragData[0]");
@@ -91,6 +102,27 @@ public class CommonTransformer {
root.renameAndWrapShadow("shadow2D", "texture");
root.renameAndWrapShadow("shadow2DLod", "textureLod");
if (root.containsCall("textureLodOffset")) {
root.injectFunction("vec4 iris_textureLodOffset(sampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("float iris_textureLodOffset(sampler2DShadow iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, vec3(iris_tlo_c.xy + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_c.z), iris_tlo_l); }");
root.injectFunction("vec4 iris_textureLodOffset(sampler3D iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec3 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec3(iris_tlo_o) / vec3(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("vec4 iris_textureLodOffset(sampler2DArray iris_tlo_s, vec3 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, vec3(iris_tlo_c.xy + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l)).xy), iris_tlo_c.z), iris_tlo_l); }");
root.injectFunction("ivec4 iris_textureLodOffset(isampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.injectFunction("uvec4 iris_textureLodOffset(usampler2D iris_tlo_s, vec2 iris_tlo_c, float iris_tlo_l, ivec2 iris_tlo_o) { return textureLod(iris_tlo_s, iris_tlo_c + vec2(iris_tlo_o) / vec2(textureSize(iris_tlo_s, int(iris_tlo_l))), iris_tlo_l); }");
root.renameFunctionCall("textureLodOffset", "iris_textureLodOffset");
}
if (parameters.type == ShaderType.VERTEX) {
root.mutateTree(tree -> {
if (tree.children != null) {
tree.children.removeIf(child ->
child instanceof GLSLParser.External_declarationContext
&& ((GLSLParser.External_declarationContext) child).declaration() != null
&& child.getText().contains("gl_PerVertex"));
}
});
}
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.gl.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
import org.antlr.v4.runtime.tree.ParseTree;
import org.taumc.glsl.ShaderPrinter;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
@@ -109,7 +110,10 @@ public class CompatibilityTransformer {
continue;
}
prevTransformer.makeOutDeclaration(inDec.get(in), in);
String outDeclaration = ShaderPrinter.getFormattedShader(inDec.get(in).fully_specified_type())
+ " " + in + ";";
prevTransformer.variable = null;
prevTransformer.injectVariable(outDeclaration.replaceFirst("in", "out"));
if (!prevTransformer.hasAssigment(in)) {
prevTransformer.initialize(inDec.get(in), in);
@@ -126,6 +130,23 @@ public class CompatibilityTransformer {
if (!prevTransformer.hasAssigment(in)) {
prevTransformer.initialize(inDec.get(in), in);
}
} else {
int outComp = vecComponents(outType.getText());
int inComp = vecComponents(inType.getText());
if (outComp < 0 || inComp < 0) {
continue;
}
String temp = "iris_template_" + in;
prevTransformer.rename(in, temp);
String newOut = ShaderPrinter.getFormattedShader(inDec.get(in).fully_specified_type())
+ " " + in + ";";
prevTransformer.variable = null;
prevTransformer.injectVariable(newOut.replaceFirst("in", "out"));
String cast = outComp < inComp
? in + " = " + inType.getText() + "(" + temp + ", vec4(0));"
: in + " = " + inType.getText() + "(" + temp + ");";
prevTransformer.appendMain(cast);
}
}
}
@@ -133,4 +154,13 @@ public class CompatibilityTransformer {
prevType = type;
}
}
private static int vecComponents(String type) {
return switch (type) {
case "vec2" -> 2;
case "vec3" -> 3;
case "vec4" -> 4;
default -> -1;
};
}
}
@@ -18,7 +18,7 @@ class CompositeDepthTransformer {
final int type = transformer.findType("centerDepthSmooth");
if (type != 0) {
transformer.injectVariable("uniform sampler2D iris_centerDepthSmooth;");
transformer.replaceExpression("centerDepthSmooth", "texture2D(iris_centerDepthSmooth, vec2(0.5)).r");
transformer.replaceExpression("centerDepthSmooth", "texture(iris_centerDepthSmooth, vec2(0.5)).r");
}
}
}
@@ -51,6 +51,13 @@ class CoreTransformHelper {
transformer.rename("gl_Vertex", "iris_Vertex");
transformer.rename("gl_MultiTexCoord0", "iris_MultiTexCoord0");
for (int i = 1; i <= 7; i++) {
transformer.replaceExpression("gl_MultiTexCoord" + i, "vec4(0.0, 0.0, 0.0, 1.0)");
}
transformer.replaceExpression("gl_Color", "vec4(1.0, 1.0, 1.0, 1.0)");
transformer.replaceExpression("gl_Normal", "vec3(0.0, 0.0, 1.0)");
transformer.renameFunctionCall("ftransform", "iris_ftransform");
transformer.injectFunction("vec4 iris_ftransform() { return (iris_ProjectionMatrix * iris_ModelViewMatrix) * iris_Vertex; }");
@@ -0,0 +1,24 @@
package net.coderbot.iris.pipeline.transform;
import net.coderbot.iris.shaderpack.ProgramSource;
import net.coderbot.iris.shaderpack.loading.ProgramId;
/**
* For some reason not all shaders read from gl_MultiTexCoord0 through the texture matrix to scroll the enchant glint?
* Maybe a modern thing but a few shaders just look wrong without it. Technically not in spec, but it looks better.
*/
public final class GlintScrollInjector {
private GlintScrollInjector() {}
public static boolean shouldInject(ProgramId id, ProgramSource source) {
if (id != ProgramId.ArmorGlint || source == null) return false;
if (!ProgramId.ArmorGlint.getSourceName().equals(source.getName())) return false;
final String vertex = source.getVertexSource().orElse(null);
if (vertex == null) return false;
// Already scrolls through the texture matrix itself
if (vertex.contains("gl_TextureMatrix[0]")) return false;
return vertex.contains("gl_MultiTexCoord0");
}
}
@@ -104,8 +104,12 @@ public class TransformPatcher {
return result;
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint) {
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, inputs, scrollGlint));
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, inputs));
return patchAttributes(vertex, geometry, tessControl, tessEval, fragment, inputs, false);
}
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String fragment, InputAvailability inputs) {
@@ -7,12 +7,14 @@ import net.coderbot.iris.shaderpack.texture.TextureStage;
public class AttributeParameters extends Parameters {
public final boolean hasGeometry;
public final InputAvailability inputs;
public final boolean scrollGlint;
// WARNING: adding new fields requires updating hashCode and equals methods!
public AttributeParameters(Patch patch, boolean hasGeometry, InputAvailability inputs) {
public AttributeParameters(Patch patch, boolean hasGeometry, InputAvailability inputs, boolean scrollGlint) {
super(patch, null);
this.hasGeometry = hasGeometry;
this.inputs = inputs;
this.scrollGlint = scrollGlint;
}
@Override
@@ -25,6 +27,7 @@ public class AttributeParameters extends Parameters {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (hasGeometry ? 1231 : 1237);
result = prime * result + (scrollGlint ? 1231 : 1237);
result = prime * result + ((inputs == null) ? 0 : inputs.hashCode());
return result;
}
@@ -40,6 +43,8 @@ public class AttributeParameters extends Parameters {
AttributeParameters other = (AttributeParameters) obj;
if (hasGeometry != other.hasGeometry)
return false;
if (scrollGlint != other.scrollGlint)
return false;
if (inputs == null) {
if (other.inputs != null)
return false;
@@ -77,11 +77,22 @@ public class IdMap {
private static final Pattern LEGACY_DIRECTIVE_PATTERN = Pattern.compile(
"(?m)^\\s*#\\s*(?:if|elif|ifdef|ifndef)\\b[^\\n]*\\bMC_VERSION\\b[^\\n]*\\b10710\\b");
/**
* Detects whether a pack's block.properties has a dedicated 1.7.10 section.
*/
public static boolean detectLegacySection(Path shaderPath) {
String rawBlockProperties = readProperties(shaderPath, "block.properties");
return rawBlockProperties != null && LEGACY_DIRECTIVE_PATTERN.matcher(rawBlockProperties).find();
}
public static String modernFallbackMcVersion() {
return AngelicaConfig.modernFallbackMcVersion > 0
? String.valueOf(AngelicaConfig.modernFallbackMcVersion) : "260101";
}
IdMap(Path shaderPath, ShaderPackOptions shaderPackOptions, Iterable<StringPair> environmentDefines) {
// Check if block.properties has a dedicated 1.7.10 section
String rawBlockProperties = readProperties(shaderPath, "block.properties");
this.hasLegacySection = rawBlockProperties != null
&& LEGACY_DIRECTIVE_PATTERN.matcher(rawBlockProperties).find();
this.hasLegacySection = detectLegacySection(shaderPath);
Iterable<StringPair> resolvedDefines;
if (this.hasLegacySection) {
@@ -100,9 +111,7 @@ public class IdMap {
}
}
String modernVersion = AngelicaConfig.modernFallbackMcVersion > 0
? String.valueOf(AngelicaConfig.modernFallbackMcVersion) : "260101";
modernDefines.add(new StringPair("MC_VERSION", modernVersion));
modernDefines.add(new StringPair("MC_VERSION", modernFallbackMcVersion()));
resolvedDefines = modernDefines;
loadProperties(shaderPath, "block.properties", shaderPackOptions, modernDefines).ifPresent(blockProperties -> {
@@ -55,7 +55,7 @@ public class PackRenderTargetDirectives {
renderTargetSettings.forEach((index, settings) -> {
if (settings.shouldClear()) {
buffersToBeCleared.add(index.intValue());
buffersToBeCleared.add(index);
}
});
@@ -1,6 +1,7 @@
package net.coderbot.iris.shaderpack;
import net.coderbot.iris.Iris;
import net.coderbot.iris.features.FeatureFlags;
import net.coderbot.iris.gl.blending.BlendModeOverride;
import net.coderbot.iris.shaderpack.include.AbsolutePackPath;
import net.coderbot.iris.shaderpack.loading.ProgramId;
@@ -529,7 +530,8 @@ public class ProgramSet {
String tessControlSource = null;
String tessEvalSource = null;
if (programSet.pack.hasFeature(net.coderbot.iris.features.FeatureFlags.TESSELLATION_SHADERS)) {
if (programSet.pack.hasFeature(FeatureFlags.TESSELLATION_SHADERS)
&& FeatureFlags.TESSELLATION_SHADERS.isUsable()) {
AbsolutePackPath tessControlPath = directory.resolve(program + ".tcs");
tessControlSource = sourceProvider.apply(tessControlPath);
@@ -547,6 +549,10 @@ public class ProgramSet {
private static ComputeSource readComputeSource(AbsolutePackPath directory,
Function<AbsolutePackPath, String> sourceProvider, String program,
ProgramSet programSet) {
if (!FeatureFlags.COMPUTE_SHADERS.isUsable()) {
return null;
}
AbsolutePackPath computePath = directory.resolve(program + ".csh");
String computeSource = sourceProvider.apply(computePath);
@@ -207,8 +207,16 @@ public class ShaderPack {
this.shaderPackOptions = new ShaderPackOptions(graph, changedConfigs);
graph = this.shaderPackOptions.getIncludes();
final String mcVersionOverride = IdMap.detectLegacySection(root) ? null : IdMap.modernFallbackMcVersion();
List<StringPair> finalEnvironmentDefines = new ArrayList<>();
environmentDefines.forEach(finalEnvironmentDefines::add);
for (StringPair define : environmentDefines) {
if (mcVersionOverride != null && "MC_VERSION".equals(define.getKey())) {
finalEnvironmentDefines.add(new StringPair("MC_VERSION", mcVersionOverride));
} else {
finalEnvironmentDefines.add(define);
}
}
for (FeatureFlags flag : FeatureFlags.values()) {
boolean usable = flag.isUsable();
Iris.logger.info("FeatureFlag {} usable: {}", flag.name(), usable);
@@ -1,6 +1,8 @@
package net.coderbot.iris.shaderpack.loading;
public enum ProgramArrayId {
Setup(ProgramGroup.Setup, 100),
Begin(ProgramGroup.Begin, 100),
ShadowComposite(ProgramGroup.ShadowComposite, 99),
Prepare(ProgramGroup.Prepare, 99),
Deferred(ProgramGroup.Deferred, 99),
@@ -1,6 +1,11 @@
package net.coderbot.iris.shaderpack.loading;
import lombok.Getter;
@Getter
public enum ProgramGroup {
Setup("setup"),
Begin("begin"),
Shadow("shadow"),
ShadowComposite("shadowcomp"),
Prepare("prepare"),
@@ -17,7 +22,4 @@ public enum ProgramGroup {
this.baseName = baseName;
}
public String getBaseName() {
return baseName;
}
}
@@ -1,9 +1,9 @@
package net.coderbot.iris.shaderpack.materialmap;
import net.coderbot.iris.Iris;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -263,7 +263,19 @@ public final class PropertiesTokenizer {
);
}
final String[] splitStates = baseEntry.split(":");
// Drop empty segments produced by consecutive colons (like "betterendforge::lumecorn")
final String[] splitStates = Arrays.stream(baseEntry.split(":"))
.filter(s -> !s.isEmpty())
.toArray(String[]::new);
if (splitStates.length == 0) {
return new ParsedBlockIdentifier(
new NamespacedId("minecraft", baseEntry),
Collections.emptySet(),
Collections.emptyMap(),
nbtProperties
);
}
// Vanilla block (minecraft namespaced)
if (splitStates.length == 1) {
@@ -277,7 +289,7 @@ public final class PropertiesTokenizer {
// Two-segment with no metas/state properties: "minecraft:stone"
if (splitStates.length == 2
&& !StringUtils.isNumeric(splitStates[1].substring(0, 1))
&& !startsWithDigit(splitStates[1])
&& !splitStates[1].contains("=")) {
return new ParsedBlockIdentifier(
new NamespacedId(splitStates[0], splitStates[1]),
@@ -296,7 +308,7 @@ public final class PropertiesTokenizer {
id = new NamespacedId("minecraft", splitStates[0]);
statesStart = 1;
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
} else if (startsWithDigit(splitStates[1]) || splitStates[1].contains("=")) {
// "stone:0:something" or "stone:lit=true:something", likely will need this for snowy blocks in the future.
id = new NamespacedId("minecraft", splitStates[0]);
statesStart = 1;
@@ -339,6 +351,10 @@ public final class PropertiesTokenizer {
);
}
private static boolean startsWithDigit(String s) {
return !s.isEmpty() && Character.isDigit(s.charAt(0));
}
private static void appendCurrent(boolean inBracket, boolean readingValue,
StringBuilder blockIdBuilder, StringBuilder nbtKey,
StringBuilder nbtValue, char c) {
@@ -80,7 +80,7 @@ public class PropertiesPreprocessor {
// Not super efficient, but this removes trailing whitespace on lines, fixing an issue with whitespace after
// line continuations (see PreprocessorTest#testWeirdPropertiesLineContinuation)
// Required for Voyager Shader
source = Arrays.stream(source.split("\\R")).map(String::trim).filter(s -> !s.isEmpty())
source = Arrays.stream(source.split("\\r\\n|\\n|\\r")).map(String::trim).filter(s -> !s.isEmpty())
.map(line -> {
if (line.startsWith("#")) {
for (PreprocessorCommand command : PreprocessorCommand.values()) {
@@ -13,6 +13,9 @@ public class CapturedRenderingState {
@Getter
private float tickDelta;
@Getter
private int textureReloadCount;
private Runnable textureReloadListener = null;
@Getter
private int currentRenderedBlockEntity;
private Runnable blockEntityIdListener = null;
@@ -31,6 +34,18 @@ public class CapturedRenderingState {
private CapturedRenderingState() {
}
public void incrementTextureReloadCount() {
this.textureReloadCount++;
if (this.textureReloadListener != null) {
this.textureReloadListener.run();
}
}
public void resetTextureReloadCount() {
this.textureReloadCount = 0;
}
public void setCurrentBlockEntity(int entity) {
this.currentRenderedBlockEntity = entity;
@@ -80,5 +95,9 @@ public class CapturedRenderingState {
return listener -> this.itemIdListener = listener;
}
public ValueUpdateNotifier getTextureReloadNotifier() {
return listener -> this.textureReloadListener = listener;
}
public ValueUpdateNotifier getEntityColorNotifier() { return listener -> this.entityColorListener = listener; }
}
@@ -96,6 +96,12 @@ public final class CommonUniforms {
}, StateUpdateNotifiers.bindTextureNotifier);
uniforms.uniform1i("gtextureId", () -> GLStateManager.getBoundTextureForServerState(0),
StateUpdateNotifiers.bindTextureNotifier);
uniforms.uniform1i("textureReloadCount", CapturedRenderingState.INSTANCE::getTextureReloadCount,
CapturedRenderingState.INSTANCE.getTextureReloadNotifier());
uniforms.uniform4i("blendFunc", () -> {
if(GLStateManager.getBlendMode().isEnabled()) {
final BlendState blend = GLStateManager.getBlendState();
@@ -1,18 +1,27 @@
package net.coderbot.iris.uniforms;
import com.gtnewhorizons.angelica.compat.etfuturum.EtFuturumCompat;
import com.gtnewhorizons.angelica.compat.mojang.Camera;
import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil;
import com.gtnewhorizons.angelica.render.CloudRenderer;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.gl.uniform.UniformHolder;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.MovingObjectPosition;
import org.joml.Math;
import org.joml.Vector3d;
import org.joml.Vector3dc;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.List;
@@ -21,9 +30,17 @@ public class IrisExclusiveUniforms {
// Reusable vectors to avoid allocations every frame
private static final Vector3d eyePositionCache = new Vector3d();
private static final Vector3d relativeEyePositionCache = new Vector3d();
private static final Vector3d playerLookCache = new Vector3d();
private static final Vector3d playerBodyCache = new Vector3d();
private static final Vector3d vehicleLookCache = new Vector3d();
private static final Vector3d relativeVehiclePositionCache = new Vector3d();
private static final Vector3f selectedBlockPosCache = new Vector3f();
private static final Vector4f lightningBoltPositionCache = new Vector4f();
private static final Vector4f ZERO_VECTOR_4f = new Vector4f(0, 0, 0, 0);
private static int cachedSelectedFrame = -1;
private static int cachedSelectedBlockId;
public static void addIrisExclusiveUniforms(UniformHolder uniforms) {
WorldInfoUniforms.addWorldInfoUniforms(uniforms);
@@ -42,6 +59,21 @@ public class IrisExclusiveUniforms {
uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "relativeEyePosition", IrisExclusiveUniforms::getRelativeEyePosition);
uniforms.uniform4f(UniformUpdateFrequency.PER_TICK, "lightningBoltPosition", IrisExclusiveUniforms::getLightningBoltPosition);
uniforms.uniform1f(UniformUpdateFrequency.PER_FRAME, "cloudTime", IrisExclusiveUniforms::getCloudTime);
uniforms.uniform1b(UniformUpdateFrequency.PER_TICK, "feetInWater", IrisExclusiveUniforms::isFeetInWater);
uniforms.uniform1b(UniformUpdateFrequency.PER_TICK, "isRiding", IrisExclusiveUniforms::isRiding);
uniforms.uniform1b(UniformUpdateFrequency.PER_TICK, "vehicleInWater", IrisExclusiveUniforms::isVehicleInWater);
uniforms.uniform1i(UniformUpdateFrequency.PER_TICK, "vehicleId", IrisExclusiveUniforms::getVehicleId);
uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "vehicleLookVector", IrisExclusiveUniforms::getVehicleLookVector);
uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "relativeVehiclePosition", IrisExclusiveUniforms::getRelativeVehiclePosition);
uniforms.uniform1f(UniformUpdateFrequency.PER_TICK, "currentPlayerArmor", IrisExclusiveUniforms::getCurrentArmor);
uniforms.uniform1f(UniformUpdateFrequency.PER_TICK, "maxPlayerArmor", () -> 50);
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "currentSelectedBlockId", IrisExclusiveUniforms::getCurrentSelectedBlockId);
uniforms.uniform3f(UniformUpdateFrequency.PER_FRAME, "currentSelectedBlockPos", IrisExclusiveUniforms::getCurrentSelectedBlockPos);
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "seaLevel", IrisExclusiveUniforms::getSeaLevel);
uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "playerLookVector", IrisExclusiveUniforms::getPlayerLookVector);
uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "playerBodyVector", IrisExclusiveUniforms::getPlayerBodyVector);
// Target the Elytra from EFR
uniforms.uniform1b(UniformUpdateFrequency.PER_TICK, "isElytraFlying", IrisExclusiveUniforms::isElytraFlying);
}
private static float getCloudTime() {
@@ -137,6 +169,149 @@ public class IrisExclusiveUniforms {
return ZERO_VECTOR_4f;
}
private static EntityLivingBase cameraEntity() {
return Minecraft.getMinecraft().renderViewEntity;
}
private static Vector3d forwardVector(Vector3d dest, float yawDeg, float pitchDeg) {
final double yr = -yawDeg * 0.017453292 - Math.PI;
final double pr = -pitchDeg * 0.017453292;
final double cy = Math.cos(yr);
final double sy = Math.sin(yr);
final double cp = -Math.cos(pr);
final double sp = Math.sin(pr);
return dest.set(sy * cp, sp, cy * cp);
}
private static boolean isFeetInWater() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
return player != null && player.isInWater();
}
private static boolean isElytraFlying() {
return EtFuturumCompat.isElytraFlying(Minecraft.getMinecraft().thePlayer);
}
private static boolean isRiding() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
return player != null && player.ridingEntity != null;
}
private static boolean isVehicleInWater() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
return player != null && player.ridingEntity != null && player.ridingEntity.isInWater();
}
private static int getVehicleId() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null || player.ridingEntity == null) return 0;
final int id = EntityIdHelper.getEntityId(player.ridingEntity);
return Math.max(id, 0);
}
private static Vector3d getVehicleLookVector() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null || player.ridingEntity == null) return vehicleLookCache.set(0, 0, 0);
final Entity v = player.ridingEntity;
return forwardVector(vehicleLookCache, v.rotationYaw, v.rotationPitch);
}
private static Vector3d getRelativeVehiclePosition() {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null || player.ridingEntity == null) return relativeVehiclePositionCache.set(0, 0, 0);
final Entity v = player.ridingEntity;
final float delta = CapturedRenderingState.INSTANCE.getTickDelta();
final double vx = v.prevPosX + (v.posX - v.prevPosX) * delta;
final double vy = v.prevPosY + (v.posY - v.prevPosY) * delta;
final double vz = v.prevPosZ + (v.posZ - v.prevPosZ) * delta;
// Match Angelica's relativeEyePosition convention (entity - camera).
final Vector3dc cam = CameraUniforms.getUnshiftedCameraPosition();
return relativeVehiclePositionCache.set(vx - cam.x(), vy - cam.y(), vz - cam.z());
}
private static float getCurrentArmor() {
final Minecraft mc = Minecraft.getMinecraft();
if (mc.thePlayer == null || !mc.playerController.gameIsSurvivalOrAdventure()) {
return -1;
}
return mc.thePlayer.getTotalArmorValue() / 50.0f;
}
private static Vector3d getPlayerLookVector() {
final EntityLivingBase e = cameraEntity();
if (e == null) return playerLookCache.set(0, 0, 0);
final float delta = CapturedRenderingState.INSTANCE.getTickDelta();
final float yaw = e.prevRotationYaw + (e.rotationYaw - e.prevRotationYaw) * delta;
final float pitch = e.prevRotationPitch + (e.rotationPitch - e.prevRotationPitch) * delta;
return forwardVector(playerLookCache, yaw, pitch);
}
private static Vector3d getPlayerBodyVector() {
final EntityLivingBase e = cameraEntity();
if (e == null) return playerBodyCache.set(0, 0, 0);
return forwardVector(playerBodyCache, e.renderYawOffset, 0.0f);
}
private static int getSeaLevel() {
// 1.7.10 has no per-world sea level accessor, best to guess 63?
return 63;
}
private static void updateSelectedBlock() {
final int frame = SystemTimeUniforms.COUNTER.getAsInt();
if (frame == cachedSelectedFrame) {
return;
}
cachedSelectedFrame = frame;
final Minecraft mc = Minecraft.getMinecraft();
final MovingObjectPosition hit = mc.objectMouseOver;
if (mc.theWorld == null || hit == null || hit.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) {
cachedSelectedBlockId = 0;
selectedBlockPosCache.set(-256.0f, -256.0f, -256.0f);
return;
}
final Vector3dc cam = CameraUniforms.getUnshiftedCameraPosition();
selectedBlockPosCache.set(
(float) ((hit.blockX + 0.5) - cam.x()),
(float) ((hit.blockY + 0.5) - cam.y()),
(float) ((hit.blockZ + 0.5) - cam.z()));
final Block block = mc.theWorld.getBlock(hit.blockX, hit.blockY, hit.blockZ);
if (block == null || block.isAir(mc.theWorld, hit.blockX, hit.blockY, hit.blockZ)) {
cachedSelectedBlockId = 0;
return;
}
final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
if (blockMetaMatches == null) {
cachedSelectedBlockId = 0;
return;
}
final Int2IntMap metaMap = blockMetaMatches.get(block);
if (metaMap == null) {
cachedSelectedBlockId = 0;
return;
}
final int meta = mc.theWorld.getBlockMetadata(hit.blockX, hit.blockY, hit.blockZ);
cachedSelectedBlockId = Math.max(BlockMaterialMapping.resolveId(metaMap, meta), 0);
}
private static int getCurrentSelectedBlockId() {
updateSelectedBlock();
return cachedSelectedBlockId;
}
private static Vector3f getCurrentSelectedBlockPos() {
updateSelectedBlock();
return selectedBlockPosCache;
}
private static int worldHeight() {
final WorldClient level = Minecraft.getMinecraft().theWorld;
return (level != null && level.provider != null) ? level.provider.getHeight() : 256;
}
public static class WorldInfoUniforms {
public static void addWorldInfoUniforms(UniformHolder uniforms) {
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "bedrockLevel", () -> 0);
@@ -148,14 +323,8 @@ public class IrisExclusiveUniforms {
return 192.0;
}
});
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "heightLimit", () -> {
final WorldClient level = Minecraft.getMinecraft().theWorld;
if (level != null && level.provider != null) {
return level.provider.getHeight();
} else {
return 256;
}
});
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "heightLimit", IrisExclusiveUniforms::worldHeight);
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "logicalHeightLimit", IrisExclusiveUniforms::worldHeight);
uniforms.uniform1b(UniformUpdateFrequency.PER_FRAME, "hasCeiling", () -> {
final WorldClient level = Minecraft.getMinecraft().theWorld;
if (level != null && level.provider != null) {
@@ -3,7 +3,10 @@ package net.coderbot.iris.uniforms;
import lombok.Getter;
import net.coderbot.iris.gl.uniform.UniformHolder;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import org.joml.Vector2i;
import org.joml.Vector3i;
import java.time.LocalDateTime;
import java.util.OptionalLong;
import java.util.function.IntSupplier;
@@ -16,19 +19,47 @@ public final class SystemTimeUniforms {
public static final Timer TIMER = new Timer();
public static final FrameCounter COUNTER = new FrameCounter();
private static LocalDateTime cachedDateTime = LocalDateTime.now();
private static long cachedDateTimeAtMs = 0L;
private SystemTimeUniforms() {
}
private static LocalDateTime dateTimeSnapshot() {
final long now = System.currentTimeMillis();
if (now - cachedDateTimeAtMs >= 50L) {
cachedDateTime = LocalDateTime.now();
cachedDateTimeAtMs = now;
}
return cachedDateTime;
}
/**
* Makes system time uniforms available to the given program
*
* @param uniforms the program to make the uniforms available to
*/
public static void addSystemTimeUniforms(UniformHolder uniforms) {
final Vector3i date = new Vector3i();
final Vector3i time = new Vector3i();
final Vector2i yearTime = new Vector2i();
uniforms
.uniform1i(UniformUpdateFrequency.PER_FRAME, "frameCounter", COUNTER)
.uniform1f(UniformUpdateFrequency.PER_FRAME, "frameTime", TIMER::getLastFrameTime)
.uniform1f(UniformUpdateFrequency.PER_FRAME, "frameTimeCounter", TIMER::getFrameTimeCounter);
.uniform1f(UniformUpdateFrequency.PER_FRAME, "frameTimeCounter", TIMER::getFrameTimeCounter)
.uniform3i(UniformUpdateFrequency.PER_TICK, "currentDate", () -> {
final LocalDateTime dt = dateTimeSnapshot();
return date.set(dt.getYear(), dt.getMonthValue(), dt.getDayOfMonth());
})
.uniform3i(UniformUpdateFrequency.PER_TICK, "currentTime", () -> {
final LocalDateTime dt = dateTimeSnapshot();
return time.set(dt.getHour(), dt.getMinute(), dt.getSecond());
})
.uniform2i(UniformUpdateFrequency.PER_TICK, "currentYearTime", () -> {
final LocalDateTime dt = dateTimeSnapshot();
final int elapsed = ((dt.getDayOfYear() - 1) * 86400) + (dt.getHour() * 3600) + (dt.getMinute() * 60) + dt.getSecond();
return yearTime.set(elapsed, (dt.toLocalDate().lengthOfYear() * 86400) - elapsed);
});
}
public static void addFloatFrameMod8Uniform(UniformHolder uniforms) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

Loaded 100 of 268 files, more files were not shown because too many files have changed in this diff. Show more