Compare commits

...
45 Commits
Author SHA1 Message Date
tiffit 5d1fabfca9 Add API for allowing other mods to have celeritas support (#1936) 2026-07-16 09:54:55 -05:00
tiffit 555b2b7dd2 Fix typo in PR template (#1937) 2026-07-14 00:10:41 -05:00
SuperSouprandJason Mitchell 7017786a7c CTM integration improvements (#1931)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-12 10:45:16 -05:00
Eclipse-Sol 1c49649685 Fix Streams not rendering correctly (#1932) 2026-07-12 10:41:36 -05:00
Caedis 2bae79c4ee Up default zoom speed to 20 (#1910) 2026-07-09 21:34:03 -04:00
Jason Mitchell c22935660a Reenable compute shaders (#1918) 2026-07-05 20:38:13 -07:00
tiffit 79c488102b CTM: Fix "flipped" value not set to false in setFace() (#1913) 2026-07-05 18:24:28 -07:00
Jason Mitchell cc1353b73a Fix infinite farplane causing incorrect culling in pdim with no clouds (#1917) 2026-07-05 18:18:17 -07:00
Eclipse-Sol d1e6dc8c94 Fix/bug fixes (#1905) 2026-07-04 13:06:36 -07:00
GribanovIvan ecbf8fea2f font: load bundled .ttf/.otf fonts from fontfiles/ and config/angelia/fonts/ (#1904) 2026-07-04 12:16:48 -07:00
tiffit 610209605f Compact CTM: Decouple block face and rendered face (#1907) 2026-07-04 03:22:04 +03:00
mist475andJason Mitchell 112aae3735 Fix lotr biome colours again (essentially copy of https://github.com/GTNewHorizons/Angelica/pull/25) (#1902)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-03 14:21:05 -07:00
RecursivePineapple 5844b1b5c1 Cubic Chunks Compatibility (#1808) 2026-07-03 12:41:04 -07:00
Jason Mitchell 05f01c17e6 No point continuously building clouds if we're not rendering them... (#1900) 2026-06-29 20:17:06 -07:00
SpacEagle17 f07d96316a Add shader setting search functionality and center selection (#1897) 2026-06-29 19:36:55 -07:00
Jason Mitchell 5b704d1bc2 Fix space elevator cable (#1896) 2026-06-28 16:33:58 -07:00
Jason Mitchell 26135124f4 Support more glDrawElement overloads in display lists (#1895) 2026-06-28 15:07:24 -07:00
Jason Mitchell 3abc89b7b2 Betweenlands fix (#1894) 2026-06-28 13:46:10 -07:00
Jason Mitchell 03d908d658 Update readme and version detectors (#1893) 2026-06-27 15:47:32 -07:00
Eclipse-Sol 76bf19c8f6 Majority shader fixes (#1868) 2026-06-27 14:48:51 -07:00
Eclipse-Sol b27834cbd9 Narrow and make damage overlay a config option (#1886) 2026-06-27 10:12:39 -07:00
sisyphussyandJason Mitchell eb0ecb0483 Fix deps (#1888)
Co-authored-by: Jason Mitchell <[email protected]>
2026-06-27 10:11:30 -07:00
Caedis 9acb0e67a7 feat: add smooth zoom option (#1890) 2026-06-27 09:57:21 -07:00
Jason Mitchell f4f9b56b17 End portal fix (#1891) 2026-06-27 09:56:21 -07:00
Midnight145 2196439e6c ClientEvent framework (primarily written for LiteloaderLoader) (#1843) 2026-06-25 20:13:18 -07:00
RedNicStone dd68c64239 Fix map rendering issue in xaero's map mods (#1880) 2026-06-21 14:13:11 -07:00
Jason Mitchell 0decc515e4 Fix universium shader (#1877) 2026-06-18 23:42:21 -07:00
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
Eclipse-Sol f92ce1d24e Fix enchant glint fix so that it looks proper (#1856) 2026-06-13 17:28:12 -07:00
sisyphussy a7fac16d97 VAO stuff (#1858) 2026-06-13 17:26:09 -07:00
Jason Mitchell 20c3caaed4 A few redirects for calls that DH was using that we weren't redirecting (#1859) 2026-06-13 16:50:44 -07:00
Jason Mitchell bf08cd50d9 Support glPixelStore in display lists (#1854) 2026-06-11 20:29:14 -07:00
Eclipse-Sol 805a266059 Support extended metas for NEID and EID (#1853) 2026-06-11 17:58:22 -07:00
Eclipse-Sol d3c9963280 Fix ctm crash due to null (#1852) 2026-06-11 18:02:08 -04:00
Eclipse-Sol 4630bc4fd8 Fix shaders not loading the correct dim (#1844) 2026-06-09 23:12:13 -07:00
Jason Mitchell ce32a3d3df Emulate glTexParameter(pname=GL_GENERATE_MIPMAP) on core (#1845) 2026-06-09 23:03:42 -07:00
350 changed files with 6355 additions and 1465 deletions

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# Description of your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- 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
+8 -3
View File
@@ -76,21 +76,21 @@ 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")
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
@@ -173,8 +173,13 @@ dependencies {
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
testRuntimeOnly("it.unimi.dsi:fastutil:8.5.18")
testRuntimeOnly("org.joml:joml:1.10.8")
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
}
+2
View File
@@ -96,6 +96,8 @@ dependencies {
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}")
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false }
// Test
testImplementation(sourceSets["stubs"].output)
+3 -1
View File
@@ -4,7 +4,9 @@ celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.10.0
gtnhlibVersion = 0.11.19
fastutilVersion = 8.5.18
jomlVersion = 1.10.8
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
@@ -1,5 +1,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -8,9 +13,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -182,8 +189,9 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
final FFPInputs ffp = FFPInputs.detect(source);
assignVertexInputLocations(transformer, ffp);
transformVertexAttributes(transformer, source, ffp);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
@@ -310,25 +318,25 @@ public class CompatShaderTransformer {
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
if (ffp.position()) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
if (ffp.color()) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
if (ffp.uv0()) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
if (ffp.uv1()) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
if (ffp.normal()) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
@@ -337,6 +345,145 @@ public class CompatShaderTransformer {
}
}
private static final int MAX_VERTEX_ATTRIBS = 16;
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
static FFPInputs detect(String source) {
return new FFPInputs(
source.contains("gl_Vertex") || source.contains("ftransform"),
source.contains("gl_Color"),
source.contains("gl_MultiTexCoord0"),
source.contains("gl_MultiTexCoord1"),
source.contains("gl_Normal"));
}
}
/** Adaptation of Mesa's assign_attribute_or_color_locations */
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
int used = 0;
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
final List<Input> toAssign = new ArrayList<>();
int index = 0;
for (TerminalNode storage : transformer.collectStorage()) {
final int tokenType = storage.getSymbol().getType();
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
ParseTree node = storage.getParent();
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
node = node.getParent();
}
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
if (decl.typeless_declaration() == null) continue;
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
final String typeArray = arraySuffix(typeSpec.array_specifier());
if (hasLayoutQualifier(decl)) {
final int loc = explicitLocation(decl);
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
if (loc >= 0) used |= slotMask(slots) << loc;
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
}
}
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
for (Input in : toAssign) {
final int loc = findAvailableSlots(used, in.slots());
if (loc < 0) continue;
used |= slotMask(in.slots()) << loc;
transformer.removeVariable(in.name());
transformer.variable = null;
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
}
}
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
}
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
if (arraySpec == null) return 1;
int product = 1;
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
if (dim.constant_expression() == null) continue;
try {
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {}
}
return product;
}
private static int baseSlots(String type) {
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
return dims.charAt(0) - '0';
}
return 1;
}
private static int slotMask(int slots) {
return (slots >= 32) ? -1 : (1 << slots) - 1;
}
private static int findAvailableSlots(int used, int needed) {
if (needed <= 0) return -1;
final int mask = slotMask(needed);
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
if (((mask << i) & ~used) == (mask << i)) return i;
}
return -1;
}
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return -1;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
if (layout == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
try {
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {
return -1;
}
}
}
}
return -1;
}
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
if (single.layout_qualifier() != null) return true;
}
return false;
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
@@ -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);
}
}
}
@@ -111,6 +111,7 @@ public class DisplayListManager {
int listMode,
CommandRecorder recorder,
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
@@ -131,6 +132,22 @@ public class DisplayListManager {
return currentRecorder != null && Thread.currentThread() == recordingThread;
}
public static UnsupportedOperationException unsupportedInList(String what) {
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() {
if (currentRecorder == null) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
final int depth = compilationStack.size() + 1;
return "while compiling display list " + glListId + " (" + mode + (depth > 1 ? ", nesting depth " + depth : "") + ")";
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
@@ -159,7 +176,9 @@ public class DisplayListManager {
* Only use this if you are 100% that the transforms will not be of any use after the display list draw call.
*/
public static void discardMatrixTransforms() {
resetRelativeTransform();
if (isRecording()) {
resetRelativeTransform();
}
}
/**
@@ -748,7 +767,7 @@ public class DisplayListManager {
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws,
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
@@ -782,11 +801,24 @@ 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();
} finally {
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
final boolean isNested = !compilationStack.isEmpty();
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
@@ -803,8 +835,9 @@ public class DisplayListManager {
final boolean hasCommands = !commandBuffer.isEmpty();
if (hasCommands) {
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
DisplayListVBO compiledBuffers = null;
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
@@ -824,16 +857,23 @@ public class DisplayListManager {
}
}
compiled = new CompiledDisplayList(commandBuffer.finish(), commandBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
currentRecorder = null;
} catch (Exception e) {
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
e.printStackTrace();
commandBuffer.delete();
currentRecorder = null;
if (compiledBuffers != null) {
compiledBuffers.delete();
}
return;
}
} else {
// Free the recorder even if empty
commandBuffer.delete();
currentRecorder = null;
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
compiled = CompiledDisplayList.EMPTY;
}
@@ -846,6 +886,10 @@ public class DisplayListManager {
if (LOG_DISPLAY_LIST_COMPILATION) {
logCompiledDisplayList(glListId, compiled, compilationStackTrace);
}
}
private static void popCompilationContext() {
final boolean isNested = !compilationStack.isEmpty();
// Handle nested compilation - restore parent context
if (isNested) {
@@ -856,6 +900,7 @@ public class DisplayListManager {
glListMode = parentContext.listMode;
currentRecorder = parentContext.recorder;
accumulatedDraws = parentContext.draws;
transformCallback = parentContext.callback;
compilationStackTrace = parentContext.stackTrace;
pendingTransformOps = parentContext.pendingOps;
multMatrixSources = parentContext.matrixSources;
@@ -865,16 +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;
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();
}
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
@@ -9,13 +9,13 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Software emulation of GL feedback mode
*/
@@ -127,12 +127,12 @@ public class FeedbackManager {
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
final int s = VAOManager.getAttribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
return VAOManager.getAttribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
@@ -151,7 +151,7 @@ public class FeedbackManager {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int elementSize = VAOManager.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
@@ -255,7 +255,7 @@ public class FeedbackManager {
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VertexAttribState.get(0).vboId;
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
if (positionVbo != 0 && positionVbo != previousVbo) {
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -9,7 +9,6 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
@@ -17,6 +16,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
@@ -83,6 +83,15 @@ 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";
final int quadCount = vertexCount / 4;
@@ -91,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);
}
@@ -106,6 +119,11 @@ 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();
@@ -124,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);
@@ -139,12 +161,20 @@ 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;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int bytesPerIndex = VAOManager.Attrib.glTypeSizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
@@ -153,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);
}
/**
@@ -7,7 +7,6 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@@ -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);
@@ -507,11 +517,6 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13.glSampleCoverage(value, invert);
@@ -817,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);
@@ -827,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);
@@ -1043,6 +1073,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -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);
@@ -125,7 +127,6 @@ public abstract class RenderBackend {
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void pixelStoref(int pname, float param) {}
public void sampleCoverage(float value, boolean invert) {}
@@ -200,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) {}
@@ -247,6 +253,7 @@ public abstract class RenderBackend {
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract boolean isVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
@@ -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");
}
}
@@ -1,15 +1,13 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
@@ -31,7 +29,6 @@ public final class ShaderManager {
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
@Getter
private boolean active = false;
@@ -57,10 +54,8 @@ public final class ShaderManager {
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
@@ -73,12 +68,6 @@ public final class ShaderManager {
public void enable() {
enabled = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
currentVertexFlags = format.getVertexFlags();
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
return false;
});
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -99,7 +88,7 @@ public final class ShaderManager {
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
public void preDraw() {
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
@@ -111,7 +100,12 @@ public final class ShaderManager {
}
}
GLStateManager.flushDeferredVertexAttribs(currentVertexFlags);
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
@@ -127,20 +121,6 @@ public final class ShaderManager {
uploadUniforms();
}
public void preDraw(int vertexFlags) {
currentVertexFlags = vertexFlags;
preDraw(
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
);
}
public void preDraw() {
preDraw(currentVertexFlags);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
@@ -177,22 +157,6 @@ public final class ShaderManager {
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
public int getCurrentVertexFlags() {return currentVertexFlags;}
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
public void onBindVertexArray(int vaoId) {
final int flags = vaoVertexFlags.get(vaoId);
if (flags != -1) {
currentVertexFlags = flags;
}
}
public void onDeleteVertexArray(int vaoId) {
vaoVertexFlags.remove(vaoId);
}
public void destroy() {
cache.destroy();
uniforms.destroy();
@@ -0,0 +1,321 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private static int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
current.ebo = boundEBO;
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
}
VAOData data = vaoMap.get(vaoId);
if (data == null) {
data = new VAOData();
vaoMap.put(vaoId, data);
}
current = data;
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
vaoMap.remove(vaoId);
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public int clientSideEnabledCount;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
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);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public static void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public static boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
/**
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
}
if (totalBytes == 0) return;
if (clientArraysVBO == 0) {
clientArraysVBO = RENDER_BACKEND.genBuffers();
}
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, clientArraysVBO);
if (totalBytes > clientArraysVBOCapacity) {
int newCap = Math.max(4096, clientArraysVBOCapacity);
while (newCap < totalBytes) newCap *= 2;
clientArraysVBOCapacity = newCap;
}
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, clientArraysVBOCapacity, GL15.GL_STREAM_DRAW);
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static final class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean genericPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -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);
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
@@ -15,11 +15,11 @@ public final class AttribSnapshot {
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
return stride != 0 ? stride : size * VAOManager.Attrib.glTypeSizeBytes(type);
}
public int typeSizeBytes() {
return VertexAttribState.Attrib.glTypeSizeBytes(type);
return VAOManager.Attrib.glTypeSizeBytes(type);
}
}
@@ -56,7 +56,7 @@ public final class AttribSnapshot {
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
@@ -67,11 +67,11 @@ public final class AttribSnapshot {
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.vboId == 0) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
final long typeBytes = (long) a.size * VAOManager.Attrib.glTypeSizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
@@ -103,9 +103,9 @@ public final class AttribSnapshot {
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
@@ -653,12 +653,19 @@ public final class CommandRecorder {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
memFree(buffer);
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
@@ -666,7 +673,10 @@ public final class CommandRecorder {
}
final int size = size();
buffer.limit(size);
return memRealloc(buffer, size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public DisplayListCommand[] getComplexObjects() {
@@ -5,7 +5,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -293,7 +293,39 @@ public final class ImmediateModeRecorder {
}
private static float readComponent(AttribReader r, int base, int component) {
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
final int typeSize = VAOManager.Attrib.glTypeSizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
@@ -3,14 +3,12 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -52,51 +50,69 @@ 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 = VertexAttribState.Attrib.glTypeSizeBytes(srcIndexType);
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null;
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);
@@ -106,11 +122,11 @@ public final class IndexedDrawCapture {
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
int enabledCount = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
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];
@@ -118,7 +134,7 @@ public final class IndexedDrawCapture {
final int[] types = new int[enabledCount];
final boolean[] normalized = new boolean[enabledCount];
int k = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
locations[k] = i;
@@ -137,6 +153,13 @@ public final class IndexedDrawCapture {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
final long readStart = relOffset + (long) minVtx * d.effectiveStride();
final long readEnd = relOffset + (long) maxVtx * d.effectiveStride() + (long) d.size() * d.typeSizeBytes();
if (readStart < 0 || readEnd > d.readBuffer().capacity()) {
GLStateManager.warnOnce("vtx-oob", "[IndexedDrawCapture] attrib {} read extent {} exceeds snapshot {} bytes - skipping", d.location(), readEnd, d.readBuffer().capacity());
return null;
}
if (a == 0) {
sharedSrcBuffer = d.readBuffer();
sharedSrcStride = d.effectiveStride();
@@ -169,12 +192,12 @@ public final class IndexedDrawCapture {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
QuadConverter.triangulateQuads(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
bakedDrawMode = GL11.GL_TRIANGLES;
} else {
bakedIndexCount = indicesCount;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
QuadConverter.widenIndices(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
@@ -184,14 +207,11 @@ public final class IndexedDrawCapture {
success = true;
return out;
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
final class PixelDataSnapshot {
private PixelDataSnapshot() {}
static @Nullable ByteBuffer copy(@Nullable ByteBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining()).order(ByteOrder.nativeOrder());
out.put(pixels);
out.flip();
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable IntBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asIntBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable FloatBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asFloatBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable DoubleBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 8).order(ByteOrder.nativeOrder());
out.asDoubleBuffer().put(pixels);
pixels.position(pos);
return out;
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexImage2DCmd(
int border,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexSubImage2DCmd(
int height,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexSubImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -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";
@@ -126,6 +128,7 @@ public class GLSMRedirector {
.add("glFrustum")
.add("glGetBoolean")
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetLight")
.add("glGetMaterial")
@@ -296,6 +299,7 @@ public class GLSMRedirector {
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("nglShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
@@ -321,6 +325,7 @@ public class GLSMRedirector {
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
@@ -342,6 +347,7 @@ public class GLSMRedirector {
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -352,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()
@@ -455,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()
@@ -587,18 +602,16 @@ public class GLSMRedirector {
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
final String glsmName = redirects != null ? redirects.get(mNode.name) : null;
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
}
}
// Redirect <init> calls for quadric classes
@@ -617,8 +630,7 @@ public class GLSMRedirector {
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null;
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,198 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
private static int clientSideEnabledCount;
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
clientSideEnabledCount = 0;
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
clientSideEnabledCount = 0;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (was) clientSideEnabledCount--;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
final boolean now = a.isClientSide();
if (was != now) clientSideEnabledCount += now ? 1 : -1;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].isClientSide()) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -7,7 +7,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -204,14 +203,8 @@ public class TessellatorStreamingDrawer {
firstVertex = 0;
}
GLStateManager.prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw(flags);
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_NORMAL_ARRAY);
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
@@ -17,6 +17,7 @@ public class TextureInfo {
@Getter @Setter protected int magFilter = GL11.GL_LINEAR;
@Getter @Setter protected int wrapS = GL11.GL_REPEAT;
@Getter @Setter protected int wrapT = GL11.GL_REPEAT;
@Getter @Setter protected int baseLevel = 0;
@Getter @Setter protected int maxLevel = 1000;
@Getter @Setter protected int minLod = -1000;
@Getter @Setter protected int maxLod = 1000;
@@ -24,6 +25,8 @@ public class TextureInfo {
@Getter @Setter protected float maxAnisotropy = 1.0f;
@Getter @Setter protected boolean generateMipmap = false;
protected TextureInfo(int id) {
this.id = id;
}
@@ -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;
@@ -0,0 +1,215 @@
package com.gtnewhorizons.angelica.glsm;
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 java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(GLSMCoreExtension.class)
public class GLSM_DisplayList_PixelStore_Test {
private static final int SRC = 8;
private static final int SUB = 4;
@AfterEach
void resetUnpackState() {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
setUnpack(0, 0, 0);
}
private static void setUnpack(int rowLength, int skipRows, int skipPixels) {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, rowLength);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, skipRows);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, skipPixels);
}
private static void setStridedUnpack() {
setUnpack(SRC, 1, 2);
}
/** RGBA source; red channel = y*size+x, alpha = 255. */
private static ByteBuffer makeSource(int size) {
final ByteBuffer buf = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < size * size; i++) {
buf.put((byte) i).put((byte) 0).put((byte) 0).put((byte) 255);
}
buf.flip();
return buf;
}
private static int makeTexture(int size, ByteBuffer pixels) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return tex;
}
private static ByteBuffer readback(int size) {
final ByteBuffer out = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
return out;
}
private static int red(ByteBuffer pixels, int size, int x, int y) {
return pixels.get((y * size + x) * 4) & 0xFF;
}
private static void assertStridedSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals((y + 1) * SRC + (x + 2), red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertTightSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals(y * SUB + x, red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertUnpack(int rowLength, int skipRows, int skipPixels) {
assertEquals(rowLength, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH));
assertEquals(skipRows, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(skipPixels, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_PIXELS));
}
@Test
void pixelStoreExecutesImmediatelyAndIsNotRecorded() {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 7);
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must execute immediately during recording");
GLStateManager.glEndList();
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must persist after glEndList");
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GLStateManager.glCallList(list);
assertEquals(0, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must not have been recorded into the list");
GLStateManager.glDeleteLists(list, 1);
}
@Test
void stridedTexSubImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertEquals(0, red(readback(SUB), SUB, 0, 0), "GL_COMPILE must not execute the upload");
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexSubImage2DCompileAndExecute() {
final ByteBuffer zeros = ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder());
final int tex = makeTexture(SUB, zeros);
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertStridedSubImage(readback(SUB));
setUnpack(0, 0, 0);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zeros);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexSubImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.util.GLSMUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -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");
@@ -133,7 +195,7 @@ public class GLSM_VAO_UnitTest {
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.boundVAO = array;
VertexAttribState.onBindVertexArray(array);
VAOManager.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HugeFarPlaneFrustumTest {
private static final float SECTION_HALF = 8.0f + 1.0f + 0.125f;
private static final float DEGENERATE_FAR = 2.147483648e9f;
private static Matrix4f combined(float zFar) {
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 854.0f / 480.0f, 0.05f, zFar);
final Matrix4f mv = new Matrix4f()
.rotate((float) Math.toRadians(25.0), 1, 0, 0)
.rotate((float) Math.toRadians(123.0), 0, 1, 0)
.translate(0.0f, -1.62f, 0.0f);
return new Matrix4f().set(proj).mul(mv);
}
private static boolean sectionVisible(FrustumIntersection f, int sx, int sy, int sz) {
final float cx = sx * 16.0f + 8.0f;
final float cy = sy * 16.0f + 8.0f;
final float cz = sz * 16.0f + 8.0f;
return f.testAab(cx - SECTION_HALF, cy - SECTION_HALF, cz - SECTION_HALF, cx + SECTION_HALF, cy + SECTION_HALF, cz + SECTION_HALF);
}
@Test
void unnormalizedFrustumMatchesNormalFarPlane() {
final FrustumIntersection normal = new FrustumIntersection(combined(512.0f), false);
final FrustumIntersection huge = new FrustumIntersection(combined(DEGENERATE_FAR), false);
int visible = 0;
int mismatches = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
final boolean vNormal = sectionVisible(normal, x, y, z);
if (vNormal) visible++;
if (vNormal != sectionVisible(huge, x, y, z)) mismatches++;
}
}
}
assertTrue(visible > 100, "sanity check broken: normal frustum accepted only " + visible + " sections");
assertEquals(0, mismatches, "sections culled differently under the degenerate far plane");
}
@Test
void normalizedSetCullsEverythingOnDegenerateFarPlane() {
final FrustumIntersection normalized = new FrustumIntersection();
normalized.set(combined(DEGENERATE_FAR));
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (sectionVisible(normalized, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "set(m) with a degenerate far plane no longer culls everything");
}
@Test
void identityFrustumOnlyPassesCameraSection() {
final FrustumIntersection identity = new FrustumIntersection(new Matrix4f());
assertTrue(sectionVisible(identity, 0, 0, 0), "identity frustum rejected the camera's own section");
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (Math.abs(x) <= 1 && Math.abs(y) <= 1 && Math.abs(z) <= 1) continue;
if (sectionVisible(identity, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "non-adjacent sections accepted by the identity frustum");
}
}
@@ -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,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, org.lwjgl.opengl.GL11.GL_COMPILE);
@@ -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");
}
}
@@ -3,12 +3,15 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
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 {
@@ -307,10 +314,10 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VertexAttribState.set(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData, 0);
VertexAttribState.setEnabled(0, true);
VertexAttribState.set(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice, 0);
VertexAttribState.setEnabled(1, true);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -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++) {
@@ -0,0 +1,61 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelUnpackStateTest {
@Test
void defaults() {
assertTrue(PixelUnpackState.DEFAULT.isDefault());
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelUnpackState s = PixelUnpackState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packAndUnknownPnamesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
}
}
+5 -1
View File
@@ -34,6 +34,10 @@ repositories {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Minecraft Libraries"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
@@ -57,7 +61,7 @@ dependencies {
api(project(":glsm"))
compileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.15:dev") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { isTransitive = false }
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
@@ -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);
@@ -544,11 +554,6 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL11C.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11C.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13C.glSampleCoverage(value, invert);
@@ -861,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);
@@ -871,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);
@@ -1081,6 +1111,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30C.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20C.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -1371,11 +1406,14 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public boolean supportsDebugOutput() {
return caps != null && caps.OpenGL43;
return caps != null && (caps.OpenGL43 || caps.GL_KHR_debug);
}
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
debugCallback = GLDebugMessageCallback.create(
(source, type, id, severity, length, message, userParam) -> {
final String msg = GLDebugMessageCallback.getMessage(length, message);
@@ -1403,6 +1441,9 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int disableDebugOutput() {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
GL43C.glDebugMessageCallback(null, 0L);
if (debugCallback != null) {
debugCallback.free();
+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,);\
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.api;
public interface ExtCeleritasRenderBlocks {
boolean angelica$shouldApplyCeleritasAO();
void angelica$setApplyingCeleritasAO(boolean val);
}
@@ -11,9 +11,13 @@ import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.Loader;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -27,13 +31,13 @@ public class FontStrategist {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
static {
HashMap<String, Font> fontSet = new HashMap<>();
if (GraphicsEnvironment.isHeadless()) {
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
availableFonts = new Font[0];
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Only bundled fonts will be available. This is likely a MacOS issue.");
} else {
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
HashMap<String, Font> fontSet = new HashMap<>();
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
for (Font font : availableFontsDirty) {
@@ -57,11 +61,14 @@ public class FontStrategist {
sb.append(". Some fonts may be missing from the font selection menu.");
LOGGER.warn(sb.toString());
}
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, fontSet.size());
}
loadBundledFonts(fontSet);
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
@@ -74,6 +81,44 @@ public class FontStrategist {
defaultResourcePacks.add(fontResourcePack);
}
// Load .ttf/.otf shipped with the pack so customFontName* can point at a font that isn't installed
// on the system. fontfiles/ is SmoothFont's folder, kept so packs built for it work unchanged.
private static void loadBundledFonts(HashMap<String, Font> fontSet) {
File configDir = Loader.instance().getConfigDir();
if (configDir == null) {
LOGGER.warn("Loader.instance().getConfigDir() returned null. Bundled fonts will not be loaded.");
return;
}
File parent = configDir.getParentFile();
File[] fontDirs = { new File(parent, "fontfiles"), new File(configDir, "angelica/fonts") };
GraphicsEnvironment ge = GraphicsEnvironment.isHeadless() ? null : GraphicsEnvironment.getLocalGraphicsEnvironment();
int loaded = 0;
for (File dir : fontDirs) {
File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf") || name.toLowerCase().endsWith(".otf"));
if (files == null) continue;
for (File file : files) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, file);
String name = font.getFontName();
if (fontSet.containsKey(name)) {
String deduped = name;
int n = 1;
while (fontSet.containsKey(deduped)) deduped = name + "_" + n++;
LOGGER.warn("Font name {} (from {}) is already registered; registering this one as {}", name, file.getName(), deduped);
name = deduped;
}
if (ge != null) ge.registerFont(font);
fontSet.put(name, font);
loaded++;
LOGGER.info("Loaded font {} from {}/{}", name, dir.getName(), file.getName());
} catch (FontFormatException | IOException e) {
LOGGER.error("Couldn't load font {}", file.getPath(), e);
}
}
}
if (loaded > 0) LOGGER.info("Loaded {} bundled font(s) from the pack", loaded);
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
@@ -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();
@@ -1,12 +1,14 @@
package com.gtnewhorizons.angelica.compat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusEvents;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
@@ -14,6 +16,8 @@ public class ModStatus {
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isEIDLoaded;
public static boolean isMetadataExtended;
public static boolean isLotrLoaded;
public static boolean isChunkAPILoaded;
public static boolean isEIDBiomeLoaded;
@@ -26,7 +30,9 @@ public class ModStatus {
public static boolean isBaublesLoaded;
public static boolean isCosmeticArmorReworkedLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isCubicChunksLoaded;
public static boolean isBOPLoaded;
public static boolean isEtFuturumLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
@@ -35,9 +41,9 @@ public class ModStatus {
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isEIDLoaded = Loader.isModLoaded("endlessids");
isLotrLoaded = Loader.isModLoaded("lotr");
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
@@ -46,6 +52,8 @@ public class ModStatus {
isBaublesLoaded = Loader.isModLoaded("Baubles");
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isCubicChunksLoaded = Loader.isModLoaded("cubicchunks");
isEtFuturumLoaded = Loader.isModLoaded("etfuturum");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBOPLoaded = Loader.isModLoaded("BiomesOPlenty");
@@ -65,5 +73,19 @@ public class ModStatus {
isNEIDMetadataExtended = true;
}
}
if (isCubicChunksLoaded) {
CubeStatusEvents.init();
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
eidBlockItemExtended = GeneralConfig.extendBlockItem;
eidBiomeExtended = GeneralConfig.extendBiome;
}
isEIDBiomeLoaded = eidBiomeExtended || Loader.isModLoaded("endlessids_biome");
isMetadataExtended = isNEIDMetadataExtended || eidBlockItemExtended;
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.compat.cubicchunks;
import net.minecraft.world.World;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import com.cardinalstar.cubicchunks.api.IColumn;
import com.cardinalstar.cubicchunks.api.ICube;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.cardinalstar.cubicchunks.world.cube.Cube;
import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
public class CubicChunksAPI {
public static ExtendedBlockStorage getCubeStorage(World world, int x, int y, int z) {
Cube cube = ((ICubeProviderInternal) world.getChunkProvider()).getLoadedCube(x, y, z);
if (cube == null || cube.isEmpty()) return null;
return cube.getStorage();
}
public static int[] getLoadedCubeLevelsInColumn(World world, int chunkX, int chunkZ) {
IColumn column = (IColumn) world.getChunkFromChunkCoords(chunkX, chunkZ);
return column.getLoadedCubes().stream().mapToInt(ICube::getY).toArray();
}
public static int getMinHeight(World world) {
return ((ICubicWorld) world).getMinHeight();
}
public static int getMaxHeight(World world) {
return ((ICubicWorld) world).getMaxHeight();
}
public static int getMinSectionY(World world) {
return ChunkSectionPos.getSectionCoord(getMinHeight(world));
}
public static int getMaxSectionYExclusive(World world) {
return ChunkSectionPos.getSectionCoord(getMaxHeight(world) - 1) + 1;
}
}
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.compat.etfuturum;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.relauncher.ReflectionHelper;
import lombok.Getter;
import net.minecraft.entity.player.EntityPlayer;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public final class EtFuturumCompat {
private static final String IELYTRA_PLAYER = "ganymedes01.etfuturum.api.elytra.IElytraPlayer";
@Getter
private static boolean loaded;
private static Class<?> elytraPlayerClass;
private static final MethodHandle isElytraFlyingHandle;
static {
MethodHandle handle = null;
try {
if (Loader.isModLoaded("etfuturum")) {
elytraPlayerClass = ReflectionHelper.getClass(EtFuturumCompat.class.getClassLoader(), IELYTRA_PLAYER);
handle = MethodHandles.lookup().findVirtual(elytraPlayerClass, "etfu$isElytraFlying",
MethodType.methodType(boolean.class));
loaded = true;
}
} catch (Throwable t) {
LOGGER.error("Failed to load Et Futurum Requiem elytra compat", t);
loaded = false;
}
isElytraFlyingHandle = handle;
}
private EtFuturumCompat() {
}
public static boolean isElytraFlying(EntityPlayer player) {
if (!loaded || isElytraFlyingHandle == null || !elytraPlayerClass.isInstance(player)) {
return false;
}
try {
return (boolean) isElytraFlyingHandle.invoke(player);
} catch (Throwable t) {
return false;
}
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.compat.lotr;
import net.minecraft.world.WorldProvider;
public final class LOTRCompat {
private static final Class<?> LOTR_WORLD_PROVIDER_CLASS;
static {
Class<?> clazz = null;
try {
clazz = Class.forName("lotr.common.world.LOTRWorldProvider", false, LOTRCompat.class.getClassLoader());
} catch (ClassNotFoundException ignored) {}
LOTR_WORLD_PROVIDER_CLASS = clazz;
}
private LOTRCompat() {}
public static boolean isLotrProvider(WorldProvider provider) {
return LOTR_WORLD_PROVIDER_CLASS != null && LOTR_WORLD_PROVIDER_CLASS.isInstance(provider);
}
}
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import org.joml.Vector3i;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
// See if we can merge/mixin/extend ChunkPosition maybe?
public class ChunkSectionPos extends Vector3i {
@@ -25,11 +27,8 @@ public class ChunkSectionPos extends Vector3i {
}
public static long asLong(int x, int y, int z) {
long l = 0L;
l |= ((long)x & 4194303L) << 42;
l |= ((long)y & 1048575L) << 0;
l |= ((long)z & 4194303L) << 20;
return l;
// Use CoordinatePacker so that cubic chunks can change the bit packing layout
return CoordinatePacker.pack(x, y, z);
}
public static int getLocalCoord(int coord) {
@@ -1,5 +1,9 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.util.MathHelper;
import com.gtnewhorizons.angelica.compat.ModStatus;
public class CompatMathHelper {
public static int smallestEncompassingPowerOfTwo(int value) {
int j = value - 1;
@@ -24,4 +28,13 @@ public class CompatMathHelper {
return k == 0 ? value : value + divisor - k;
}
}
public static int clampHeight(int y) {
if (ModStatus.isCubicChunksLoaded) {
// no clamping
return y;
} else {
return MathHelper.clamp_int(y, 0, 255);
}
}
}
@@ -227,13 +227,13 @@ public class AngelicaConfig {
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
"While using \"Compact Vertex Format\" makes the situation even worse.",
"This option fixes it.",
"Requires texture reloading (F3 + T) after changing this option to take effect"})
@Config.DefaultBoolean(true)
public static boolean fixBlockCrack;
"This option fixes it, though may lead to other visual artifacts.",
"Requires game restart after changing this option to take effect"})
@Config.DefaultBoolean(false)
public static boolean blockCrackFix;
@Config.Comment({
"The \"epsilon\" value for the fixBlockCrack option. ",
"The \"epsilon\" value for the blockCrackFix option. ",
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
"May cause intense flickering (z-fighting) between blocks if the value is too high"
})
@@ -241,7 +241,7 @@ public class AngelicaConfig {
@Config.DefaultDouble(0.001)
public static double blockCrackFixEpsilon;
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
@Config.Comment("Block classes that have bugs when rendering with the blockCrackFix can be put here to avoid manipulating them")
@Config.DefaultStringList({
"net.minecraft.block.BlockCauldron",
"net.minecraft.block.BlockStairs"
@@ -249,7 +249,7 @@ public class AngelicaConfig {
public static String[] blockCrackFixBlacklist;
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
"Add a block class here if you see flickering (z-fighting) with blockCrackFix enabled"
})
@Config.DefaultStringList({
"gregtech.common.blocks.BlockOres",
@@ -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)
@@ -67,6 +83,13 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.vbo.MixinRenderGlobal")
),
ANGELICA_VBO_CLOUDS_FAR_PLANE(
new MixinBuilder()
.setApplyIf(() -> AngelicaConfig.enableVBOClouds && !AngelicaConfig.enableNotFineFeatures)
.setPhase(Phase.EARLY)
.addClientMixins("notfine.clouds.MixinEntityRenderer")
),
ANGELICA_PANORAMA_BLUR(
new MixinBuilder("Replace main menu panorama with modern equivalent")
.setPhase(Phase.EARLY)
@@ -117,7 +140,7 @@ public enum Mixins implements IMixins {
.setPhase(Phase.EARLY)
.addClientMixins("angelica.bugfixes.MixinRenderBlocks_CrackFix")
.addExcludedMod(TargetedMod.FALSETWEAKS)
.setApplyIf(() -> AngelicaConfig.fixBlockCrack)),
.setApplyIf(() -> AngelicaConfig.blockCrackFix)),
ANGELICA_FIX_FLUID_RENDERER_CHECKING_BLOCK_AGAIN(
new MixinBuilder("Fix RenderBlockFluid reading the block type from the world access multiple times")
@@ -188,6 +211,8 @@ public enum Mixins implements IMixins {
.addClientMixins(
"rendering.MixinBlock"
, "rendering.MixinBlockFluidBase"
, "rendering.MixinBlockLiquid"
, "rendering.BlockLiquidFlowInvoker"
, "rendering.AccessorBiomeColorEvent"
, "rendering.MixinBiomeGenBase"
, "rendering.MixinChunk"
@@ -215,8 +240,7 @@ public enum Mixins implements IMixins {
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
.addClientMixins(
"celeritas.terrain.ChunkTrackerAccessor"
, "celeritas.terrain.MixinChunkProviderClient"
"celeritas.terrain.MixinChunkProviderClient"
, "celeritas.terrain.MixinMinecraft_ChunkUpdates"
, "celeritas.terrain.MixinRenderGlobal"
, "celeritas.terrain.MixinRenderSectionManager"
@@ -282,6 +306,9 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderEndPortal"
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
, "shaders.MixinTextureManager_ReloadCount"
, "shaders.AccessorModelBox"
, "shaders.MixinModelBiped"
)
),
@@ -390,6 +417,12 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.removeUnicodeEvenScaling)
.addClientMixins("angelica.bugfixes.MixinScaledResolution_UnicodeFix")),
FARSEEK_WORLDSLICE_COMPAT(new MixinBuilder("Let Farseek resolve celeritas' WorldSlice so Streams' water renders properly")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.FARSEEK)
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
.addClientMixins("client.farseek.MixinFarseekIBlockAccessValue")),
SECURITYCRAFT_COMPAT(new MixinBuilder("Fix reflection in SecurityCraft for compat with Angelica")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.SECURITYCRAFT)
@@ -17,6 +17,7 @@ public enum TargetedMod implements ITargetMod {
DRAGON_API("Reika.DragonAPI.Auxiliary.DragonAPIASMHandler", "DragonAPI"),
ET_FUTURUM_REQUIEM(null, "etfuturum"),
EXTRAUTILS(null, "ExtraUtilities"),
FARSEEK("farseek.core.FarseekCoreMod", "farseek"),
FALSETWEAKS("com.falsepattern.falsetweaks.asm.CoreLoadingPlugin", "falsetweaks"),
MINEFACTORY_RELOADED(null, "MineFactoryReloaded"),
NTM_SPACE("com.hbm.dim.SolarSystem"),
@@ -28,6 +29,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;
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
public interface ChunkTrackerAccessor {
Long2IntOpenHashMap angelica$getChunkStatus();
LongOpenHashSet angelica$getChunkReady();
LongSet angelica$getUnloadQueue();
LongSet angelica$getLoadQueue();
}
@@ -1,8 +1,11 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import org.joml.FrustumIntersection;
import org.joml.Matrix4fc;
public interface ClippingHelperExt {
FrustumIntersection celeritas$getFrustumIntersection();
void celeritas$updateFrustumIntersection();
Matrix4fc celeritas$getCombinedMatrix();
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import java.util.Map;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
public interface IChunkTileEntityMapHolder {
ConcurrentTileEntityMap angelica$getConcurrentTEMap();
@@ -5,5 +5,5 @@ import net.minecraft.util.AxisAlignedBB;
public interface ITileEntityBoundingBoxCache {
AxisAlignedBB angelica$getCachedRenderBoundingBox();
boolean angelica$isInfiniteExtent();
byte angelica$boundsClass();
}
@@ -1,5 +1,35 @@
package com.gtnewhorizons.angelica.proxy;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import biomesoplenty.api.content.BOPCBlocks;
import com.google.common.base.Objects;
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
@@ -8,7 +38,9 @@ import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.debug.F3Direction;
@@ -52,34 +84,6 @@ import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import net.coderbot.iris.Iris;
import net.coderbot.iris.client.IrisDebugScreenHandler;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
public final class ClientProxy extends CommonProxy {
@@ -102,6 +106,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);
@@ -291,10 +296,10 @@ public final class ClientProxy extends CommonProxy {
event.left.set(i + 4, String.format(
"lc: %d b: %s bl: %d sl: %d rl: %d",
chunk.getTopFilledSegment() + 15,
chunk.getBiomeGenForWorldCoords(bX & 15, bZ & 15, mc.theWorld.getWorldChunkManager()).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getBlockLightValue(bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15, 0)));
this.mc.theWorld.getBiomeGenForCoords(bX & 15, bZ & 15).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getBlockLightValue(bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15, 0)));
}
}
// Draw a frametime graph
@@ -26,8 +26,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import jss.notfine.core.Settings;
import jss.notfine.gui.options.named.GraphicsQualityOff;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.shaderpack.CloudSetting;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
@@ -45,6 +49,7 @@ import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -76,6 +81,8 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private static final float SCROLL_SPEED = 1.0f / 256.0f;
private static final float EDGE_OVERLAP = 0.0001f;
private static final float INTERIOR_INSET = EDGE_OVERLAP * 8.0f;
private static final float MAX_FAR_PLANE_DISTANCE = 65536.0f;
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
@@ -103,11 +110,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
private int anchorMarginCells = 0;
private int cachedEdgesGeneration = Integer.MIN_VALUE;
private boolean cachedEmitTopFace, cachedEmitBottomFace, cachedCameraInsideCloud;
private boolean cachedEmitTopFace, cachedEmitBottomFace;
private int cachedRenderRadius = -1;
private float cachedCloudInteriorHeight = Float.NaN;
private boolean geomBuilt;
private int cloudMode = -1, renderDistance = -1, cloudElevation = -1, scaleMult = -1;
private boolean enabled;
private ByteBuffer emitBuffer;
private long emitBufferAddr;
@@ -136,6 +145,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private GlProgram<CloudUniforms> program;
private final Matrix4f mvpScratch = new Matrix4f();
private final Matrix4f mvScratch = new Matrix4f();
public CloudRenderer() {
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this);
@@ -160,10 +170,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff) Settings.MODE_CLOUDS.option.getStore();
final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY
|| cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1;
final float dimCloudHeight = mc.theWorld == null ? 128.0f : mc.theWorld.provider.getCloudHeight();
final boolean finiteCloudHeight = Float.isFinite(dimCloudHeight);
final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF
&& mc.gameSettings.shouldRenderClouds()
&& mc.theWorld != null
&& mc.theWorld.provider.isSurfaceWorld();
&& mc.theWorld.provider.isSurfaceWorld()
&& finiteCloudHeight;
final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int) Settings.RENDER_DISTANCE_CLOUDS.option.getStore());
final int cloudScaleMult = (int) Settings.CLOUD_SCALE.option.getStore();
@@ -174,11 +187,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
invalidateGeometry();
}
enabled = newEnabled;
cloudMode = cloudQualitySetting;
renderDistance = targetDistance;
scaleMult = cloudScaleMult;
cloudElevation = mc.theWorld == null ? 128 : (int) mc.theWorld.provider.getCloudHeight();
cloudElevation = finiteCloudHeight ? (int) dimCloudHeight : 128;
// Allows the setting to work with RFG and similar without hardcoding.
// The minimum height check is so stuff like Aether cloud height doesn't get messed up.
if (cloudElevation >= 96) {
@@ -186,6 +200,26 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
/**
* Far-plane distance needed so none of the cloud geometry gets clipped.
*/
public float getRequiredFarPlaneDistance() {
if (!enabled || scaleMult <= 0 || renderDistance <= 0) return 0.0f;
if (mc.theWorld == null) return 0.0f;
final float cloudInteriorWidth = 12.0f * scaleMult;
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
float radiusBlocks = (renderRadius + marginChunks) * CELLS_PER_CHUNK * cloudInteriorWidth;
if (cloudMode != 2) radiusBlocks *= (float) Math.sqrt(2);
float verticalSlack = cloudElevation;
final Entity view = mc.renderViewEntity;
if (view != null) {
verticalSlack = Math.abs(cloudElevation + 0.33f - (float) view.posY) + 4.0f * scaleMult;
}
return Math.min(radiusBlocks + verticalSlack, MAX_FAR_PLANE_DISTANCE);
}
private void invalidateGeometry() {
anchorCellX = Integer.MIN_VALUE;
anchorCellZ = Integer.MIN_VALUE;
@@ -193,6 +227,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
cachedEdgesGeneration = Integer.MIN_VALUE;
cachedCloudInteriorHeight = Float.NaN;
cachedVertexCount = 0;
geomBuilt = false;
}
private void initProgram() {
@@ -215,23 +250,30 @@ public class CloudRenderer implements IResourceManagerReloadListener {
program.unbind();
}
private void uploadFogUniforms(float partialTicks, int renderRadius, float cloudInteriorWidth) {
private void uploadFogUniforms() {
final CloudUniforms u = program.getInterface();
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
u.setFogEnabled(fogEnabled);
if (fogEnabled) {
final float start = renderDistance * 16.0f;
final float end = renderRadius * CELLS_PER_CHUNK * cloudInteriorWidth;
final float range = end - start;
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? end / range : 1.0f);
final Vec3 sky = mc.theWorld.getSkyColor(mc.renderViewEntity, partialTicks);
u.setFogColor((float) sky.xCoord, (float) sky.yCoord, (float) sky.zCoord);
if (!fogEnabled) return;
final FogState fog = GLStateManager.getFogState();
final Vector3d color = fog.getFogColor();
u.setFogColor((float) color.x, (float) color.y, (float) color.z);
switch (fog.getFogMode()) {
case GL11.GL_LINEAR -> {
final float range = fog.getEnd() - fog.getStart();
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fog.getEnd() / range : 1.0f, 0.0f, 0.0f);
}
case GL11.GL_EXP -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
default -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
}
}
public boolean render(int cloudTicks, float partialTicks) {
if (mc.theWorld == null || mc.renderViewEntity == null) return false;
if (cloudMode != 2 || scaleMult <= 0) return false;
if (!enabled || scaleMult <= 0) return false;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline != null && pipeline.getCloudSetting() == CloudSetting.OFF) return false;
boolean textureSetupDone = false;
if (edges == null) {
@@ -266,41 +308,45 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final boolean emitTopFace = cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean emitBottomFace = cameraRelativeY <= 0.0F;
final boolean cameraInsideY = cameraRelativeY <= 0.0F && cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean cameraInsideCloud = cameraInsideY && edges.cellOpaque(anchorX, anchorZ);
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final boolean anchorInRange = vao != null
&& Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells;
final boolean insideBand = cloudMode == 2 && emitTopFace && emitBottomFace;
final boolean anchorInRange = geomBuilt
&& (insideBand
? anchorX == anchorCellX && anchorZ == anchorCellZ
: Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells);
final boolean geomCacheValid = anchorInRange
&& cachedEdgesGeneration == edgesGeneration
&& cachedEmitTopFace == emitTopFace
&& cachedEmitBottomFace == emitBottomFace
&& cachedCameraInsideCloud == cameraInsideCloud
&& cachedRenderRadius == renderRadius
&& cachedCloudInteriorHeight == cloudInteriorHeight;
if (!geomCacheValid) {
final int marginCells = cameraInsideCloud ? 0 : Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, cameraInsideCloud, marginCells);
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, marginCells);
anchorCellX = anchorX;
anchorCellZ = anchorZ;
anchorMarginCells = marginCells;
cachedEdgesGeneration = edgesGeneration;
cachedEmitTopFace = emitTopFace;
cachedEmitBottomFace = emitBottomFace;
cachedCameraInsideCloud = cameraInsideCloud;
cachedRenderRadius = renderRadius;
cachedCloudInteriorHeight = cloudInteriorHeight;
geomBuilt = true;
}
if (vao == null || cachedVertexCount == 0) return true;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
if (cloudTexId != -1 && !textureSetupDone) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cloudTexId);
} else {
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
}
final Vec3 color = mc.theWorld.getCloudColour(partialTicks);
float r = (float) color.xCoord;
@@ -337,10 +383,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
mv.pushMatrix();
mv.scale(cloudInteriorWidth, 1.0f, cloudInteriorWidth);
mv.translate(-(cameraRelativeX + anchorDriftX), cameraRelativeY, -(cameraRelativeZ + anchorDriftZ));
mvScratch.set(mv);
GLStateManager.getProjectionMatrix().mul(mv, mvpScratch);
mv.popMatrix();
u.mvp.set(mvpScratch);
uploadFogUniforms(partialTicks, renderRadius, cloudInteriorWidth);
u.mv.set(mvScratch);
uploadFogUniforms();
drawAllFactors(r, g, b, false);
program.unbind();
@@ -362,15 +410,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private void drawAllFactors(float r, float g, float b, boolean shadersActive) {
vao.bind();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GLStateManager.disableCull();
GLStateManager.disableBlend();
GLStateManager.glDepthMask(true);
GLStateManager.glColorMask(false, false, false, false);
// Depth prepass: alpha must match color pass so discard threshold writes same depth coverage.
if (shadersActive) {
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, CloudUniforms.ALPHA);
} else {
program.getInterface().setColorMult(1.0f, 1.0f, 1.0f);
}
final int totalQuads = cachedVertexCount / 4;
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, totalQuads * 6, GL11.GL_UNSIGNED_INT, 0L);
@@ -403,25 +449,32 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
vao.unbind();
GLStateManager.enableCull();
GLStateManager.glDepthMask(true);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.disableBlend();
}
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, boolean cameraInsideCloud, int marginCells) {
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, int marginCells) {
prepareEmitBuffer();
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
final int chunkRange = cameraInsideCloud ? 0 : (renderRadius + marginChunks);
final int outerLo = cameraInsideCloud ? -1 : -chunkRange;
final int outerHi = cameraInsideCloud ? 0 : chunkRange;
final int chunkRange = renderRadius + marginChunks;
final int outerLo = -chunkRange;
final int outerHi = chunkRange;
final int radiusCellsWithMargin = cameraInsideCloud ? CELLS_PER_CHUNK : (chunkRange * CELLS_PER_CHUNK);
final int radiusCellsWithMargin = chunkRange * CELLS_PER_CHUNK;
final int radiusCellsSq = radiusCellsWithMargin * radiusCellsWithMargin;
final float scrollX = anchorX * SCROLL_SPEED;
final float scrollZ = anchorZ * SCROLL_SPEED;
if (cloudMode != 2) {
emitFastLayer(chunkRange * CELLS_PER_CHUNK, scrollX, scrollZ);
finishGeometry();
return;
}
final int rows = (outerHi - outerLo + 1) * CELLS_PER_CHUNK;
final int lpr = (rows + 63) >>> 6;
final int minX = outerLo * CELLS_PER_CHUNK;
@@ -446,7 +499,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
for (int chunkX = outerLo; chunkX <= outerHi; chunkX++) {
for (int chunkZ = outerLo; chunkZ <= outerHi; chunkZ++) {
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, cameraInsideCloud, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
}
}
@@ -454,6 +507,53 @@ public class CloudRenderer implements IResourceManagerReloadListener {
emitTopBottomGlobalGreedy(emitTopFace, emitBottomFace, ye, scrollX, scrollZ);
}
if (emitTopFace && emitBottomFace) {
emitInteriorFaces(anchorX, anchorZ, y1, scrollX, scrollZ);
}
finishGeometry();
}
private void emitInteriorFaces(int anchorX, int anchorZ, double y1, float scrollX, float scrollZ) {
for (int dz = -1; dz <= 1; dz++) {
for (int dx = -1; dx <= 1; dx++) {
if (!edges.cellOpaque(anchorX + dx, anchorZ + dz)) continue;
final double x0 = dx + INTERIOR_INSET;
final double x1 = dx + 1 - INTERIOR_INSET;
final double z0 = dz + INTERIOR_INSET;
final double z1 = dz + 1 - INTERIOR_INSET;
final double yb = INTERIOR_INSET;
final double yt = y1 - INTERIOR_INSET;
final float uL = dx * SCROLL_SPEED + scrollX;
final float uR = (dx + 1) * SCROLL_SPEED + scrollX;
final float uC = (dx + 0.5F) * SCROLL_SPEED + scrollX;
final float vN = dz * SCROLL_SPEED + scrollZ;
final float vS = (dz + 1) * SCROLL_SPEED + scrollZ;
final float vC = (dz + 0.5F) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, yb, z1, uL, vS, x1, yb, z1, uR, vS, x1, yb, z0, uR, vN, x0, yb, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, yt, z0, uL, vN, x1, yt, z0, uR, vN, x1, yt, z1, uR, vS, x0, yt, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, yb, z0, uC, vN, x0, yt, z0, uC, vN, x0, yt, z1, uC, vS, x0, yb, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(x1, yb, z1, uC, vS, x1, yt, z1, uC, vS, x1, yt, z0, uC, vN, x1, yb, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, yb, z0, uL, vC, x1, yb, z0, uR, vC, x1, yt, z0, uR, vC, x0, yt, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, yt, z1, uL, vC, x1, yt, z1, uR, vC, x1, yb, z1, uR, vC, x0, yb, z1, uL, vC, F_Z, N_NEG_Z);
}
}
}
private void emitFastLayer(int radiusCells, float scrollX, float scrollZ) {
final double r0 = -radiusCells;
final double r1 = radiusCells;
final float u0 = -radiusCells * SCROLL_SPEED + scrollX;
final float u1 = radiusCells * SCROLL_SPEED + scrollX;
final float v0 = -radiusCells * SCROLL_SPEED + scrollZ;
final float v1 = radiusCells * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(QUAD_BYTES);
emitCloudQuad(r0, 0.0, r0, u0, v0, r1, 0.0, r0, u1, v0, r1, 0.0, r1, u1, v1, r0, 0.0, r1, u0, v1, F_BOTTOM, N_POS_Y);
}
private void finishGeometry() {
final int totalBytes = (int) (writeAddr - emitBufferAddr);
final int vertexCount = totalBytes / VERTEX_STRIDE;
if (vertexCount == 0) {
@@ -514,7 +614,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean cameraInsideCloud, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
final float chunkOffsetX = chunkX * (float) CELLS_PER_CHUNK;
final float chunkOffsetZ = chunkZ * (float) CELLS_PER_CHUNK;
final int baseU = chunkX * CELLS_PER_CHUNK + anchorX;
@@ -534,43 +634,11 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final int relX = chunkX * CELLS_PER_CHUNK + k;
final int relZ = chunkZ * CELLS_PER_CHUNK + j;
if (relX * relX + relZ * relZ > radiusCellsSq) continue;
if (cameraInsideCloud && (Math.abs(relX) > 1 || Math.abs(relZ) > 1)) continue;
emittableBits |= 1L << (j * CELLS_PER_CHUNK + k);
}
}
if (emittableBits == 0L) return;
if (cameraInsideCloud) {
long bits = emittableBits;
while (bits != 0L) {
final int bit = Long.numberOfTrailingZeros(bits);
bits &= bits - 1;
final int k = bit & 7;
final int j = bit >>> 3;
final double x0 = chunkOffsetX + k;
final double x1 = chunkOffsetX + k + 1;
final double z0 = chunkOffsetZ + j;
final double z1 = chunkOffsetZ + j + 1;
final double xe = x1 - EDGE_OVERLAP;
final double zs = z1 - EDGE_OVERLAP;
final float uC = (chunkOffsetX + k + 0.5F) * SCROLL_SPEED + scrollX;
final float vC = (chunkOffsetZ + j + 0.5F) * SCROLL_SPEED + scrollZ;
final float uL = (chunkOffsetX + k) * SCROLL_SPEED + scrollX;
final float uR = (chunkOffsetX + k + 1) * SCROLL_SPEED + scrollX;
final float vN = (chunkOffsetZ + j) * SCROLL_SPEED + scrollZ;
final float vS = (chunkOffsetZ + j + 1) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, 0.0, z1, uL, vS, x1, 0.0, z1, uR, vS, x1, 0.0, z0, uR, vN, x0, 0.0, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, ye, z0, uL, vN, x1, ye, z0, uR, vN, x1, ye, z1, uR, vS, x0, ye, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, 0.0, z0, uC, vN, x0, y1, z0, uC, vN, x0, y1, z1, uC, vS, x0, 0.0, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(xe, 0.0, z1, uC, vS, xe, y1, z1, uC, vS, xe, y1, z0, uC, vN, xe, 0.0, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, 0.0, z0, uL, vC, x1, 0.0, z0, uR, vC, x1, y1, z0, uR, vC, x0, y1, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, y1, zs, uL, vC, x1, y1, zs, uR, vC, x1, 0.0, zs, uR, vC, x0, 0.0, zs, uL, vC, F_Z, N_NEG_Z);
}
return;
}
if (emitTopFace || emitBottomFace) {
final int globalColStart = chunkX * CELLS_PER_CHUNK - gridMinX;
final int globalRowStart = chunkZ * CELLS_PER_CHUNK - gridMinZ;
@@ -9,6 +9,7 @@ final class CloudUniforms {
static final float ALPHA = 0.8f;
final GlUniformMatrix4f mvp;
final GlUniformMatrix4f mv;
final GlUniformFloat4v colorMult;
final GlUniformFloat4v fogParams;
final GlUniformFloat4v fogColor;
@@ -18,11 +19,12 @@ final class CloudUniforms {
private int lastFogEnabled = -1;
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
private final float[] uploadBuf = new float[4];
CloudUniforms(ShaderBindingContext ctx) {
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
mv = ctx.bindUniform("u_MVMatrix", GlUniformMatrix4f::new);
colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new);
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
@@ -51,14 +53,16 @@ final class CloudUniforms {
}
}
void setFogParams(float x, float y) {
if (x != lastFogParamX || y != lastFogParamY) {
void setFogParams(float x, float y, float z, float w) {
if (x != lastFogParamX || y != lastFogParamY || z != lastFogParamZ || w != lastFogParamW) {
lastFogParamX = x;
lastFogParamY = y;
lastFogParamZ = z;
lastFogParamW = w;
uploadBuf[0] = x;
uploadBuf[1] = y;
uploadBuf[2] = 0f;
uploadBuf[3] = 0f;
uploadBuf[2] = z;
uploadBuf[3] = w;
fogParams.set(uploadBuf);
}
}
@@ -7,8 +7,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public class AngelicaRenderQueue {
private static final Thread MAIN_THREAD = Thread.currentThread();
private static final Queue<Runnable> TASKS = new ConcurrentLinkedQueue<>();
// Metrics
@@ -33,11 +34,11 @@ public class AngelicaRenderQueue {
}
private static final Executor EXECUTOR = (runnable) -> {
if(Thread.currentThread() == MAIN_THREAD) {
if(GLStateManager.isMainThread()) {
runnable.run();
} else {
TASKS.add(runnable);
LockSupport.unpark(MAIN_THREAD);
LockSupport.unpark(GLStateManager.getMainThread());
}
};
@@ -45,6 +46,11 @@ public class AngelicaRenderQueue {
return EXECUTOR;
}
public static void submit(Runnable runnable) {
TASKS.add(runnable);
LockSupport.unpark(GLStateManager.getMainThread());
}
public static int processTasks(int max) {
int tasksRun = 0;
while(tasksRun < max) {
@@ -0,0 +1,229 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import net.coderbot.iris.gl.image.GlImage;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.player.EntityPlayer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Skins in 1.7.10 are 64x32 while modern has 64x64 textures. We have to address it here.
*/
public final class PlayerReflectionCapture {
public static final int VERTEX_COUNT = 288;
public static final int FLOATS_PER_VERTEX = 5; // x, y, z, u, v
private static final int RAW_INTS_PER_VERTEX = 8;
private static final int VERTEX_FLAGS = VertexFlags.convertToFlags(true, false, true, false);
private static boolean active;
private static boolean drawnThisCapture;
@Getter
private static Object target;
@Getter
private static EntityPlayer targetEntity;
private static int vbo;
private static int vao;
private static int[] rawData;
private static float[] vertexScratch;
private static int skin64Tex;
private static ByteBuffer skinPixels;
private static int lastCapturedSkin;
private static int lastUploadedAtlas;
private static int skinCopyWidth = 64;
private static int skinCopyHeight = 32;
private static ByteBuffer repackBuffer;
private static long repackAddress;
private static int repackCapacity;
private PlayerReflectionCapture() {}
public static void begin(EntityPlayer player) {
active = false;
drawnThisCapture = false;
target = null;
targetEntity = null;
if (player == null) return;
final Render render = RenderManager.instance.getEntityRenderObject(player);
if (render instanceof RendererLivingEntity living && living.mainModel instanceof ModelBiped model) {
target = model;
targetEntity = player;
active = true;
}
}
public static void end() {
active = false;
target = null;
targetEntity = null;
}
public static boolean shouldCapture() {
return active && !drawnThisCapture;
}
/**
* Emit the 288-vertex player batch VBO.
*/
public static void drawPlayerCapture(float[] xyzuv) {
if (drawnThisCapture) return;
drawnThisCapture = true;
if (xyzuv == null || xyzuv.length < VERTEX_COUNT * FLOATS_PER_VERTEX) return;
// Build the Tessellator layout
if (rawData == null) rawData = new int[VERTEX_COUNT * RAW_INTS_PER_VERTEX];
final int[] raw = rawData;
for (int i = 0; i < VERTEX_COUNT; i++) {
final int s = i * FLOATS_PER_VERTEX;
final int d = i * RAW_INTS_PER_VERTEX;
raw[d] = Float.floatToRawIntBits(xyzuv[s]);
raw[d + 1] = Float.floatToRawIntBits(xyzuv[s + 1]);
raw[d + 2] = Float.floatToRawIntBits(xyzuv[s + 2]);
raw[d + 3] = Float.floatToRawIntBits(xyzuv[s + 3]);
raw[d + 4] = Float.floatToRawIntBits(xyzuv[s + 4]);
raw[d + 5] = 0xFFFFFFFF; // color
raw[d + 6] = 0x00008000; // normal
raw[d + 7] = 0x00F000F0; // light
}
// Pack into our own native buffer using the player model's vertex format
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[VERTEX_FLAGS];
ensureRepackCapacity(VERTEX_COUNT * format.getVertexSize());
final long writePtr = format.writeToBuffer0(repackAddress, raw, VERTEX_COUNT * RAW_INTS_PER_VERTEX);
repackBuffer.position(0);
repackBuffer.limit((int) (writePtr - repackAddress));
// Upload to our dedicated VBO at offset 0
ensureVAO(format);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, repackBuffer, GL15.GL_STREAM_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Bind a 64x64 copy of the skin as `tex`
final int activeSkin = GLStateManager.getBoundTextureForServerState();
final boolean haveSkin = activeSkin != 0 && activeSkin != skin64Tex;
if (haveSkin) {
ensureSkin64Texture();
final GlImage atlas = GlImage.BY_NAME.get("playerAtlas_img");
final int atlasId = atlas != null ? atlas.getId() : 0;
final boolean skinChanged = activeSkin != lastCapturedSkin;
if (skinChanged) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(activeSkin);
final int srcW = info.getWidth();
final int srcH = info.getHeight();
if (srcW > 0 && srcH > 0) {
final int required = srcW * srcH * 4;
if (skinPixels == null || skinPixels.capacity() < required) {
skinPixels = ByteBuffer.allocateDirect(required).order(ByteOrder.nativeOrder());
}
// The 1.7.10 model UVs only address the top 64x32 region.
skinCopyWidth = Math.min(64, srcW);
skinCopyHeight = Math.min(32, srcH);
skinPixels.clear();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
lastCapturedSkin = activeSkin;
}
}
if (skinPixels != null && (skinChanged || atlasId != lastUploadedAtlas)) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
if (atlas != null) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, atlasId);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
}
lastUploadedAtlas = atlasId;
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
}
final ColorMask prevColor = GLStateManager.getColorMask();
final boolean pr = prevColor.red, pg = prevColor.green, pb = prevColor.blue, pa = prevColor.alpha;
final boolean prevDepthWrite = GLStateManager.getDepthState().isEnabled();
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glDepthMask(false);
QuadConverter.drawQuadsAsTriangles(0, VERTEX_COUNT);
GLStateManager.glDepthMask(prevDepthWrite);
GLStateManager.glColorMask(pr, pg, pb, pa);
if (haveSkin) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
}
GLStateManager.glBindVertexArray(0);
}
public static float[] vertexScratch() {
if (vertexScratch == null) vertexScratch = new float[VERTEX_COUNT * FLOATS_PER_VERTEX];
return vertexScratch;
}
private static void ensureVAO(VertexFormat format) {
if (vao != 0) return;
vbo = GLStateManager.glGenBuffers();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
private static void ensureSkin64Texture() {
if (skin64Tex != 0) return;
final int prev = GLStateManager.getBoundTextureForServerState();
skin64Tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
final ByteBuffer zero = ByteBuffer.allocateDirect(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zero);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prev);
}
private static void ensureRepackCapacity(int requiredBytes) {
if (repackBuffer != null && requiredBytes <= repackCapacity) return;
int newCapacity = Math.max(0x4000, repackCapacity);
while (newCapacity < requiredBytes) newCapacity *= 2;
if (repackBuffer != null) memFree(repackBuffer);
repackBuffer = memAlloc(newCapacity);
repackAddress = memAddress0(repackBuffer);
repackCapacity = newCapacity;
}
}
@@ -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;
@@ -22,15 +22,14 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ChunkTrackerAccessor;
import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkStatus;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerImpl;
import org.embeddedt.embeddium.impl.util.PositionUtil;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
/**
* Configurable chunk tracker supporting "fast" and "aggressive" loading modes.
* Based on Beddium's SafeChunkTracker by Ven and FalsePattern.
@@ -41,109 +40,23 @@ import org.embeddedt.embeddium.impl.util.PositionUtil;
*
* Mode can be switched at runtime via options menu - triggers renderer reload.
*/
public class AngelicaChunkTracker extends ChunkTracker {
private final LongOpenHashSet chunkReadyForced = new LongOpenHashSet();
public class AngelicaChunkTracker extends ChunkTrackerImpl {
private ChunkTrackerAccessor self() {
return (ChunkTrackerAccessor) this;
@Override
public void forEachReady(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
super.forEachReady(sectionManager);
}
@Override
public void onChunkStatusAdded(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final var cur = prev | flags;
if (prev == cur) {
return;
public void forEachEvent(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
self().angelica$getChunkStatus().put(key, cur);
this.updateNeighbors(x, z);
}
@Override
public void onChunkStatusRemoved(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final int cur = prev & ~flags;
if (prev == cur) {
return;
}
if (cur == self().angelica$getChunkStatus().defaultReturnValue()) {
self().angelica$getChunkStatus().remove(key);
} else {
self().angelica$getChunkStatus().put(key, cur);
}
this.updateNeighbors(x, z);
}
private void updateNeighbors(int x, int z) {
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
this.updateMerged(ox + x, oz + z);
}
}
}
private void updateMerged(int x, int z) {
final long key = PositionUtil.packChunk(x, z);
final int selfFlags = self().angelica$getChunkStatus().get(key);
// If self doesn't have FLAG_ALL, can't be ready at all
if (selfFlags != ChunkStatus.FLAG_ALL) {
// Remove from ready if present
final boolean wasReady = self().angelica$getChunkReady().remove(key) | this.chunkReadyForced.remove(key);
if (wasReady && !self().angelica$getLoadQueue().remove(key)) {
self().angelica$getUnloadQueue().add(key);
}
return;
}
// Check if all neighbors also have FLAG_ALL
int mergedFlags = selfFlags;
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
mergedFlags &= self().angelica$getChunkStatus().get(PositionUtil.packChunk(ox + x, oz + z));
}
}
if (mergedFlags == ChunkStatus.FLAG_ALL) {
// All neighbors ready - add to both sets -- Trigger load if newly added to chunkReadyForced (first time ready)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().add(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
} else {
// Self ready but neighbors not - add to forced, remove from proper
// Trigger load if state changed (moved from proper to forced, or newly forced)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().remove(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
}
}
@Override
public LongCollection getReadyChunks() {
if (AngelicaConfig.useVanillaChunkTracking) {
// Aggressive mode: return both ready sets
final LongOpenHashSet combined = new LongOpenHashSet(self().angelica$getChunkReady());
combined.addAll(this.chunkReadyForced);
return LongSets.unmodifiable(combined);
} else {
// Default mode: only return chunks with all neighbors ready
return LongSets.unmodifiable(self().angelica$getChunkReady());
}
}
@Override
public void forEachEvent(ChunkEventHandler loadEventHandler, ChunkEventHandler unloadEventHandler) {
ChunkTracker.forEachChunk(self().angelica$getUnloadQueue(), unloadEventHandler);
self().angelica$getUnloadQueue().clear();
ChunkTracker.forEachChunk(self().angelica$getLoadQueue(), loadEventHandler);
self().angelica$getLoadQueue().clear();
super.forEachEvent(sectionManager);
}
}
@@ -1,5 +1,8 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
@@ -53,7 +56,18 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
final ChunkTaskProvider provider = ChunkTaskRegistry.getActiveProvider();
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, 0, 16, provider.threadCount(), provider);
final int minSection;
final int maxSection;
if (ModStatus.isCubicChunksLoaded && world instanceof ICubicWorld) {
minSection = CubicChunksAPI.getMinSectionY(world);
maxSection = CubicChunksAPI.getMaxSectionYExclusive(world);
} else {
minSection = 0;
maxSection = 16;
}
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, minSection, maxSection, provider.threadCount(), provider);
}
public void setCameraPosition(double x, double y, double z) {
@@ -125,6 +139,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
if (chunk.isEmpty()) {
return true;
}
if (ModStatus.isCubicChunksLoaded) {
final var section = CubicChunksAPI.getCubeStorage(this.world, x, y, z);
return section == null || section.isEmpty();
}
final var array = chunk.getBlockStorageArray();
if (y < 0 || y >= array.length) {
return true;
@@ -198,6 +218,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
return IrisShaderProviderHolder.isShadowPass();
}
public void markShadowGraphDirty() {
if (this.shadowRenderListManager != null) {
this.shadowRenderListManager.setNeedsUpdate(true);
}
}
@Override
public Collection<String> getDebugStrings() {
List<String> list = new ArrayList<>(super.getDebugStrings());
@@ -1,15 +1,10 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
@@ -17,6 +12,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraftforge.client.MinecraftForgeClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.gl.device.CommandList;
@@ -32,10 +28,18 @@ import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, AngelicaRenderSectionManager, BlockRenderLayer, TileEntity, CeleritasWorldRenderer.TileEntityRenderContext> implements IDynamicLightWorldRenderer {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
@@ -110,12 +114,20 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
@Override
public int getMinimumBuildHeight() {
return 0;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMinHeight(this.world);
} else {
return 0;
}
}
@Override
public int getMaximumBuildHeight() {
return 256;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMaxHeight(this.world);
} else {
return 256;
}
}
@Override
@@ -346,8 +358,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,42 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder;
import com.cardinalstar.cubicchunks.api.event.CubeEvent;
import com.cardinalstar.cubicchunks.util.CubePos;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class CubeStatusEvents {
public static void init() {
MinecraftForge.EVENT_BUS.register(new CubeStatusEvents());
}
private ChunkTracker getTracker() {
return ChunkTrackerHolder.get(Minecraft.getMinecraft().theWorld);
}
@SubscribeEvent
public void onCubeLoaded(CubeEvent.Load event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeLoaded(pos.getX(), pos.getY(), pos.getZ());
}
}
@SubscribeEvent
public void onCubeUnloaded(CubeEvent.Unload event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeUnloaded(pos.getX(), pos.getY(), pos.getZ());
}
}
}
@@ -0,0 +1,157 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import com.cardinalstar.cubicchunks.util.HashMap3D;
import com.cardinalstar.cubicchunks.util.HashSet3D;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
public class CubeStatusTracker implements ChunkTracker {
/// Cubes that have been synced to the client and are currently loaded.
private final HashSet3D loadedCubes = new HashSet3D();
/// Cubes that have their neighbours loaded (as needed) and are ready to be rendered.
private final HashSet3D validCubes = new HashSet3D();
private final HashMap3D<PendingState> changedCubes = new HashMap3D<>();
private enum PendingState {
Add,
Remove
}
/// When true, adjacent cubes will not be checked.
private boolean fastMode = false;
public CubeStatusTracker() {
}
@Override
public void setFastMode(boolean fastMode) {
this.fastMode = fastMode;
this.loadedCubes.forEach(this::pollCube);
}
@Override
public boolean isFastModeEnabled() {
return fastMode;
}
public void onCubeLoaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.add(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
public void onCubeUnloaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.remove(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
private void onCubeChanged(int cubeX, int cubeY, int cubeZ) {
pollCube(cubeX, cubeY, cubeZ);
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
pollCube(cubeX + dx, cubeY + dy, cubeZ + dz);
}
}
}
}
}
private void pollCube(int cubeX, int cubeY, int cubeZ) {
boolean renderable = canRender(cubeX, cubeY, cubeZ);
boolean isRendering = validCubes.contains(cubeX, cubeY, cubeZ);
if (renderable != isRendering) {
changedCubes.put(cubeX, cubeY, cubeZ, renderable ? PendingState.Add : PendingState.Remove);
} else {
changedCubes.remove(cubeX, cubeY, cubeZ);
}
}
private boolean canRender(int cubeX, int cubeY, int cubeZ) {
if (!loadedCubes.contains(cubeX, cubeY, cubeZ)) return false;
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
if (!loadedCubes.contains(cubeX + dx, cubeY + dy, cubeZ + dz)) return false;
}
}
}
}
return true;
}
@Override
public void onChunkStatusAdded(int i, int i1, int i2) {
// do nothing
}
@Override
public void onChunkStatusRemoved(int i, int i1, int i2) {
// do nothing
}
@Override
public void forEachReady(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
} else {
validCubes.remove(x, y, z);
}
});
changedCubes.clear();
for (var pos : validCubes.fastEntryIterable()) {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
renderSectionManager.onSectionAdded(x, y, z);
}
}
@Override
public void forEachEvent(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
renderSectionManager.onSectionAdded(x, y, z);
} else {
validCubes.remove(x, y, z);
renderSectionManager.onSectionRemoved(x, y, z);
}
});
changedCubes.clear();
}
}
@@ -7,6 +7,7 @@ import static com.gtnewhorizons.angelica.rendering.celeritas.iris.IrisExtendedCh
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Setter;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.coderbot.iris.vertices.NormalHelper;
@@ -55,7 +56,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
public void prepareToRenderBlock(BlockRenderContext ctx, Block block, int metadata, short renderType, byte lightValue) {
this.context = ctx;
Int2IntMap metaMap = blockMetaMatches != null ? blockMetaMatches.get(block) : null;
ctx.blockId = (short) (metaMap != null ? metaMap.get(metadata) : -1);
ctx.blockId = (short) (metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1);
ctx.renderType = renderType;
ctx.lightValue = lightValue;
}
@@ -84,7 +85,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
private int lookupFluidMeta(Block block, int metadata) {
final Int2IntMap metaMap = blockMetaMatches.get(block);
return metaMap != null ? metaMap.get(metadata) : -1;
return metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1;
}
private static Block liquidCounterpart(Block block) {
@@ -1,26 +1,12 @@
package com.gtnewhorizons.angelica.rendering.celeritas.world;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import java.util.Arrays;
import java.util.List;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
import net.minecraft.block.Block;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.MathHelper;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
@@ -30,9 +16,24 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
/**
* Takes a slice of world state (block states, biome and light data arrays) and copies the data for use in off-thread
@@ -111,7 +112,13 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
public static ChunkRenderContext prepare(World world, ChunkSectionPos origin, ClonedChunkSectionCache sectionCache) {
final Chunk chunk = world.getChunkFromChunkCoords(origin.x, origin.z);
final ExtendedBlockStorage section = chunk.getBlockStorageArray()[origin.y];
final ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, origin.x, origin.y, origin.z);
} else {
section = chunk.getBlockStorageArray()[origin.y];
}
// If the chunk section is absent or empty, terminate early
if (section == null || section.isEmpty()) {
@@ -241,7 +248,10 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
@Override
public int getLightBrightnessForSkyBlocks(int x, int y, int z, int min) {
if (y < 0 || y >= 256 || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
final int minHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMinHeight(this.world) : 0;
final int maxHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMaxHeight(this.world) : 256;
if (y < minHeight || y >= maxHeight || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
// skyBrightness = 15, blockBrightness = min
return (15 << 20) | (min << 4);
}
@@ -372,7 +382,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
if (!blockBoxContains(this.volume, x, y, z)) {
return 0;
}
y = MathHelper.clamp_int(y, 0, 255);
y = CompatMathHelper.clampHeight(y);
final int relX = x - this.baseX;
final int relY = y - this.baseY;
@@ -4,12 +4,11 @@ import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ExtendedBlockStorageExt;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.lotr.LOTRCompat;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import mega.fluidlogged.internal.mixin.hook.FLSubChunk;
@@ -25,9 +24,13 @@ import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraftforge.fluids.Fluid;
import java.util.Map;
public class ClonedChunkSection {
private static final ExtendedBlockStorage EMPTY_SECTION = new ExtendedBlockStorage(0, false);
private static final int SECTION_BLOCK_LENGTH = 16;
private static final int BIOME_DATA_LENGTH = SECTION_BLOCK_LENGTH * SECTION_BLOCK_LENGTH;
private final ClonedChunkSectionCache backingCache;
private final World world;
@@ -53,7 +56,13 @@ public class ClonedChunkSection {
throw new RuntimeException("Couldn't retrieve chunk at " + pos.toChunkPos());
}
ExtendedBlockStorage section = getChunkSection(chunk, pos);
ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, pos.x, pos.y, pos.z);
} else {
section = getChunkSection(chunk, pos);
}
if (section == null) {
section = EMPTY_SECTION;
@@ -62,7 +71,7 @@ public class ClonedChunkSection {
this.pos = pos;
this.data = new ExtendedBlockStorageExt(chunk, section);
this.biomeData = new BiomeGenBase[16 * 16];
this.biomeData = new BiomeGenBase[BIOME_DATA_LENGTH];
copyBlockEntities(chunk, pos);
@@ -72,15 +81,22 @@ public class ClonedChunkSection {
}
private void fillBiomeData(Chunk chunk) {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < 16; lz++) {
for (int lx = 0; lx < 16; lx++) {
this.biomeData[lx | (lz << 4)] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
if (LOTRCompat.isLotrProvider(world.provider)) {
for (int z = pos.getMinZ(); z <= pos.getMaxZ(); z++) {
for (int x = pos.getMinX(); x <= pos.getMaxX(); x++) {
this.biomeData[((z & 15) << 4) | (x & 15)] = world.getBiomeGenForCoords(x, z);
}
}
} else {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < SECTION_BLOCK_LENGTH; lz++) {
for (int lx = 0; lx < SECTION_BLOCK_LENGTH; lx++) {
this.biomeData[(lz << 4) | lx] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
}
}
}
}
@SuppressWarnings("unchecked")
private void copyBlockEntities(Chunk chunk, ChunkSectionPos pos) {
this.tileEntities.clear();
@@ -88,22 +104,13 @@ public class ClonedChunkSection {
map.readLock();
try {
final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = map.getDelegate();
if (!delegate.isEmpty()) {
final int minY = pos.getMinY();
final int maxY = pos.getMaxY();
for (Object2ObjectMap.Entry<ChunkPosition, TileEntity> entry : Object2ObjectMaps.fastIterable(delegate)) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
this.tileEntities.put(ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15), te);
}
}
}
map.forEachInYRange(pos.getMinY(), pos.getMaxY(), entry -> {
final ChunkPosition tePos = entry.getKey();
this.tileEntities.put(
ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15),
entry.getValue()
);
});
} finally {
map.readUnlock();
}
@@ -169,12 +176,15 @@ public class ClonedChunkSection {
this.lastUsedTimestamp = timestamp;
}
public static boolean isOutOfBuildLimitVertically(int y) {
public static boolean isOutOfBuildLimitVertically(World world, int y) {
if (ModStatus.isCubicChunksLoaded) {
return y < CubicChunksAPI.getMinHeight(world) || y >= CubicChunksAPI.getMaxHeight(world);
}
return y < 0 || y >= 256;
}
private static ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(ChunkSectionPos.getBlockCoord(pos.y))) {
private ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(this.world, ChunkSectionPos.getBlockCoord(pos.y))) {
return chunk.getBlockStorageArray()[pos.y];
}
return null;
@@ -0,0 +1,166 @@
package com.gtnewhorizons.angelica.shadercompat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Maybe overcomplicated, but it's required to bridge the gap between the modern glint texture and white texture 1.7.10
* has. Some shaders are able to render the glint just fine as they seem to have some amount of support for older
* Minecraft, but others like AstraLex do *not* render correctly. It expects the color to be within the texture.
* This might introduce some oddities that I'm not seeing, but for now this appears to fix the issue.
*/
public final class ShaderGlint {
private static final ResourceLocation GLINT_MASK = new ResourceLocation("textures/misc/enchanted_item_glint.png");
private static final int MAX_CACHE = 64;
private static boolean maskLoaded;
private static boolean maskFailed;
private static int maskW;
private static int maskH;
private static int[] maskLum;
private static final Int2IntLinkedOpenHashMap colorToTexture = new Int2IntLinkedOpenHashMap();
static {
colorToTexture.defaultReturnValue(0);
}
private static final GlintColorHandler COLOR_HANDLER = ShaderGlint::onColorChanged;
private static boolean injecting;
private static boolean swapped;
private static int depth;
private static int prevTexture;
private static ByteBuffer tintBuffer;
private ShaderGlint() {}
public static void beginGlint() {
if (ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
// If a glint span is already active, keep the outer span's saved state and
// let the nested render ride on its handler.
if (depth++ > 0) return;
swapped = false;
injecting = false;
// Save the texture bound before the glint section so endGlint can put it back if we swap
prevTexture = GLStateManager.getBoundTextureForServerState(0);
// Observe vertex-color changes for the rest of the glint section
GLSMHooks.glintColorHandler = COLOR_HANDLER;
}
public static void endGlint() {
if (depth == 0 || --depth > 0) return;
if (GLSMHooks.glintColorHandler != COLOR_HANDLER) return;
GLSMHooks.glintColorHandler = null;
if (swapped) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prevTexture);
}
swapped = false;
injecting = false;
}
private static void onColorChanged(float red, float green, float blue, float alpha) {
if (injecting) return;
final int texture = getTintedTexture(red, green, blue);
if (texture <= 0) return;
injecting = true;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, alpha);
injecting = false;
swapped = true;
}
private static int getTintedTexture(float r, float g, float b) {
ensureMaskLoaded();
if (!maskLoaded) return -1;
final int ri = clamp8(r);
final int gi = clamp8(g);
final int bi = clamp8(b);
final int key = (ri << 16) | (gi << 8) | bi;
final int cached = colorToTexture.getAndMoveToLast(key);
if (cached != 0) return cached;
if (colorToTexture.size() >= MAX_CACHE) {
GLStateManager.glDeleteTextures(colorToTexture.remove(colorToTexture.firstIntKey()));
}
final int texture = uploadTinted(ri / 255.0F, gi / 255.0F, bi / 255.0F);
colorToTexture.putAndMoveToLast(key, texture);
return texture;
}
private static int uploadTinted(float r, float g, float b) {
final ByteBuffer pixels = tintBuffer;
pixels.clear();
for (final int l : maskLum) {
pixels.put((byte) Math.round(l * r));
pixels.put((byte) Math.round(l * g));
pixels.put((byte) Math.round(l * b));
pixels.put((byte) 0xFF);
}
pixels.flip();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
// Match the vanilla glint texture
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, maskW, maskH, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return texture;
}
private static void ensureMaskLoaded() {
if (maskLoaded || maskFailed) return;
try (final InputStream in = Minecraft.getMinecraft().getResourceManager().getResource(GLINT_MASK).getInputStream()) {
final BufferedImage img = ImageIO.read(in);
maskW = img.getWidth();
maskH = img.getHeight();
maskLum = new int[maskW * maskH];
final int[] argb = img.getRGB(0, 0, maskW, maskH, null, 0, maskW);
for (int i = 0; i < argb.length; i++) {
final int p = argb[i];
final int rr = (p >> 16) & 0xFF;
final int gg = (p >> 8) & 0xFF;
final int bb = p & 0xFF;
maskLum[i] = (rr + gg + bb) / 3;
}
tintBuffer = BufferUtils.createByteBuffer(maskW * maskH * 4);
maskLoaded = true;
} catch (final Exception e) {
maskFailed = true;
}
}
private static int clamp8(float v) {
final int i = Math.round(v * 255.0F);
if (i < 0) return 0;
return Math.min(i, 255);
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
@@ -10,15 +11,30 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
/**
* Thread-safe wrapper around Object2ObjectOpenHashMap for chunkTileEntityMap.
* Thread-safe wrapper around a tile entity map for chunkTileEntityMap.
* May wrap either a plain Object2ObjectOpenHashMap (vanilla) or a ColumnTileEntityMap (cubic chunks).
*/
public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = new Object2ObjectOpenHashMap<>();
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> flatDelegate;
private final Map<ChunkPosition, TileEntity> delegate;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ConcurrentLinkedQueue<ChunkPosition> invalidationQueue = new ConcurrentLinkedQueue<>();
/** Vanilla path: backs the map with a fresh flat map. */
public ConcurrentTileEntityMap() {
this.flatDelegate = new Object2ObjectOpenHashMap<>();
this.delegate = this.flatDelegate;
}
/** cubic chunks path: wraps an existing map (e.g. ColumnTileEntityMap). */
public ConcurrentTileEntityMap(Map<ChunkPosition, TileEntity> toWrap) {
this.flatDelegate = null;
this.delegate = toWrap;
}
public void queueInvalidation(ChunkPosition pos) {
invalidationQueue.add(pos);
}
@@ -59,14 +75,27 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
}
}
public Object2ObjectOpenHashMap<ChunkPosition, TileEntity> getDelegate() {
return delegate;
}
public TileEntity putDirect(ChunkPosition key, TileEntity value) {
return delegate.put(key, value);
}
/**
* Iterates all non-invalid tile entities whose Y coordinate falls within [minY, maxY].
* Caller must hold the read lock.
*/
public void forEachInYRange(int minY, int maxY, Consumer<Map.Entry<ChunkPosition, TileEntity>> action) {
final Iterable<? extends Map.Entry<ChunkPosition, TileEntity>> entries =
flatDelegate != null ? Object2ObjectMaps.fastIterable(flatDelegate) : delegate.entrySet();
for (Map.Entry<ChunkPosition, TileEntity> entry : entries) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
action.accept(entry);
}
}
}
private static boolean isRenderWorkerThread() {
return RenderThreadContext.hasWorldSlice();
}
@@ -135,7 +164,10 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
lock.writeLock().lock();
try {
invalidationQueue.clear();
delegate.clear();
if (flatDelegate != null) {
flatDelegate.clear();
}
// wrapped delegate manages its own TE lifetime; skip clear()
} finally {
lock.writeLock().unlock();
}
@@ -4,6 +4,8 @@ import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import jss.notfine.core.Settings;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
@@ -19,6 +21,13 @@ public class Zoom {
@Getter
private static float zoom = ZOOM_DEFAULT;
public static final float ZOOM_SMOOTH_SNAP_EPSILON = 0.001F;
@Getter
private static float currentZoom = 1.0F;
private static long zoomLerpLastNano = 0L;
@Getter
private static final KeyBinding zoomKey = new KeyBinding("Zoom", 0, "key.categories.misc");
@@ -38,6 +47,29 @@ public class Zoom {
zoom = MathHelper.clamp_float((float) (zoom * Math.pow(ZOOM_STEP, Integer.signum(eventDWheel))), ZOOM_MIN, ZOOM_MAX);
}
public static void updateZoomLerp() {
final long now = System.nanoTime();
float dt = zoomLerpLastNano == 0L ? 0.0F : (now - zoomLerpLastNano) / 1.0e9F;
zoomLerpLastNano = now;
final float target = zoomEnabled ? zoom : 1.0F;
if (dt <= 0.0F || !(boolean) Settings.ZOOM_SMOOTH.option.getStore()) {
currentZoom = target;
return;
}
// Clamp dt so a long frame (alt-tab, GC pause) does not snap the zoom.
dt = Math.min(dt, 0.1F);
final int rate = (int) Settings.ZOOM_SMOOTH_SPEED.option.getStore();
currentZoom += (target - currentZoom) * (1.0F - (float) Math.exp(-dt * rate));
if (Math.abs(target - currentZoom) < ZOOM_SMOOTH_SNAP_EPSILON) {
currentZoom = target;
}
}
private static void resetMouseFilters(Minecraft mc) {
if (mc.entityRenderer != null) {
((IMouseFilterExt) mc.entityRenderer.mouseFilterXAxis).angelica$reset();
@@ -79,4 +111,10 @@ public class Zoom {
handleKeyEvent();
}
}
@SubscribeEvent
public void onRenderTick(TickEvent.RenderTickEvent e) {
if (e.phase != TickEvent.Phase.START) return;
updateZoomLerp();
}
}
@@ -6,6 +6,7 @@ import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import com.prupe.mcpatcher.mal.block.BlockStateMatcher;
import net.minecraftforge.common.util.ForgeDirection;
final class BlockOrientation extends RenderBlockState {
@@ -39,12 +40,13 @@ final class BlockOrientation extends RenderBlockState {
private int altMetadata;
private int metadataBits;
private int renderType;
private boolean iCtmBlock;
private int blockFace;
private int textureFace;
private int textureFaceOrig;
private int rotateUV;
private boolean flipBottom;
private boolean flipped;
@Override
public void clear() {
@@ -57,7 +59,7 @@ final class BlockOrientation extends RenderBlockState {
offsetsComputed = false;
haveOffsets = false;
dx = dy = dz = 0;
flipBottom = !fixedBottomFaceUV;
flipped = false;
}
@Override
@@ -75,6 +77,11 @@ final class BlockOrientation extends RenderBlockState {
return z;
}
@Override
public int getMetadata() {
return metadata;
}
@Override
public int getBlockFace() {
return blockFace;
@@ -102,7 +109,10 @@ final class BlockOrientation extends RenderBlockState {
@Override
public int[] getOffset(int blockFace, int relativeDirection) {
return NEIGHBOR_OFFSET[flipBottom && blockFace == 0 ? 1 : blockFace][rotateUV(relativeDirection)];
if(flipped){
blockFace = ForgeDirection.OPPOSITES[blockFace];
}
return NEIGHBOR_OFFSET[blockFace][rotateUV(relativeDirection)];
}
@Override
@@ -146,14 +156,14 @@ final class BlockOrientation extends RenderBlockState {
}
public void setFlipBottom(){
flipBottom = true;
public void flipFace() {
flipped = true;
}
@Override
public boolean shouldConnectByBlock(Block neighbor, int neighborX, int neighborY, int neighborZ) {
return block == neighbor
&& (metadataBits & (1 << blockAccess.getBlockMetadata(neighborX, neighborY, neighborZ))) != 0;
return iCtmBlock ? ((ICTMBlock) block).shouldConnectByBlock(this, neighbor, neighborX, neighborY, neighborZ)
: block == neighbor && (metadataBits & (1 << blockAccess.getBlockMetadata(neighborX, neighborY, neighborZ))) != 0;
}
@Override
@@ -171,11 +181,12 @@ final class BlockOrientation extends RenderBlockState {
copy.altMetadata = altMetadata;
copy.metadataBits = metadataBits;
copy.renderType = renderType;
copy.iCtmBlock = iCtmBlock;
copy.blockFace = blockFace;
copy.textureFace = textureFace;
copy.textureFaceOrig = textureFaceOrig;
copy.rotateUV = rotateUV;
copy.flipBottom = flipBottom;
copy.flipped = flipped;
copy.blockAccess = blockAccess;
copy.block = block;
@@ -199,6 +210,7 @@ final class BlockOrientation extends RenderBlockState {
this.z = z;
renderType = block.getRenderType();
metadata = altMetadata = blockAccess.getBlockMetadata(x, y, z);
iCtmBlock = block instanceof ICTMBlock;
offsetsComputed = false;
}
@@ -208,7 +220,7 @@ final class BlockOrientation extends RenderBlockState {
rotateUV = 0;
textureFace = blockFaceToTextureFace(blockFace);
metadataBits = (1 << metadata) | (1 << altMetadata);
flipBottom = !fixedBottomFaceUV;
flipped = !fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal();
}
void setBlockMetadata(Block block, int metadata, int face) {
@@ -219,6 +231,7 @@ final class BlockOrientation extends RenderBlockState {
renderType = block.getRenderType();
blockFace = textureFace = textureFaceOrig = face;
this.metadata = metadata;
iCtmBlock = block instanceof ICTMBlock;
metadataBits = 1 << metadata;
dx = dy = dz = 0;
rotateUV = 0;
@@ -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);
}
}
@@ -9,13 +9,14 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Consumer;
import com.github.bsideup.jabel.Desugar;
import net.minecraft.block.Block;
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,12 +33,24 @@ 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 {
public static class Overrides {
private final List<TileOverride> all = new ArrayList<>();
private final Map<Block, List<BlockStateMatcher>> block = new IdentityHashMap<>();
private final Map<String, List<TileOverride>> tile = new HashMap<>();
public List<TileOverride> getAll() {
return all;
}
public Map<Block, List<BlockStateMatcher>> getBlock() {
return block;
}
public Map<String, List<TileOverride>> getTile() {
return tile;
}
}
private static Overrides overrides = new Overrides();
@@ -82,6 +95,8 @@ public class CTMUtils {
private static final AtomicInteger texturePackChangeCounter = new AtomicInteger(0);
private static final ArrayList<Consumer<Overrides>> ctmRegistrationCallbacks = new ArrayList<>();
static {
try {
Class.forName(MCPatcherUtils.RENDER_PASS_CLASS)
@@ -117,6 +132,9 @@ public class CTMUtils {
.listResources(TexturePackAPI.MCPATCHER_SUBDIR + "ctm", ".properties", true)) {
registerOverrideWithoutLock(newOverrides, TileOverride.create(resource, tileLoader));
}
for (Consumer<Overrides> callback : ctmRegistrationCallbacks) {
callback.accept(newOverrides);
}
}
for (ResourceLocation resource : BlendMethod.getAllBlankResources()) {
tileLoader.preloadTile(resource, false);
@@ -257,7 +275,7 @@ public class CTMUtils {
return RenderPassAPI.instance.skipDefaultRendering(block);
}
private static void registerOverrideWithoutLock(Overrides overrides, TileOverride override) {
public static void registerOverrideWithoutLock(Overrides overrides, TileOverride override) {
if (override != null && !override.isDisabled()) {
boolean registered = false;
List<BlockStateMatcher> matchingBlocks = override.getMatchingBlocks();
@@ -308,4 +326,8 @@ public class CTMUtils {
private static TileOverrideIterator.Metadata newMetadataIterator() {
return new TileOverrideIterator.Metadata(overrides.block, overrides.tile);
}
public static void addCtmRegistrationCallback(Consumer<Overrides> consumer) {
ctmRegistrationCallbacks.add(consumer);
}
}
@@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import net.minecraftforge.common.util.ForgeDirection;
public final class CompactCtmQuadProcessor {
@@ -52,29 +53,33 @@ public final class CompactCtmQuadProcessor {
this.override = compact;
}
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon) {
if(renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.setFlipBottom();
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon, int face) {
boolean shouldFlip = face == ForgeDirection.DOWN.ordinal();
if(ForgeDirection.OPPOSITES[face] == renderBlockState.getBlockFace()){
shouldFlip = !shouldFlip;
}
if(shouldFlip && renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.flipFace();
}
int neighborBits = override.getNeighborBits(renderBlockState, origIcon);
int ctmIndex = TileOverride.neighborMap[neighborBits & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, renderBlockState, replacement);
renderWholeFace(rb, renderBlockState, replacement, face);
return true;
}
if (neighborBits == 0 || neighborBits == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(neighborBits)];
if (sprite != null) {
renderWholeFace(rb, renderBlockState, sprite);
renderWholeFace(rb, renderBlockState, sprite, face);
return true;
}
return false;
}
renderSplitFace(rb, renderBlockState, neighborBits);
renderSplitFace(rb, renderBlockState, neighborBits, face);
return true;
}
@@ -117,8 +122,7 @@ public final class CompactCtmQuadProcessor {
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections) {
final int face = renderBlockState.getBlockFace();
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections, int face) {
final double minX = rb.renderMinX, minY = rb.renderMinY, minZ = rb.renderMinZ;
final double maxX = rb.renderMaxX, maxY = rb.renderMaxY, maxZ = rb.renderMaxZ;
@@ -155,7 +159,7 @@ public final class CompactCtmQuadProcessor {
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
}
if (ao) {
@@ -219,18 +223,17 @@ public final class CompactCtmQuadProcessor {
return (sky << 16) | (block & 0xFFFF);
}
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
int face = renderBlockState.getBlockFace();
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
Block block = renderBlockState.getBlock();
int x = renderBlockState.getX();
int y = renderBlockState.getY();
@@ -0,0 +1,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);
}
}
@@ -0,0 +1,10 @@
package com.prupe.mcpatcher.ctm;
import net.minecraft.block.Block;
/**
* Implement this interface on blocks that support connected textures to allow them to control if they connect to neighbors or not
*/
public interface ICTMBlock {
boolean shouldConnectByBlock(RenderBlockState renderBlockState, Block neighbor, int neighborX, int neighborY, int neighborZ);
}
Loaded 100 of 350 files, more files were not shown because too many files have changed in this diff. Show more