mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 09:44:52 +00:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4910cfd6d3 | ||
|
|
d1c6677caf | ||
|
|
8cf0296284 | ||
|
|
4fd98e5026 | ||
|
|
8e293f13fb | ||
|
|
2db20ad866 | ||
|
|
10a5b550f2 | ||
|
|
c1403dcf13 | ||
|
|
5d1fabfca9 | ||
|
|
555b2b7dd2 | ||
|
|
7017786a7c | ||
|
|
1c49649685 | ||
|
|
2bae79c4ee | ||
|
|
c22935660a | ||
|
|
79c488102b | ||
|
|
cc1353b73a | ||
|
|
d1e6dc8c94 | ||
|
|
ecbf8fea2f | ||
|
|
610209605f | ||
|
|
112aae3735 | ||
|
|
5844b1b5c1 | ||
|
|
05f01c17e6 | ||
|
|
f07d96316a | ||
|
|
5b704d1bc2 | ||
|
|
26135124f4 | ||
|
|
3abc89b7b2 | ||
|
|
03d908d658 | ||
|
|
76bf19c8f6 | ||
|
|
b27834cbd9 | ||
|
|
eb0ecb0483 | ||
|
|
9acb0e67a7 | ||
|
|
f4f9b56b17 | ||
|
|
2196439e6c | ||
|
|
dd68c64239 | ||
|
|
0decc515e4 | ||
|
|
b3e1e89c88 | ||
|
|
32bff85526 | ||
|
|
02fc95f239 | ||
|
|
07fd6d78a0 | ||
|
|
87aa853f81 | ||
|
|
ab69eea654 | ||
|
|
edccf63b66 | ||
|
|
80413fb3de | ||
|
|
f3ed9aa82b | ||
|
|
307c563b6d | ||
|
|
f92ce1d24e | ||
|
|
a7fac16d97 | ||
|
|
20c3caaed4 | ||
|
|
bf08cd50d9 | ||
|
|
805a266059 | ||
|
|
d3c9963280 | ||
|
|
4630bc4fd8 | ||
|
|
ce32a3d3df | ||
|
|
1991dc58ec | ||
|
|
ae19236e1a | ||
|
|
7ad368c937 | ||
|
|
5c86c87bcf | ||
|
|
99ba7fa775 | ||
|
|
5ae4ab8c5c | ||
|
|
ddd68e023f | ||
|
|
f78cfdd5fb | ||
|
|
183bcfbf41 | ||
|
|
502f30c0f9 | ||
|
|
ceff04adba | ||
|
|
b4dfdb8cc5 | ||
|
|
3064818477 | ||
|
|
27524e6a89 | ||
|
|
5d5e910fc0 | ||
|
|
f325fd7fc3 | ||
|
|
ec278d3d7a |
No files matched your search
@@ -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>
|
||||
Executable
+40
@@ -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"
|
||||
@@ -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
|
||||
@@ -12,6 +12,7 @@ build/
|
||||
/config/
|
||||
/crash-reports/
|
||||
/logs/
|
||||
tmp/
|
||||
options.txt
|
||||
/saves/
|
||||
usernamecache.json
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+10
-5
@@ -66,7 +66,7 @@ configurations.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
|
||||
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
|
||||
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
|
||||
@@ -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
|
||||
@@ -144,7 +144,7 @@ dependencies {
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.3-GTNH:dev")
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,6 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
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;
|
||||
|
||||
@@ -9,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;
|
||||
@@ -114,13 +120,17 @@ public class CompatShaderTransformer {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
// Shaders already at 330+ core don't need compat transformation
|
||||
public static boolean isCoreShader(String source) {
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
if (version >= 330 && "core".equals(vm.group(2))) return false;
|
||||
return version >= 330 && "core".equals(vm.group(2));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
if (isCoreShader(source)) return false;
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
@@ -179,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;");
|
||||
@@ -307,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");
|
||||
}
|
||||
@@ -334,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();
|
||||
|
||||
@@ -7,8 +7,6 @@ import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
@@ -17,6 +15,8 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
@@ -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]";
|
||||
@@ -103,8 +108,15 @@ public class CompatUniformManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
|
||||
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static final class ProgramUniforms {
|
||||
final int[] locs;
|
||||
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
|
||||
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
|
||||
ProgramUniforms(int[] locs) { this.locs = locs; }
|
||||
}
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
@@ -123,15 +135,6 @@ public class CompatUniformManager {
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// Dirty tracking: skip uploads when state hasn't changed and program is the same
|
||||
private static int lastProgram = -1;
|
||||
private static int lastMvGen = -1;
|
||||
private static int lastProjGen = -1;
|
||||
private static int lastTexMatGen = -1;
|
||||
private static int lastFragmentGen = -1;
|
||||
private static int lastLightingGen = -1;
|
||||
private static int lastClipPlaneGen = -1;
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
/**
|
||||
@@ -154,45 +157,49 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programLocations.put(program, locs);
|
||||
programUniforms.put(program, new ProgramUniforms(locs));
|
||||
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUseProgram(int program) {
|
||||
if (program == 0) return;
|
||||
/**
|
||||
* Uploads the emulated uniforms.
|
||||
*
|
||||
* @return Whether the program needs emulation (FFP or Iris Program)
|
||||
*/
|
||||
public static boolean refreshCompatUniforms(int program) {
|
||||
final ProgramUniforms pu = programUniforms.get(program);
|
||||
if (pu == null) return false;
|
||||
final int[] locs = pu.locs;
|
||||
|
||||
int[] locs = programLocations.get(program);
|
||||
if (locs == null) return;
|
||||
|
||||
final boolean programChanged = program != lastProgram;
|
||||
lastProgram = program;
|
||||
|
||||
// Matrix uniforms — skip if generation unchanged and same program
|
||||
// Matrix uniforms — skip if this program's storage already holds the current generation
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int mvLinearGen = GLStateManager.mvLinearGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
final boolean mvChanged = mvGen != pu.lastMvGen;
|
||||
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
|
||||
final boolean projChanged = projGen != pu.lastProjGen;
|
||||
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
|
||||
pu.lastMvGen = mvGen;
|
||||
pu.lastMvLinearGen = mvLinearGen;
|
||||
pu.lastProjGen = projGen;
|
||||
pu.lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
lastFragmentGen = fragGen;
|
||||
if (fragGen != pu.lastFragmentGen) {
|
||||
pu.lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
if (litGen != pu.lastLightingGen) {
|
||||
pu.lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
@@ -201,14 +208,16 @@ public class CompatUniformManager {
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
lastClipPlaneGen = cpGen;
|
||||
if (cpGen != pu.lastClipPlaneGen) {
|
||||
pu.lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
@@ -225,12 +234,19 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mvLinearChanged) {
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
@@ -244,7 +260,6 @@ public class CompatUniformManager {
|
||||
normalMatrix.get(mat3Buf);
|
||||
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,14 +434,15 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programLocations.remove(program);
|
||||
programUniforms.remove(program);
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programLocations.containsKey(program);
|
||||
return programUniforms.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
return programLocations.get(program);
|
||||
final ProgramUniforms pu = programUniforms.get(program);
|
||||
return pu == null ? null : pu.locs;
|
||||
}
|
||||
}
|
||||
@@ -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,28 +857,44 @@ 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;
|
||||
}
|
||||
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
|
||||
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
|
||||
previous.delete();
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
|
||||
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
|
||||
previous.delete();
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
|
||||
// Log compilation details if enabled (before context restoration changes glListId)
|
||||
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 +905,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 +915,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,33 +1017,43 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
private static void executeDisplayList(int list) {
|
||||
final CompiledDisplayList compiled = displayListCache.get(list);
|
||||
if (compiled != null) {
|
||||
compiled.render(list);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to VBOManager or legacy display list
|
||||
if (list < 0) {
|
||||
// Negative IDs are VBOManager space (entity models, etc.)
|
||||
final VertexBuffer vbo = VBOManager.get(list);
|
||||
if (vbo != null) {
|
||||
vbo.render();
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
final CompiledDisplayList compiled = displayListCache.get(list);
|
||||
if (compiled != null) {
|
||||
compiled.render(list);
|
||||
return;
|
||||
}
|
||||
// Per OpenGL spec: if list is undefined, glCallList has no effect
|
||||
|
||||
// Fall back to VBOManager or legacy display list
|
||||
if (list < 0) {
|
||||
// Negative IDs are VBOManager space (entity models, etc.)
|
||||
final VertexBuffer vbo = VBOManager.get(list);
|
||||
if (vbo != null) {
|
||||
vbo.render();
|
||||
}
|
||||
// Per OpenGL spec: if list is undefined, glCallList has no effect
|
||||
}
|
||||
// An uncached positive-ID list = no-op (per OpenGL spec)
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
// An uncached positive-ID list = no-op (per OpenGL spec)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete display lists and free their VBO resources.
|
||||
*/
|
||||
public static void glDeleteLists(int list, int range) {
|
||||
for (int i = list; i < list + range; i++) {
|
||||
CompiledDisplayList compiled = displayListCache.remove(i);
|
||||
if (compiled != null) {
|
||||
compiled.delete();
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
for (int i = list; i < list + range; i++) {
|
||||
CompiledDisplayList compiled = displayListCache.remove(i);
|
||||
if (compiled != null) {
|
||||
compiled.delete();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,16 +83,34 @@ public final class QuadConverter {
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(int first, int vertexCount) {
|
||||
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;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
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);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
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) {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
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;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
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;
|
||||
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);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,27 +124,41 @@ public final class QuadConverter {
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
|
||||
}
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
GLStateManager.preDraw();
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth -- allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
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);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth — allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
@@ -139,12 +171,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 +193,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;
|
||||
@@ -147,6 +146,9 @@ public class RenderSystem {
|
||||
coreProfile = true;
|
||||
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.getInstance().enable();
|
||||
} else {
|
||||
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
|
||||
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +304,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void bufferData(int target, FloatBuffer data, int usage) {
|
||||
RENDER_BACKEND.bufferData(target, data, usage);
|
||||
GLStateManager.glBufferData(target, data, usage);
|
||||
}
|
||||
|
||||
public static int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
@@ -448,7 +450,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int createBuffers() {
|
||||
return RENDER_BACKEND.genBuffers();
|
||||
return GLStateManager.glGenBuffers();
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
@@ -456,13 +458,13 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void bufferStorage(int target, long size, int flags) {
|
||||
RENDER_BACKEND.bufferStorage(target, size, flags);
|
||||
GLStateManager.glBufferStorage(target, size, flags);
|
||||
}
|
||||
|
||||
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
|
||||
buf.asIntBuffer().put(data);
|
||||
RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
GLStateManager.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
@@ -550,7 +552,7 @@ public class RenderSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460" */
|
||||
static String parseGlVersionString(String info) {
|
||||
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
|
||||
if (!matcher.matches()) {
|
||||
@@ -562,4 +564,18 @@ public class RenderSystem {
|
||||
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
|
||||
return major + minor + bugfix;
|
||||
}
|
||||
|
||||
/** Parse a GL version string into packed major*10+minor (e.g. "4.3.0 - Build ..." -> 43), or -1 if unparseable. */
|
||||
public static int parseMajorMinor(String glVersion) {
|
||||
if (glVersion == null) return -1;
|
||||
final Matcher matcher = SEMVER_PATTERN.matcher(glVersion.trim());
|
||||
if (!matcher.matches()) return -1;
|
||||
return Integer.parseInt(matcher.group("major")) * 10 + Integer.parseInt(matcher.group("minor"));
|
||||
}
|
||||
|
||||
public static boolean isContextValid(int requestedMajor, int requestedMinor, int actualVersion, boolean coreBitSet) {
|
||||
if (!coreBitSet) return false;
|
||||
if (actualVersion < 0) return true;
|
||||
return actualVersion >= requestedMajor * 10 + requestedMinor;
|
||||
}
|
||||
}
|
||||
+56
-6
@@ -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);
|
||||
@@ -317,7 +327,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) {
|
||||
throw new UnsupportedOperationException("GLRenderBackend.copyBufferSubData should not be called — LWJGL service handles this");
|
||||
GL31.glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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);
|
||||
@@ -617,6 +622,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL11.glGetTexImage(target, level, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
|
||||
GL11.glGetTexImage(target, level, format, type, pixelBufferOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
|
||||
return GL30.glGetFramebufferAttachmentParameteri(target, attachment, pname);
|
||||
@@ -817,6 +827,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 +842,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);
|
||||
@@ -1028,6 +1063,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
return GL30.glMapBufferRange(target, offset, length, access, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushMappedBufferRange(int target, long offset, long length) {
|
||||
GL30.glFlushMappedBufferRange(target, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int genVertexArrays() {
|
||||
return GL30.glGenVertexArrays();
|
||||
@@ -1043,6 +1083,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);
|
||||
@@ -1214,6 +1259,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
return GL45.glCreateBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void namedBufferData(int buffer, long size, int usage) {
|
||||
GL45.glNamedBufferData(buffer, size, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void namedBufferData(int buffer, ByteBuffer data, int usage) {
|
||||
GL45.glNamedBufferData(buffer, data, usage);
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract rendering backend.
|
||||
@@ -25,6 +27,14 @@ public abstract class RenderBackend {
|
||||
/** Higher priority backends are preferred. */
|
||||
public int getPriority() { return 0; }
|
||||
|
||||
public boolean supportsFileDrop() { return false; }
|
||||
|
||||
public void startFileDrop() {}
|
||||
|
||||
public void stopFileDrop() {}
|
||||
|
||||
public List<String> pollDroppedFiles() { return Collections.emptyList(); }
|
||||
|
||||
public abstract int getMinGLSLVersion();
|
||||
|
||||
public abstract void flush();
|
||||
@@ -77,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);
|
||||
@@ -115,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) {}
|
||||
|
||||
|
||||
@@ -140,6 +151,7 @@ public abstract class RenderBackend {
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
|
||||
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
|
||||
|
||||
public abstract int createShader(int type);
|
||||
@@ -190,8 +202,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) {}
|
||||
@@ -233,10 +250,12 @@ public abstract class RenderBackend {
|
||||
public abstract int getBufferParameteri(int target, int pname);
|
||||
public abstract boolean isBuffer(int buffer);
|
||||
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
|
||||
public abstract void flushMappedBufferRange(int target, long offset, long length);
|
||||
|
||||
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));
|
||||
@@ -275,6 +294,7 @@ public abstract class RenderBackend {
|
||||
public abstract void namedFramebufferDrawBuffers(int framebuffer, IntBuffer bufs);
|
||||
public abstract void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
|
||||
public abstract int createBuffers();
|
||||
public abstract void namedBufferData(int buffer, long size, int usage);
|
||||
public abstract void namedBufferData(int buffer, ByteBuffer data, int usage);
|
||||
public abstract void namedBufferData(int buffer, FloatBuffer data, int usage);
|
||||
public abstract void namedBufferSubData(int buffer, long offset, ByteBuffer data);
|
||||
|
||||
@@ -104,8 +104,8 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = RENDER_BACKEND.createBuffers();
|
||||
RENDER_BACKEND.namedBufferData(buffer, data, usage);
|
||||
final int buffer = GLStateManager.glCreateBuffers();
|
||||
GLStateManager.glNamedBufferData(buffer, data, usage);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,9 +121,9 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = RENDER_BACKEND.genBuffers();
|
||||
final int buffer = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
RENDER_BACKEND.bufferData(target, data, usage);
|
||||
GLStateManager.glBufferData(target, data, usage);
|
||||
GLStateManager.glBindBuffer(target, 0);
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,16 @@ public class Program {
|
||||
@Getter private final VertexKey vertexKey;
|
||||
@Getter private final FragmentKey fragmentKey;
|
||||
|
||||
// Per-program uniform dirty-tracking
|
||||
static final class UploadState {
|
||||
int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
|
||||
int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
|
||||
int texGenGen = -1, clipPlaneGen = -1;
|
||||
float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
|
||||
int viewportWidth = -1, viewportHeight = -1;
|
||||
}
|
||||
final UploadState uploadState = new UploadState();
|
||||
|
||||
// Uniform locations (-1 = not present in this variant)
|
||||
// Matrices
|
||||
public int locModelViewMatrix = -1;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
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.QuadConverter;
|
||||
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 com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
@@ -22,7 +22,7 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
|
||||
/**
|
||||
* FFP shader manager
|
||||
*/
|
||||
public class ShaderManager {
|
||||
public final class ShaderManager {
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
@@ -31,11 +31,11 @@ public 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;
|
||||
private Program currentProgram = null;
|
||||
private int lastBoundProgramId = -1;
|
||||
private long currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
|
||||
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
|
||||
@@ -56,10 +56,8 @@ public 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"));
|
||||
}
|
||||
@@ -70,52 +68,72 @@ public class ShaderManager {
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
warmUp();
|
||||
enabled = true;
|
||||
|
||||
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
|
||||
currentVertexFlags = format.getVertexFlags();
|
||||
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
|
||||
return false;
|
||||
});
|
||||
|
||||
GLStateManager.LOGGER.info("FFP shader emulation enabled");
|
||||
}
|
||||
|
||||
// Force loading of classes before the SplashThread kicks off
|
||||
// Ensure it's GL free
|
||||
private static void warmUp() {
|
||||
try {
|
||||
final long[] fkScratch = new long[FragmentKey.MAX_UNITS];
|
||||
final int fkLen = FragmentKey.packFromState(fkScratch);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(fkScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
VertexShaderGenerator.generate(vk);
|
||||
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
|
||||
GeometryShaderGenerator.generate(vk);
|
||||
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
|
||||
for (Class<?> c : touched) {
|
||||
c.getName();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
GLStateManager.LOGGER.warn("FFP warmup failed; draw-path classes will resolve lazily", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
active = true;
|
||||
updateVariant(true, true, true, true);
|
||||
uploadUniforms();
|
||||
lastBoundProgramId = GLStateManager.getActiveProgram();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
lastBoundProgramId = -1;
|
||||
currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
currentFKLen = 0;
|
||||
}
|
||||
|
||||
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
GLStateManager.flushDeferredVertexAttribs();
|
||||
public void preDraw() {
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null) bh.flushDeferredBlend();
|
||||
|
||||
if (!active) {
|
||||
if (enabled) {
|
||||
final int currentProgramId = GLStateManager.getActiveProgram();
|
||||
if (currentProgramId != 0) {
|
||||
CompatUniformManager.onUseProgram(currentProgramId);
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
} else {
|
||||
return;
|
||||
// Handle FFP & Iris uniforms
|
||||
final int currentProgramId = GLStateManager.getActiveProgram();
|
||||
if (currentProgramId != 0) {
|
||||
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
|
||||
return; // Don't emulate FFP on non-iris core shaders
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
|
||||
@@ -127,33 +145,16 @@ public 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 updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
private void commitVariant(long vkPacked, int fkLen) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
|
||||
currentFKLen = fkLen;
|
||||
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
|
||||
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
|
||||
final int programId = currentProgram.getProgramId();
|
||||
if (programId != lastBoundProgramId) {
|
||||
RENDER_BACKEND.useProgram(programId);
|
||||
lastBoundProgramId = programId;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadUniforms() {
|
||||
@@ -180,22 +181,6 @@ public 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();
|
||||
|
||||
@@ -46,116 +46,99 @@ public class Uniforms {
|
||||
private static final double LN2 = java.lang.Math.log(2.0);
|
||||
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
|
||||
|
||||
// Dirty tracking: last-uploaded generation per category + program ID.
|
||||
// Program change forces full re-upload since uniform locations differ.
|
||||
private int lastProgramId = -1;
|
||||
private int lastMvGen = -1;
|
||||
private int lastProjGen = -1;
|
||||
private int lastTexMatGen = -1;
|
||||
private int lastLightingGen = -1;
|
||||
private int lastFragmentGen = -1;
|
||||
private int lastColorGen = -1;
|
||||
private int lastNormalGen = -1;
|
||||
private int lastTexCoordGen = -1;
|
||||
private float lastLightmapX = Float.NaN;
|
||||
private float lastLightmapY = Float.NaN;
|
||||
private int lastTexGenGen = -1;
|
||||
private int lastClipPlaneGen = -1;
|
||||
private float lastLineWidth = Float.NaN;
|
||||
private int lastViewportWidth = -1;
|
||||
private int lastViewportHeight = -1;
|
||||
|
||||
/**
|
||||
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
|
||||
* Uses generation counters to skip categories that haven't changed since last upload.
|
||||
*/
|
||||
public void upload(Program program) {
|
||||
final boolean programChanged = program.getProgramId() != lastProgramId;
|
||||
lastProgramId = program.getProgramId();
|
||||
final Program.UploadState st = program.uploadState;
|
||||
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int mvLinearGen = GLStateManager.mvLinearGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
final boolean mvChanged = mvGen != st.mvGen;
|
||||
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen;
|
||||
final boolean projChanged = projGen != st.projGen;
|
||||
final boolean texMatChanged = texMatGen != st.texMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged);
|
||||
st.mvGen = mvGen;
|
||||
st.mvLinearGen = mvLinearGen;
|
||||
st.projGen = projGen;
|
||||
st.texMatGen = texMatGen;
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightingEnabled()) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
if (litGen != st.lightingGen) {
|
||||
uploadLighting(program);
|
||||
lastLightingGen = litGen;
|
||||
st.lightingGen = litGen;
|
||||
}
|
||||
}
|
||||
|
||||
// Current color/normal/texcoord — skip if generation unchanged
|
||||
if (!program.getVertexKey().hasVertexColor()) {
|
||||
final int colorGen = GLStateManager.colorGeneration;
|
||||
if (programChanged || colorGen != lastColorGen) {
|
||||
if (colorGen != st.colorGen) {
|
||||
uploadCurrentColor(program);
|
||||
lastColorGen = colorGen;
|
||||
st.colorGen = colorGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
|
||||
final int normalGen = ShaderManager.getNormalGeneration();
|
||||
if (programChanged || normalGen != lastNormalGen) {
|
||||
if (normalGen != st.normalGen) {
|
||||
uploadCurrentNormal(program);
|
||||
lastNormalGen = normalGen;
|
||||
st.normalGen = normalGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
|
||||
final int texGen = ShaderManager.getTexCoordGeneration();
|
||||
if (programChanged || texGen != lastTexCoordGen) {
|
||||
if (texGen != st.texCoordGen) {
|
||||
uploadCurrentTexCoords(program);
|
||||
lastTexCoordGen = texGen;
|
||||
st.texCoordGen = texGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
|
||||
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
|
||||
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) {
|
||||
uploadCurrentLightmapCoord(program);
|
||||
lastLightmapX = GLSMConfig.lastBrightnessX;
|
||||
lastLightmapY = GLSMConfig.lastBrightnessY;
|
||||
st.lightmapX = GLSMConfig.lastBrightnessX;
|
||||
st.lightmapY = GLSMConfig.lastBrightnessY;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().texGenEnabled()) {
|
||||
final int tgGen = GLStateManager.texGenGeneration;
|
||||
if (programChanged || tgGen != lastTexGenGen) {
|
||||
if (tgGen != st.texGenGen) {
|
||||
uploadTexGen(program);
|
||||
lastTexGenGen = tgGen;
|
||||
st.texGenGen = tgGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().clipPlanesEnabled()) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
if (cpGen != st.clipPlaneGen) {
|
||||
uploadClipPlanes(program);
|
||||
lastClipPlaneGen = cpGen;
|
||||
st.clipPlaneGen = cpGen;
|
||||
}
|
||||
}
|
||||
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
if (fragGen != st.fragmentGen) {
|
||||
uploadFragmentUniforms(program);
|
||||
lastFragmentGen = fragGen;
|
||||
st.fragmentGen = fragGen;
|
||||
}
|
||||
|
||||
// Wide line emulation uniforms
|
||||
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
|
||||
uploadWideLineUniforms(program, programChanged);
|
||||
uploadWideLineUniforms(program);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
private void uploadMatrices(Program program, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
@@ -165,7 +148,9 @@ public class Uniforms {
|
||||
mv.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (mvLinearChanged) {
|
||||
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
|
||||
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
@@ -314,12 +299,13 @@ public class Uniforms {
|
||||
if (program.locCurrentColor == -1) return;
|
||||
// Upload the current color from GLSM
|
||||
final var color = GLStateManager.getColor();
|
||||
tempVec4.set(
|
||||
Math.clamp(0f, 1f, color.getRed()),
|
||||
Math.clamp(0f, 1f, color.getGreen()),
|
||||
Math.clamp(0f, 1f, color.getBlue()),
|
||||
Math.clamp(0f, 1f, color.getAlpha()));
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(Math.clamp(0f, 1f, color.getRed()));
|
||||
vec4Buf.put(Math.clamp(0f, 1f, color.getGreen()));
|
||||
vec4Buf.put(Math.clamp(0f, 1f, color.getBlue()));
|
||||
vec4Buf.put(Math.clamp(0f, 1f, color.getAlpha()));
|
||||
vec4Buf.flip();
|
||||
tempVec4.get(vec4Buf);
|
||||
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
|
||||
}
|
||||
|
||||
@@ -342,10 +328,7 @@ public class Uniforms {
|
||||
// Upload current normal from FFPShaderManager's tracked normal
|
||||
final var normal = ShaderManager.getCurrentNormal();
|
||||
vec3Buf.clear();
|
||||
vec3Buf.put(normal.x);
|
||||
vec3Buf.put(normal.y);
|
||||
vec3Buf.put(normal.z);
|
||||
vec3Buf.flip();
|
||||
normal.get(vec3Buf);
|
||||
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
|
||||
}
|
||||
|
||||
@@ -381,10 +364,7 @@ public class Uniforms {
|
||||
private void uploadPlane(int loc, float[] plane) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(plane[0]);
|
||||
vec4Buf.put(plane[1]);
|
||||
vec4Buf.put(plane[2]);
|
||||
vec4Buf.put(plane[3]);
|
||||
vec4Buf.put(plane, 0, 4);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(loc, vec4Buf);
|
||||
}
|
||||
@@ -470,29 +450,26 @@ public class Uniforms {
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadWideLineUniforms(Program program, boolean programChanged) {
|
||||
private void uploadWideLineUniforms(Program program) {
|
||||
final Program.UploadState st = program.uploadState;
|
||||
final float lineWidth = GLStateManager.getLineState().getWidth();
|
||||
if (programChanged || lineWidth != lastLineWidth) {
|
||||
if (lineWidth != st.lineWidth) {
|
||||
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
|
||||
lastLineWidth = lineWidth;
|
||||
st.lineWidth = lineWidth;
|
||||
}
|
||||
final int vw = GLStateManager.getViewportState().width;
|
||||
final int vh = GLStateManager.getViewportState().height;
|
||||
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
|
||||
if (vw != st.viewportWidth || vh != st.viewportHeight) {
|
||||
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
|
||||
lastViewportWidth = vw;
|
||||
lastViewportHeight = vh;
|
||||
st.viewportWidth = vw;
|
||||
st.viewportHeight = vh;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadVec4(int loc, Vector4f v) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(v.x);
|
||||
vec4Buf.put(v.y);
|
||||
vec4Buf.put(v.z);
|
||||
vec4Buf.put(v.w);
|
||||
vec4Buf.flip();
|
||||
v.get(vec4Buf);
|
||||
RENDER_BACKEND.uniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -35,11 +35,30 @@ public final class EcosystemNarrowRules {
|
||||
"com.fiskmods.lightsabers.asm.transformers"
|
||||
)),
|
||||
|
||||
new NarrowRule("FiskHeroes", "com.fiskmods.heroes.asm", List.of(
|
||||
"com.fiskmods.heroes.asm.SHLoadingPlugin",
|
||||
"com.fiskmods.heroes.asm.transformers"
|
||||
)),
|
||||
|
||||
new NarrowRule("Ears", "com.unascribed.ears", List.of(
|
||||
"com.unascribed.ears.asm",
|
||||
"com.unascribed.ears.common.agent"
|
||||
)),
|
||||
|
||||
new NarrowRule("FoamFix", "pl.asie.foamfix", List.of(
|
||||
"pl.asie.foamfix.bugfixmod",
|
||||
"pl.asie.foamfix.coremod",
|
||||
"pl.asie.foamfix.forkage",
|
||||
"pl.asie.foamfix.ghostbuster",
|
||||
"pl.asie.foamfix.FoamFixMod",
|
||||
"pl.asie.foamfix.HackyMessageFactoryWrapper",
|
||||
"pl.asie.foamfix.LaunchWrapperRuntimeFix",
|
||||
"pl.asie.foamfix.ProxyClient",
|
||||
"pl.asie.foamfix.ProxyCommon",
|
||||
"pl.asie.foamfix.repack.com.unascribed.ears.asm",
|
||||
"pl.asie.foamfix.repack.com.unascribed.ears.common.agent"
|
||||
)),
|
||||
|
||||
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
|
||||
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
|
||||
"alfheim.common.core.asm.hook.AlfheimHPHooks",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+56
-13
@@ -5,11 +5,13 @@ 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
|
||||
@@ -18,28 +20,37 @@ import java.util.Arrays;
|
||||
public final class ImmediateModeRecorder {
|
||||
private static final CallbackTessellator tessellator = new CallbackTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/** Separate tessellator for the splash thread. Nulled after splash completes. */
|
||||
private static DirectTessellator splashTessellator;
|
||||
private record ThreadTessellator(Thread thread, DirectTessellator tessellator) {}
|
||||
|
||||
private static final Queue<ThreadTessellator> threadTessellators = new ConcurrentLinkedQueue<>();
|
||||
private static final ThreadLocal<DirectTessellator> threadTessellator = ThreadLocal.withInitial(() -> {
|
||||
final DirectTessellator t = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
threadTessellators.add(new ThreadTessellator(Thread.currentThread(), t));
|
||||
cleanupOrphanTessellators();
|
||||
return t;
|
||||
});
|
||||
|
||||
private ImmediateModeRecorder() {
|
||||
|
||||
}
|
||||
|
||||
public static void initSplashTessellator() {
|
||||
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
public static int getThreadTessellatorCount() {
|
||||
return threadTessellators.size();
|
||||
}
|
||||
|
||||
public static void destroySplashTessellator() {
|
||||
splashTessellator.delete();
|
||||
splashTessellator = null;
|
||||
public static synchronized void cleanupOrphanTessellators() {
|
||||
threadTessellators.removeIf(tt -> {
|
||||
if (tt.thread().isAlive()) return false;
|
||||
tt.tessellator().delete();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static DirectTessellator tessellator() {
|
||||
final DirectTessellator splash = splashTessellator;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
return splash;
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
return tessellator;
|
||||
}
|
||||
return tessellator;
|
||||
return threadTessellator.get();
|
||||
}
|
||||
|
||||
public static DirectTessellator getInternalTessellator() {
|
||||
@@ -293,7 +304,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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+57
-37
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
-69
@@ -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);
|
||||
}
|
||||
}
|
||||
+37
-69
@@ -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,13 @@ 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";
|
||||
private static final String ARBBufferObject = "org/lwjgl/opengl/ARBBufferObject";
|
||||
private static final String ARBVertexBufferObject = "org/lwjgl/opengl/ARBVertexBufferObject";
|
||||
private static final String ARBBufferStorage = "org/lwjgl/opengl/ARBBufferStorage";
|
||||
private static final String ARBCopyBuffer = "org/lwjgl/opengl/ARBCopyBuffer";
|
||||
private static final String ARBMapBufferRange = "org/lwjgl/opengl/ARBMapBufferRange";
|
||||
|
||||
// Redirect VAO related calls from NHLib
|
||||
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
|
||||
@@ -126,6 +133,7 @@ public class GLSMRedirector {
|
||||
.add("glFrustum")
|
||||
.add("glGetBoolean")
|
||||
.add("glGetFloat")
|
||||
.add("glGetFloatv", "glGetFloat")
|
||||
.add("glGetInteger")
|
||||
.add("glGetLight")
|
||||
.add("glGetMaterial")
|
||||
@@ -282,11 +290,16 @@ public class GLSMRedirector {
|
||||
.add("glBindBuffer")
|
||||
.add("glDeleteBuffers")
|
||||
.add("glBufferData")
|
||||
.add("nglBufferData")
|
||||
.add("glBufferSubData")
|
||||
.add("nglBufferSubData")
|
||||
.add("glMapBuffer")
|
||||
.add("glUnmapBuffer")
|
||||
.add("glGetBufferSubData")
|
||||
.add("nglGetBufferSubData")
|
||||
.add("glGetBufferParameteri")
|
||||
.add("glGetBufferParameter")
|
||||
.add("glGetBufferParameteriv")
|
||||
.add("glIsBuffer");
|
||||
final var gl20 = RedirectMap.newMap()
|
||||
.add("glBlendEquationSeparate")
|
||||
@@ -296,6 +309,7 @@ public class GLSMRedirector {
|
||||
.add("glStencilOpSeparate")
|
||||
.add("glUseProgram")
|
||||
.add("glShaderSource")
|
||||
.add("nglShaderSource")
|
||||
.add("glLinkProgram")
|
||||
.add("glDeleteProgram")
|
||||
.add("glCreateShader")
|
||||
@@ -321,6 +335,7 @@ public class GLSMRedirector {
|
||||
.add("glUniformMatrix2")
|
||||
.add("glUniformMatrix3")
|
||||
.add("glUniformMatrix4")
|
||||
.add("glUniformMatrix4fv", "glUniformMatrix4")
|
||||
.add("glDeleteShader")
|
||||
.add("glGetShaderi")
|
||||
.add("glGetShaderInfoLog")
|
||||
@@ -342,6 +357,7 @@ public class GLSMRedirector {
|
||||
.add("glGenVertexArrays")
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
.add("glIsVertexArray")
|
||||
.add("glBindFramebuffer")
|
||||
.add("glDeleteFramebuffers")
|
||||
.add("glGenFramebuffers")
|
||||
@@ -350,9 +366,13 @@ public class GLSMRedirector {
|
||||
.add("glVertexAttribIPointer")
|
||||
.add("glGenerateMipmap")
|
||||
.add("glGetFramebufferAttachmentParameteri")
|
||||
.add("glBlitFramebuffer");
|
||||
.add("glBlitFramebuffer")
|
||||
.add("glMapBufferRange")
|
||||
.add("glFlushMappedBufferRange");
|
||||
final var gl31 = RedirectMap.newMap()
|
||||
.add("glDrawElementsInstanced");
|
||||
.add("glDrawElementsInstanced")
|
||||
.add("glDrawArraysInstanced")
|
||||
.add("glCopyBufferSubData");
|
||||
final var gl32 = RedirectMap.newMap()
|
||||
.add("glFramebufferTexture");
|
||||
final var gl33 = RedirectMap.newMap()
|
||||
@@ -376,6 +396,10 @@ public class GLSMRedirector {
|
||||
final var gl44 = RedirectMap.newMap()
|
||||
.add("glBufferStorage")
|
||||
.add("glClearTexImage");
|
||||
final var gl45 = RedirectMap.newMap()
|
||||
.add("glCreateBuffers")
|
||||
.add("glNamedBufferData")
|
||||
.add("glNamedBufferSubData");
|
||||
|
||||
// Merge all GL version maps into the flat lookup
|
||||
glMethodRedirects.putAll(gl11);
|
||||
@@ -391,6 +415,7 @@ public class GLSMRedirector {
|
||||
glMethodRedirects.putAll(gl42);
|
||||
glMethodRedirects.putAll(gl43);
|
||||
glMethodRedirects.putAll(gl44);
|
||||
glMethodRedirects.putAll(gl45);
|
||||
|
||||
// MINECRAFT
|
||||
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
|
||||
@@ -455,6 +480,39 @@ 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")
|
||||
);
|
||||
final var arbBufferObject = RedirectMap.newMap()
|
||||
.add("glBindBufferARB", "glBindBuffer")
|
||||
.add("glGenBuffersARB", "glGenBuffers")
|
||||
.add("glDeleteBuffersARB", "glDeleteBuffers")
|
||||
.add("glIsBufferARB", "glIsBuffer")
|
||||
.add("glBufferDataARB", "glBufferData")
|
||||
.add("glBufferSubDataARB", "glBufferSubData")
|
||||
.add("glGetBufferSubDataARB", "glGetBufferSubData")
|
||||
.add("glMapBufferARB", "glMapBuffer")
|
||||
.add("glUnmapBufferARB", "glUnmapBuffer")
|
||||
.add("glGetBufferParameterARB", "glGetBufferParameter")
|
||||
.add("glGetBufferParameteriARB", "glGetBufferParameteri")
|
||||
.add("glGetBufferParameterivARB", "glGetBufferParameteriv");
|
||||
methodRedirects.put(ARBBufferObject, arbBufferObject);
|
||||
methodRedirects.put(ARBVertexBufferObject, arbBufferObject);
|
||||
methodRedirects.put(ARBBufferStorage, RedirectMap.newMap()
|
||||
.add("glBufferStorage")
|
||||
);
|
||||
methodRedirects.put(ARBCopyBuffer, RedirectMap.newMap()
|
||||
.add("glCopyBufferSubData")
|
||||
);
|
||||
methodRedirects.put(ARBMapBufferRange, RedirectMap.newMap()
|
||||
.add("glMapBufferRange")
|
||||
.add("glFlushMappedBufferRange")
|
||||
);
|
||||
|
||||
// APPLE
|
||||
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
|
||||
@@ -587,18 +645,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 +673,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);
|
||||
|
||||
@@ -27,65 +27,75 @@ public class LightModelState implements ISettableState<LightModelState> {
|
||||
twoSide = 0.0F;
|
||||
}
|
||||
|
||||
public void setAmbient(FloatBuffer newBuffer) {
|
||||
public boolean setAmbient(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setAmbient(IntBuffer newBuffer) {
|
||||
public boolean setAmbient(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setColorControl(int val) {
|
||||
public boolean setColorControl(int val) {
|
||||
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
|
||||
this.colorControl = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setColorControl(IntBuffer newBuffer) {
|
||||
setColorControl(newBuffer.get());
|
||||
public boolean setColorControl(IntBuffer newBuffer) {
|
||||
return setColorControl(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setLocalViewer(float val) {
|
||||
public boolean setLocalViewer(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
|
||||
this.localViewer = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setLocalViewer(FloatBuffer newBuffer) {
|
||||
setLocalViewer(newBuffer.get());
|
||||
public boolean setLocalViewer(FloatBuffer newBuffer) {
|
||||
return setLocalViewer(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setLocalViewer(IntBuffer newBuffer) {
|
||||
setLocalViewer((float) newBuffer.get());
|
||||
public boolean setLocalViewer(IntBuffer newBuffer) {
|
||||
return setLocalViewer((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setLocalViewer(int val) {
|
||||
setLocalViewer((float) val);
|
||||
public boolean setLocalViewer(int val) {
|
||||
return setLocalViewer((float) val);
|
||||
}
|
||||
|
||||
public void setTwoSide(float val) {
|
||||
public boolean setTwoSide(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
|
||||
this.twoSide = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setTwoSide(FloatBuffer newBuffer) {
|
||||
setTwoSide(newBuffer.get());
|
||||
public boolean setTwoSide(FloatBuffer newBuffer) {
|
||||
return setTwoSide(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setTwoSide(IntBuffer newBuffer) {
|
||||
setTwoSide((float) newBuffer.get());
|
||||
public boolean setTwoSide(IntBuffer newBuffer) {
|
||||
return setTwoSide((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setTwoSide(int val) {
|
||||
setTwoSide((float) val);
|
||||
public boolean setTwoSide(int val) {
|
||||
return setTwoSide((float) val);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ public class LightState implements ISettableState<LightState> {
|
||||
|
||||
private static final Vector4i vector4i = new Vector4i();
|
||||
private static final Vector4f vector4f = new Vector4f();
|
||||
private static final Vector3f vector3f = new Vector3f();
|
||||
private static final Vector3i vector3i = new Vector3i();
|
||||
private static final Matrix3f matrix3f = new Matrix3f();
|
||||
|
||||
@@ -27,6 +28,10 @@ public class LightState implements ISettableState<LightState> {
|
||||
public final Vector4f specular;
|
||||
public final Vector4f position;
|
||||
public final Vector3f spotDirection;
|
||||
private final Vector4f rawPosition;
|
||||
private int posMvGeneration = -1;
|
||||
private final Vector3f rawSpotDirection;
|
||||
private int spotDirLinearGeneration = -1;
|
||||
public float spotExponent;
|
||||
public float spotCutoff;
|
||||
public float spotCosCutoff;
|
||||
@@ -66,178 +71,221 @@ public class LightState implements ISettableState<LightState> {
|
||||
this.linearAttenuation = linearAttenuation;
|
||||
this.quadraticAttenuation = quadraticAttenuation;
|
||||
|
||||
this.rawPosition = new Vector4f(position);
|
||||
this.position = position;
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
this.posMvGeneration = GLStateManager.mvGeneration;
|
||||
|
||||
this.rawSpotDirection = new Vector3f(spotDirection);
|
||||
this.spotDirection = spotDirection;
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration;
|
||||
}
|
||||
|
||||
public void setAmbient(FloatBuffer newBuffer) {
|
||||
public boolean setAmbient(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setAmbient(IntBuffer newBuffer) {
|
||||
public boolean setAmbient(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setDiffuse(FloatBuffer newBuffer) {
|
||||
public boolean setDiffuse(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setDiffuse(IntBuffer newBuffer) {
|
||||
public boolean setDiffuse(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpecular(FloatBuffer newBuffer) {
|
||||
public boolean setSpecular(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpecular(IntBuffer newBuffer) {
|
||||
public boolean setSpecular(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setPosition(FloatBuffer newBuffer) {
|
||||
// We are bypassing cache everytime for position, because the necessary components to enable tracking the cache
|
||||
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
|
||||
// with and the untransformed coordinates in addition to the final transformation.
|
||||
this.position.set(newBuffer);
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
public boolean setPosition(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
return applyPosition(vector4f);
|
||||
}
|
||||
|
||||
public void setPosition(IntBuffer newBuffer) {
|
||||
public boolean setPosition(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
this.position.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
vector4f.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
|
||||
return applyPosition(vector4f);
|
||||
}
|
||||
|
||||
public void setSpotDirection(FloatBuffer newBuffer) {
|
||||
// We are bypassing cache everytime for spot direction, because the necessary components to enable tracking the cache
|
||||
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
|
||||
// with and the untransformed coordinates in addition to the final transformation.
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.set(newBuffer);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
private boolean applyPosition(Vector4f raw) {
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
|
||||
this.rawPosition.set(raw);
|
||||
this.posMvGeneration = mvGen;
|
||||
this.position.set(raw);
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpotDirection(IntBuffer newBuffer) {
|
||||
public boolean setSpotDirection(FloatBuffer newBuffer) {
|
||||
vector3f.set(newBuffer);
|
||||
return applySpotDirection(vector3f);
|
||||
}
|
||||
|
||||
public boolean setSpotDirection(IntBuffer newBuffer) {
|
||||
vector3i.set(newBuffer);
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
|
||||
return applySpotDirection(vector3f);
|
||||
}
|
||||
|
||||
public void setSpotExponent(FloatBuffer newBuffer) {
|
||||
setSpotExponent(newBuffer.get());
|
||||
private boolean applySpotDirection(Vector3f raw) {
|
||||
final int mvLinearGen = GLStateManager.mvLinearGeneration;
|
||||
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
|
||||
this.rawSpotDirection.set(raw);
|
||||
this.spotDirLinearGeneration = mvLinearGen;
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.set(raw);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpotExponent(IntBuffer newBuffer) {
|
||||
setSpotExponent((float) newBuffer.get());
|
||||
public boolean setSpotExponent(FloatBuffer newBuffer) {
|
||||
return setSpotExponent(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setSpotExponent(int i) {
|
||||
setSpotExponent((float) i);
|
||||
public boolean setSpotExponent(IntBuffer newBuffer) {
|
||||
return setSpotExponent((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setSpotExponent(float f) {
|
||||
public boolean setSpotExponent(int i) {
|
||||
return setSpotExponent((float) i);
|
||||
}
|
||||
|
||||
public boolean setSpotExponent(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
|
||||
this.spotExponent = f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpotCutoff(FloatBuffer newBuffer) {
|
||||
setSpotCutoff(newBuffer.get());
|
||||
public boolean setSpotCutoff(FloatBuffer newBuffer) {
|
||||
return setSpotCutoff(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setSpotCutoff(IntBuffer newBuffer) {
|
||||
setSpotCutoff((float) newBuffer.get());
|
||||
public boolean setSpotCutoff(IntBuffer newBuffer) {
|
||||
return setSpotCutoff((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setSpotCutoff(int i) {
|
||||
setSpotCutoff((float) i);
|
||||
public boolean setSpotCutoff(int i) {
|
||||
return setSpotCutoff((float) i);
|
||||
}
|
||||
|
||||
public void setSpotCutoff(float f) {
|
||||
public boolean setSpotCutoff(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
|
||||
this.spotCutoff = f;
|
||||
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setConstantAttenuation(FloatBuffer newBuffer) {
|
||||
setConstantAttenuation(newBuffer.get());
|
||||
public boolean setConstantAttenuation(FloatBuffer newBuffer) {
|
||||
return setConstantAttenuation(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setConstantAttenuation(IntBuffer newBuffer) {
|
||||
setConstantAttenuation((float) newBuffer.get());
|
||||
public boolean setConstantAttenuation(IntBuffer newBuffer) {
|
||||
return setConstantAttenuation((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setConstantAttenuation(int i) {
|
||||
setConstantAttenuation((float) i);
|
||||
public boolean setConstantAttenuation(int i) {
|
||||
return setConstantAttenuation((float) i);
|
||||
}
|
||||
|
||||
public void setConstantAttenuation(float f) {
|
||||
public boolean setConstantAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
|
||||
this.constantAttenuation = f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setLinearAttenuation(FloatBuffer newBuffer) {
|
||||
setLinearAttenuation(newBuffer.get());
|
||||
public boolean setLinearAttenuation(FloatBuffer newBuffer) {
|
||||
return setLinearAttenuation(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setLinearAttenuation(IntBuffer newBuffer) {
|
||||
setLinearAttenuation((float) newBuffer.get());
|
||||
public boolean setLinearAttenuation(IntBuffer newBuffer) {
|
||||
return setLinearAttenuation((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setLinearAttenuation(int i) {
|
||||
setLinearAttenuation((float) i);
|
||||
public boolean setLinearAttenuation(int i) {
|
||||
return setLinearAttenuation((float) i);
|
||||
}
|
||||
|
||||
public void setLinearAttenuation(float f) {
|
||||
public boolean setLinearAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
|
||||
this.linearAttenuation = f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setQuadraticAttenuation(FloatBuffer newBuffer) {
|
||||
setQuadraticAttenuation(newBuffer.get());
|
||||
public boolean setQuadraticAttenuation(FloatBuffer newBuffer) {
|
||||
return setQuadraticAttenuation(newBuffer.get());
|
||||
}
|
||||
|
||||
public void setQuadraticAttenuation(IntBuffer newBuffer) {
|
||||
setQuadraticAttenuation((float) newBuffer.get());
|
||||
public boolean setQuadraticAttenuation(IntBuffer newBuffer) {
|
||||
return setQuadraticAttenuation((float) newBuffer.get());
|
||||
}
|
||||
|
||||
public void setQuadraticAttenuation(int i) {
|
||||
setQuadraticAttenuation((float) i);
|
||||
public boolean setQuadraticAttenuation(int i) {
|
||||
return setQuadraticAttenuation((float) i);
|
||||
}
|
||||
|
||||
public void setQuadraticAttenuation(float f) {
|
||||
public boolean setQuadraticAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
|
||||
this.quadraticAttenuation = f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,7 +294,11 @@ public class LightState implements ISettableState<LightState> {
|
||||
this.diffuse.set(state.diffuse);
|
||||
this.specular.set(state.specular);
|
||||
this.position.set(state.position);
|
||||
this.rawPosition.set(state.rawPosition);
|
||||
this.posMvGeneration = state.posMvGeneration;
|
||||
this.spotDirection.set(state.spotDirection);
|
||||
this.rawSpotDirection.set(state.rawSpotDirection);
|
||||
this.spotDirLinearGeneration = state.spotDirLinearGeneration;
|
||||
this.spotExponent = state.spotExponent;
|
||||
this.spotCutoff = state.spotCutoff;
|
||||
this.spotCosCutoff = state.spotCosCutoff;
|
||||
|
||||
@@ -37,82 +37,100 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
colorIndexes = new Vector3f(0.0F, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
public void setAmbient(FloatBuffer newBuffer) {
|
||||
public boolean setAmbient(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setAmbient(IntBuffer newBuffer) {
|
||||
public boolean setAmbient(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setDiffuse(FloatBuffer newBuffer) {
|
||||
public boolean setDiffuse(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setDiffuse(IntBuffer newBuffer) {
|
||||
public boolean setDiffuse(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpecular(FloatBuffer newBuffer) {
|
||||
public boolean setSpecular(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSpecular(IntBuffer newBuffer) {
|
||||
public boolean setSpecular(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setEmission(FloatBuffer newBuffer) {
|
||||
public boolean setEmission(FloatBuffer newBuffer) {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
|
||||
this.emission.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setEmission(IntBuffer newBuffer) {
|
||||
public boolean setEmission(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
|
||||
this.emission.set(vector4f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setShininess(float val) {
|
||||
public boolean setShininess(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
|
||||
this.shininess = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// On the buffer versions of shininess, we need to call `get(0)` explicitly specifying the index
|
||||
// otherwise calling `get()` advances the buffer position, and some mods may re-use the same buffer
|
||||
// in subsequent calls, resulting in a BufferUnderflowException.
|
||||
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get(0)); }
|
||||
public boolean setShininess(FloatBuffer newBuffer) { return setShininess(newBuffer.get(0)); }
|
||||
|
||||
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get(0)); }
|
||||
public boolean setShininess(IntBuffer newBuffer) { return setShininess((float) newBuffer.get(0)); }
|
||||
|
||||
public void setShininess(int val) { setShininess((float) val);}
|
||||
public boolean setShininess(int val) { return setShininess((float) val);}
|
||||
|
||||
public void setColorIndexes(FloatBuffer newBuffer) {
|
||||
public boolean setColorIndexes(FloatBuffer newBuffer) {
|
||||
// You are reading this correctly, nvidia drivers flip the y and z values in
|
||||
// glMaterial specifically for GL_COLOR_INDEXES. Other drivers do not do this.
|
||||
// This probably breaks things but who knows, GLSM is setup to track to what the
|
||||
@@ -125,15 +143,19 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
|
||||
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
|
||||
this.colorIndexes.set(vector3f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setColorIndexes(IntBuffer newBuffer) {
|
||||
public boolean setColorIndexes(IntBuffer newBuffer) {
|
||||
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
|
||||
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
|
||||
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
|
||||
this.colorIndexes.set(vector3f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-27
@@ -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;
|
||||
@@ -89,13 +88,18 @@ public class TessellatorStreamingDrawer {
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
final int requiredBytes = vertexCount * vertexSize;
|
||||
ensureRepackCapacity(requiredBytes);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
ensureRepackCapacity(requiredBytes);
|
||||
|
||||
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit((int)(writePtr - repackAddress));
|
||||
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit((int)(writePtr - repackAddress));
|
||||
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
|
||||
// Shrink rawBuffer if oversized
|
||||
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
|
||||
@@ -188,30 +192,29 @@ public class TessellatorStreamingDrawer {
|
||||
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
|
||||
ensureVAO(flags, format);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(packed, vertexSize);
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(packed, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(packed);
|
||||
firstVertex = 0;
|
||||
}
|
||||
|
||||
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(packed);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
|
||||
|
||||
// Use program and upload — should not error
|
||||
GL20.glUseProgram(program);
|
||||
CompatUniformManager.onUseProgram(program);
|
||||
CompatUniformManager.refreshCompatUniforms(program);
|
||||
|
||||
// Verify no GL errors
|
||||
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after uniform upload");
|
||||
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
|
||||
|
||||
// Use program and upload — should not error
|
||||
GL20.glUseProgram(program);
|
||||
CompatUniformManager.onUseProgram(program);
|
||||
CompatUniformManager.refreshCompatUniforms(program);
|
||||
|
||||
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after fog uniform upload");
|
||||
}
|
||||
|
||||
+28
@@ -9,9 +9,11 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/** Tests GLSM display list behavior matches OpenGL spec. */
|
||||
@@ -257,4 +259,30 @@ class DisplayListCompilationGLSMTest {
|
||||
|
||||
GLStateManager.glEndList();
|
||||
}
|
||||
|
||||
@Test
|
||||
void glsm_unsupportedOpInList_abortsCleanly() {
|
||||
testList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
assertTrue(DisplayListManager.isRecording(), "recording after glNewList");
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
|
||||
|
||||
assertFalse(DisplayListManager.isRecording(), "recording state must be reset after an in-list throw");
|
||||
assertEquals(DisplayListManager.RecordMode.NONE, DisplayListManager.getRecordMode());
|
||||
|
||||
assertDoesNotThrow(() -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
|
||||
assertDoesNotThrow(GLStateManager::glEndList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void glsm_abortIfLeaked_recoversOpenList() {
|
||||
testList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
assertTrue(DisplayListManager.isRecording());
|
||||
|
||||
DisplayListManager.abortIfLeaked();
|
||||
|
||||
assertFalse(DisplayListManager.isRecording(), "frame-begin backstop must clear a leaked open list");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import org.junit.jupiter.api.extension.AfterAllCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.opengl.ContextAttribs;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.DisplayMode;
|
||||
import org.lwjgl.opengl.PixelFormat;
|
||||
|
||||
/**
|
||||
* 3.3 core-profile context extension
|
||||
*/
|
||||
public class GLSMCoreExtension implements BeforeAllCallback, AfterAllCallback {
|
||||
private boolean ownsDisplay = false;
|
||||
|
||||
@Override
|
||||
public void beforeAll(ExtensionContext context) throws LWJGLException {
|
||||
if (Display.isCreated()) return;
|
||||
Display.setDisplayModeAndFullscreen(new DisplayMode(800, 600));
|
||||
Display.setResizable(false);
|
||||
Display.setFullscreen(false);
|
||||
Display.create(
|
||||
new PixelFormat().withDepthBits(24).withStencilBits(8),
|
||||
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
|
||||
ownsDisplay = true;
|
||||
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete();
|
||||
GLStateManager.BYPASS_CACHE = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAll(ExtensionContext context) {
|
||||
if (ownsDisplay && Display.isCreated()) Display.destroy();
|
||||
ownsDisplay = false;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
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;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.opengl.Display;
|
||||
@@ -19,7 +19,19 @@ import java.lang.reflect.Field;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
|
||||
|
||||
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExtensionContext.Store.CloseableResource {
|
||||
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExecutionCondition, ExtensionContext.Store.CloseableResource {
|
||||
|
||||
// Compat profile is not available on OSX
|
||||
private static final boolean IS_MAC = System.getProperty("os.name", "").toLowerCase().contains("mac");
|
||||
private static final boolean FORCE_RUN = Boolean.getBoolean("angelica.glsm.test.forceRun");
|
||||
|
||||
@Override
|
||||
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||
if (IS_MAC && !FORCE_RUN) {
|
||||
return ConditionEvaluationResult.disabled("Skipped on macOS: needs GL3+ compat profile");
|
||||
}
|
||||
return ConditionEvaluationResult.enabled("OS supports GL3+ compat profile");
|
||||
}
|
||||
|
||||
private static final Unsafe theUnsafe;
|
||||
static {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
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.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@ExtendWith(GLSMCoreExtension.class)
|
||||
public class GLSM_DirtyTracking_UnitTest {
|
||||
|
||||
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
|
||||
@BeforeEach
|
||||
void pushTrackedState() {
|
||||
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_FOG_BIT | GL11.GL_CURRENT_BIT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void popTrackedState() {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
@Test
|
||||
void materialBumpsOnlyOnChange() {
|
||||
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
|
||||
final int gen = GLStateManager.lightingGeneration;
|
||||
|
||||
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
|
||||
assertEquals(gen, GLStateManager.lightingGeneration, "identical material must not bump lightingGeneration");
|
||||
|
||||
newf4b(0.9F, 0.2F, 0.3F, 0.4F);
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
|
||||
assertEquals(gen + 1, GLStateManager.lightingGeneration, "changed material must bump lightingGeneration once");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightModelBumpsOnlyOnChange() {
|
||||
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
|
||||
final int gen = GLStateManager.lightingGeneration;
|
||||
|
||||
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
|
||||
assertEquals(gen, GLStateManager.lightingGeneration, "identical light model ambient must not bump");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightColorBumpsOnlyOnChange() {
|
||||
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
|
||||
final int gen = GLStateManager.lightingGeneration;
|
||||
|
||||
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
|
||||
assertEquals(gen, GLStateManager.lightingGeneration, "identical light diffuse must not bump");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightPositionDirtySkipsUnderSameModelview() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
|
||||
|
||||
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
|
||||
final int gen = GLStateManager.lightingGeneration;
|
||||
|
||||
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
|
||||
assertEquals(gen, GLStateManager.lightingGeneration, "same position under same modelview must not bump");
|
||||
|
||||
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
|
||||
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
|
||||
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw position under changed modelview must bump");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
@Test
|
||||
void translationDoesNotBumpLinearGeneration() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
final int mv = GLStateManager.mvGeneration;
|
||||
final int lin = GLStateManager.mvLinearGeneration;
|
||||
|
||||
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
|
||||
assertEquals(mv + 1, GLStateManager.mvGeneration, "translate must bump mvGeneration");
|
||||
assertEquals(lin, GLStateManager.mvLinearGeneration, "translate must NOT bump mvLinearGeneration");
|
||||
|
||||
GLStateManager.glRotatef(30.0F, 0.0F, 1.0F, 0.0F);
|
||||
assertEquals(mv + 2, GLStateManager.mvGeneration, "rotate must bump mvGeneration");
|
||||
assertEquals(lin + 1, GLStateManager.mvLinearGeneration, "rotate must bump mvLinearGeneration");
|
||||
|
||||
GLStateManager.glScalef(2.0F, 2.0F, 2.0F);
|
||||
assertEquals(lin + 2, GLStateManager.mvLinearGeneration, "scale must bump mvLinearGeneration");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fogBumpsOnlyOnChange() {
|
||||
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
|
||||
final int gen = GLStateManager.fragmentGeneration;
|
||||
|
||||
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
|
||||
assertEquals(gen, GLStateManager.fragmentGeneration, "identical fog density must not bump fragmentGeneration");
|
||||
|
||||
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.7F);
|
||||
assertEquals(gen + 1, GLStateManager.fragmentGeneration, "changed fog density must bump fragmentGeneration");
|
||||
}
|
||||
|
||||
@Test
|
||||
void spotDirectionDirtySkipsUnderTranslationOnly() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
newf3b(0.0F, 0.0F, -1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
|
||||
final int gen = GLStateManager.lightingGeneration;
|
||||
|
||||
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
|
||||
newf3b(0.0F, 0.0F, -1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
|
||||
assertEquals(gen, GLStateManager.lightingGeneration, "spot direction under translation-only must not bump");
|
||||
|
||||
GLStateManager.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
|
||||
newf3b(0.0F, 0.0F, -1.0F);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
|
||||
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw spot direction under changed 3x3 must bump");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorMaterialFreezesLastColorOnDisable() {
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
GLStateManager.glColor4f(1.0F, 0.0F, 0.0F, 1.0F); // enable-time color
|
||||
GLStateManager.enableColorMaterial();
|
||||
GLStateManager.glColor4f(0.0F, 0.0F, 1.0F, 1.0F); // last color while enabled
|
||||
GLStateManager.disableColorMaterial();
|
||||
|
||||
final Vector4f diffuse = GLStateManager.getFrontMaterial().diffuse;
|
||||
assertEquals(0.0F, diffuse.x, "diffuse must freeze at last color (blue), not enable-time red");
|
||||
assertEquals(0.0F, diffuse.y, "green channel must be 0");
|
||||
assertEquals(1.0F, diffuse.z, "blue channel must be 1");
|
||||
}
|
||||
|
||||
static void newf4b(float x, float y, float z, float w) {
|
||||
f4b.clear();
|
||||
f4b.put(x).put(y).put(z).put(w);
|
||||
f4b.flip();
|
||||
}
|
||||
|
||||
static void newf3b(float x, float y, float z) {
|
||||
f3b.clear();
|
||||
f3b.put(x).put(y).put(z);
|
||||
f3b.flip();
|
||||
}
|
||||
}
|
||||
+215
@@ -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,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class GLVersionParseTest {
|
||||
|
||||
@Test
|
||||
void parseMajorMinor() {
|
||||
assertEquals(43, RenderSystem.parseMajorMinor("4.3.0 - Build 20.19.15.4835"));
|
||||
assertEquals(46, RenderSystem.parseMajorMinor("4.6.0 NVIDIA 551.61"));
|
||||
assertEquals(41, RenderSystem.parseMajorMinor("4.1 Metal - 88"));
|
||||
assertEquals(33, RenderSystem.parseMajorMinor("3.3"));
|
||||
assertEquals(46, RenderSystem.parseMajorMinor("4.6 (Core Profile) Mesa 24.0.3"));
|
||||
assertEquals(-1, RenderSystem.parseMajorMinor(null));
|
||||
assertEquals(-1, RenderSystem.parseMajorMinor(""));
|
||||
assertEquals(-1, RenderSystem.parseMajorMinor("garbage"));
|
||||
assertEquals(-1, RenderSystem.parseMajorMinor("OpenGL ES 3.2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isContextValid() {
|
||||
assertTrue(RenderSystem.isContextValid(4, 4, 46, true));
|
||||
assertTrue(RenderSystem.isContextValid(4, 4, 44, true));
|
||||
assertFalse(RenderSystem.isContextValid(4, 4, 43, true));
|
||||
assertFalse(RenderSystem.isContextValid(4, 4, 44, false));
|
||||
assertTrue(RenderSystem.isContextValid(4, 4, -1, true));
|
||||
assertFalse(RenderSystem.isContextValid(4, 4, -1, false));
|
||||
assertTrue(RenderSystem.isContextValid(3, 3, 33, true));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(GLSMCoreExtension.class)
|
||||
class ImmediateModeThreadIsolationTest {
|
||||
|
||||
@Test
|
||||
void backgroundThreadsGetDistinctTessellators() throws Exception {
|
||||
final DirectTessellator mainTess = ImmediateModeRecorder.getInternalTessellator();
|
||||
final AtomicReference<DirectTessellator> bg1 = new AtomicReference<>();
|
||||
final AtomicReference<DirectTessellator> bg2 = new AtomicReference<>();
|
||||
|
||||
final Thread t1 = new Thread(() -> bg1.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-1");
|
||||
final Thread t2 = new Thread(() -> bg2.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-2");
|
||||
t1.start(); t2.start();
|
||||
t1.join(); t2.join();
|
||||
|
||||
assertNotNull(bg1.get());
|
||||
assertNotNull(bg2.get());
|
||||
assertNotSame(mainTess, bg1.get(), "background thread must not share the main-thread tessellator");
|
||||
assertNotSame(mainTess, bg2.get(), "background thread must not share the main-thread tessellator");
|
||||
assertNotSame(bg1.get(), bg2.get(), "background threads must not share a tessellator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentImmediateModeDoesNotCorruptRecording() throws Exception {
|
||||
final List<Throwable> failures = new CopyOnWriteArrayList<>();
|
||||
final CountDownLatch start = new CountDownLatch(1);
|
||||
final AtomicBoolean stop = new AtomicBoolean(false);
|
||||
|
||||
final Runnable spam = () -> {
|
||||
try {
|
||||
start.await();
|
||||
while (!stop.get()) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GLStateManager.glVertex3f(0, 0, 0);
|
||||
GLStateManager.glVertex3f(1, 0, 0);
|
||||
GLStateManager.glVertex3f(1, 1, 0);
|
||||
GLStateManager.glVertex3f(0, 1, 0);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failures.add(t);
|
||||
}
|
||||
};
|
||||
final Thread t1 = new Thread(spam, "iso-spam-1");
|
||||
final Thread t2 = new Thread(spam, "iso-spam-2");
|
||||
t1.start(); t2.start();
|
||||
start.countDown();
|
||||
|
||||
final List<Integer> lists = new ArrayList<>();
|
||||
try {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GLStateManager.glVertex3f(0, 0, 0);
|
||||
GLStateManager.glVertex3f(1, 0, 0);
|
||||
GLStateManager.glVertex3f(1, 1, 0);
|
||||
GLStateManager.glVertex3f(0, 1, 0);
|
||||
GLStateManager.glEnd();
|
||||
GLStateManager.glEndList();
|
||||
lists.add(list);
|
||||
}
|
||||
} finally {
|
||||
stop.set(true);
|
||||
t1.join(5000); t2.join(5000);
|
||||
}
|
||||
|
||||
assertTrue(failures.isEmpty(), "background immediate mode threw: " + failures);
|
||||
for (int list : lists) {
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(list);
|
||||
assertNotNull(compiled, "list " + list + " should be compiled");
|
||||
assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.DRAW_RANGE, 0), "list " + list + " should contain exactly the main thread's draw");
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sweepFreesDeadThreadTessellators() throws Exception {
|
||||
final Thread t = new Thread(() -> ImmediateModeRecorder.getInternalTessellator(), "iso-sweep");
|
||||
t.start();
|
||||
t.join();
|
||||
final int before = ImmediateModeRecorder.getThreadTessellatorCount();
|
||||
assertTrue(before >= 1);
|
||||
ImmediateModeRecorder.cleanupOrphanTessellators();
|
||||
assertTrue(ImmediateModeRecorder.getThreadTessellatorCount() < before, "dead thread's tessellator should be swept");
|
||||
}
|
||||
|
||||
@Test
|
||||
void drawLockGatesOnSplashComplete() throws Exception {
|
||||
assertFalse(GLStateManager.acquireDrawLock(), "post-splash acquire must be a no-op");
|
||||
|
||||
final Field f = GLStateManager.class.getDeclaredField("splashComplete");
|
||||
f.setAccessible(true);
|
||||
f.set(null, false);
|
||||
try {
|
||||
assertTrue(GLStateManager.acquireDrawLock(), "during splash the lock must engage");
|
||||
GLStateManager.releaseDrawLock();
|
||||
} finally {
|
||||
f.set(null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
|
||||
@Test
|
||||
void testGlColor4fSetsVertexAttrib() {
|
||||
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
|
||||
GLStateManager.flushDeferredVertexAttribs();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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");
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
@ExtendWith(GLSMCoreExtension.class)
|
||||
class ProgramUniformTrackingTest {
|
||||
|
||||
private static Program buildProgram(boolean hasColor) {
|
||||
final VertexKey vk = VertexKey.fromState(hasColor, false, false, false, 0);
|
||||
final FragmentKey fk = FragmentKey.fromState();
|
||||
return Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matrixTrackingIsPerProgram() {
|
||||
final Uniforms uniforms = new Uniforms();
|
||||
final Program a = buildProgram(true);
|
||||
final Program b = buildProgram(false);
|
||||
assertNotEquals(a.getProgramId(), b.getProgramId(), "distinct vertex keys must yield distinct programs");
|
||||
try {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
|
||||
final int genA = GLStateManager.mvGeneration;
|
||||
GL20.glUseProgram(a.getProgramId());
|
||||
uniforms.upload(a);
|
||||
assertEquals(genA, a.uploadState.mvGen, "A records the mv generation it uploaded");
|
||||
|
||||
GLStateManager.glTranslatef(4.0F, 0.0F, 0.0F);
|
||||
final int genB = GLStateManager.mvGeneration;
|
||||
assertNotEquals(genA, genB);
|
||||
GL20.glUseProgram(b.getProgramId());
|
||||
uniforms.upload(b);
|
||||
assertEquals(genB, b.uploadState.mvGen, "B records the new mv generation");
|
||||
assertEquals(genA, a.uploadState.mvGen, "A is unchanged -- tracking is per-program, not global");
|
||||
|
||||
// Return to A with the modelview still at genB: A is stale -> re-uploads and catches up.
|
||||
GL20.glUseProgram(a.getProgramId());
|
||||
uniforms.upload(a);
|
||||
assertEquals(genB, a.uploadState.mvGen, "A re-uploads on return and catches up to genB");
|
||||
|
||||
// Re-upload A with no change: gate (mvGeneration == A.mvGen) is satisfied -> skip.
|
||||
uniforms.upload(a);
|
||||
assertEquals(genB, a.uploadState.mvGen, "A already current -> skipped");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
} finally {
|
||||
GL20.glUseProgram(0);
|
||||
a.destroy();
|
||||
b.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
-6
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
+116
-7
@@ -23,6 +23,9 @@ import org.lwjgl.opengl.GL44C;
|
||||
import org.lwjgl.opengl.GL45C;
|
||||
import org.lwjgl.opengl.GLCapabilities;
|
||||
import org.lwjgl.opengl.GLDebugMessageCallback;
|
||||
import org.lwjgl.sdl.SDL_DropEvent;
|
||||
import org.lwjgl.sdl.SDL_EventFilter;
|
||||
import org.lwjgl.sdl.SDLEvents;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -31,6 +34,10 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* LWJGL3 GL implementation of {@link RenderBackend}.
|
||||
@@ -43,6 +50,10 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
private GLDebugMessageCallback debugCallback;
|
||||
private boolean debugOutputActive;
|
||||
|
||||
private SDL_EventFilter dropEventFilter;
|
||||
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
|
||||
private volatile boolean watchingDrops;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
caps = GL.getCapabilities();
|
||||
@@ -50,7 +61,49 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// no-op
|
||||
stopFileDrop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFileDrop() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startFileDrop() {
|
||||
if (watchingDrops) return;
|
||||
dropEventFilter = SDL_EventFilter.create((userData, eventPtr) -> {
|
||||
if (SDL_DropEvent.ntype(eventPtr) == SDLEvents.SDL_EVENT_DROP_FILE) {
|
||||
final String path = SDL_DropEvent.ndataString(eventPtr);
|
||||
if (path != null && !path.isEmpty()) {
|
||||
droppedFiles.add(path);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
SDLEvents.SDL_AddEventWatch(dropEventFilter, 0L);
|
||||
watchingDrops = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopFileDrop() {
|
||||
if (!watchingDrops) return;
|
||||
SDLEvents.SDL_RemoveEventWatch(dropEventFilter, 0L);
|
||||
dropEventFilter.free();
|
||||
dropEventFilter = null;
|
||||
droppedFiles.clear();
|
||||
watchingDrops = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> pollDroppedFiles() {
|
||||
if (droppedFiles.isEmpty()) return Collections.emptyList();
|
||||
final List<String> out = new ArrayList<>();
|
||||
String p;
|
||||
while ((p = droppedFiles.poll()) != null) {
|
||||
out.add(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,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);
|
||||
@@ -491,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);
|
||||
@@ -601,6 +659,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
GL11C.glGetTexImage(target, level, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
|
||||
GL11C.glGetTexImage(target, level, format, type, pixelBufferOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
|
||||
return GL30C.glGetFramebufferAttachmentParameteri(target, attachment, pname);
|
||||
@@ -808,6 +871,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);
|
||||
@@ -818,6 +886,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);
|
||||
@@ -1013,6 +1101,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
return GL30C.glMapBufferRange(target, offset, length, access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushMappedBufferRange(int target, long offset, long length) {
|
||||
GL30C.glFlushMappedBufferRange(target, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int genVertexArrays() {
|
||||
return GL30C.glGenVertexArrays();
|
||||
@@ -1028,6 +1121,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);
|
||||
@@ -1193,6 +1291,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
return GL45C.glCreateBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void namedBufferData(int buffer, long size, int usage) {
|
||||
GL45C.glNamedBufferData(buffer, size, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void namedBufferData(int buffer, ByteBuffer data, int usage) {
|
||||
GL45C.glNamedBufferData(buffer, data, usage);
|
||||
@@ -1318,11 +1421,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);
|
||||
@@ -1350,6 +1456,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
@@ -16,7 +16,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -327,6 +327,15 @@ public class BatchingFontRenderer {
|
||||
private int fontAAStrengthLast = -1;
|
||||
|
||||
private void flushBatch() {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
flushBatchInner();
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void flushBatchInner() {
|
||||
if (vertexDataPos == 0) {
|
||||
clearBatch();
|
||||
return;
|
||||
|
||||
@@ -2,17 +2,25 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
public final class FontProviderUnicode implements FontProvider, IResourceManagerReloadListener {
|
||||
|
||||
private FontProviderUnicode() {
|
||||
onResourceManagerReload(Minecraft.getMinecraft().getResourceManager());
|
||||
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceManagerReload(IResourceManager resourceManager) {
|
||||
try {
|
||||
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
InputStream inputstream = resourceManager.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputstream.read(this.glyphWidth);
|
||||
}
|
||||
|
||||
@@ -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,30 @@
|
||||
package com.gtnewhorizons.angelica.compat.etfuturum;
|
||||
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import ganymedes01.etfuturum.api.client.EndFlashAPI;
|
||||
|
||||
/**
|
||||
* Use EFR to power End flash uniforms.
|
||||
*/
|
||||
public final class EndFlashCompat {
|
||||
|
||||
private static final boolean AVAILABLE = Loader.isModLoaded("etfuturum") && EndFlashCompat.class.getResource("/ganymedes01/etfuturum/api/client/EndFlashAPI.class") != null;
|
||||
|
||||
private EndFlashCompat() {}
|
||||
|
||||
public static boolean isAvailable() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
public static float getIntensity(float partialTicks) {
|
||||
return AVAILABLE ? EndFlashAPI.getIntensity(partialTicks) : 0.0F;
|
||||
}
|
||||
|
||||
public static float getXAngle() {
|
||||
return AVAILABLE ? EndFlashAPI.getXAngle() : 0.0F;
|
||||
}
|
||||
|
||||
public static float getYAngle() {
|
||||
return AVAILABLE ? EndFlashAPI.getYAngle() : 0.0F;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -310,6 +317,16 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public boolean narrowEars;
|
||||
|
||||
@Config.Comment("Narrow Fisk's Superheroes transformer exclusions to allow GL redirection")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public boolean narrowFiskHeroes;
|
||||
|
||||
@Config.Comment("Narrow FoamFix transformer exclusions to allow GL redirection in its repackaged Ears")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public boolean narrowFoamFix;
|
||||
}
|
||||
|
||||
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
|
||||
@@ -340,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());
|
||||
@@ -186,6 +189,8 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
narrowTransformerConfig("AdvancedLightsabers", AngelicaConfig.transformerCompat.narrowAdvancedLightsabers);
|
||||
narrowTransformerConfig("Alfheim", AngelicaConfig.transformerCompat.narrowAlfheim);
|
||||
narrowTransformerConfig("Ears", AngelicaConfig.transformerCompat.narrowEars);
|
||||
narrowTransformerConfig("FiskHeroes", AngelicaConfig.transformerCompat.narrowFiskHeroes);
|
||||
narrowTransformerConfig("FoamFix", AngelicaConfig.transformerCompat.narrowFoamFix);
|
||||
|
||||
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.IncompatibleModsDisablerTweaker");
|
||||
if (AngelicaConfig.enableHudCaching) {
|
||||
|
||||
-1
@@ -17,7 +17,6 @@ public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
public CeleritasBlockTransformer() {
|
||||
this.inner = new CeleritasBlockTransform(AngelicaClientTweaker.isObfEnv());
|
||||
this.exclusions = inner.getTransformerExclusions();
|
||||
this.inner.setCeleritasSetting();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+30
-34
@@ -31,16 +31,21 @@ public final class CeleritasBlockTransform implements Opcodes {
|
||||
private static final Logger LOGGER = LogManager.getLogger("CeleritasBlockTransformer");
|
||||
private static final String BlockClass = "net/minecraft/block/Block";
|
||||
private static final String ThreadedBlockData = "com/gtnewhorizons/angelica/client/rendering/ThreadedBlockData";
|
||||
/** All classes under <tt>net.minecraft.block.Block*</tt> are Block subclasses save for these. */
|
||||
private static final String[] VanillaBlockExclusions = {
|
||||
"net/minecraft/block/IGrowable",
|
||||
"net/minecraft/block/ITileEntityProvider",
|
||||
"net/minecraft/block/BlockEventData",
|
||||
"net/minecraft/block/BlockSourceImpl",
|
||||
"net/minecraft/block/material/"
|
||||
};
|
||||
|
||||
private final Map<String, String> fieldNameToRedirect = new HashMap<>();
|
||||
private final Set<String> blockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private final Set<String> moddedBlockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
// Block subclass owners we shouldn't redirect because they shadow some fields we want to redirect
|
||||
private final Set<String> blockSubclassExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private final ClassConstantPoolParser cstPoolParser;
|
||||
|
||||
private final MethodHandle celeritasEnabledGetter;
|
||||
private boolean isCeleritasEnabled;
|
||||
|
||||
public CeleritasBlockTransform(boolean isObf) {
|
||||
final List<Pair<String, String>> mappings = ImmutableList.of(
|
||||
Pair.of("minX", "field_149759_B"),
|
||||
@@ -56,37 +61,30 @@ public final class CeleritasBlockTransform implements Opcodes {
|
||||
}
|
||||
|
||||
this.cstPoolParser = new ClassConstantPoolParser(this.fieldNameToRedirect.keySet().toArray(new String[0]));
|
||||
}
|
||||
|
||||
blockSubclasses.add(BlockClass);
|
||||
|
||||
try {
|
||||
// Needed because the config is loaded in LaunchClassLoader, but we need to access it in the parent system loader.
|
||||
final Class<?> angelicaConfig = Class.forName("com.gtnewhorizons.angelica.config.AngelicaConfig", true, Launch.classLoader);
|
||||
celeritasEnabledGetter = MethodHandles.lookup().findStaticGetter(angelicaConfig, "enableCeleritas", boolean.class);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
private boolean isVanillaBlockSubclass(String className) {
|
||||
if (!className.startsWith(BlockClass)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCeleritasSetting() {
|
||||
isCeleritasEnabled = true;
|
||||
}
|
||||
|
||||
private boolean isCeleritasEnabled() {
|
||||
if (isCeleritasEnabled) return true;
|
||||
try {
|
||||
return (boolean) celeritasEnabledGetter.invokeExact();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
for (String exclusion : VanillaBlockExclusions) {
|
||||
if (className.startsWith(exclusion)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This method needs to be called for every class, including the ones we don't want to transform
|
||||
private boolean isBlockSubclass(String className) {
|
||||
return isVanillaBlockSubclass(className) || moddedBlockSubclasses.contains(className);
|
||||
}
|
||||
|
||||
// This method needs to be called for every class, including the ones we don't want to transform.
|
||||
// Vanilla blocks are recognized by name; only modded subclasses need tracking, and FML's
|
||||
// EventSubscriptionTransformer guarantees a modded class's superclass loads first.
|
||||
public void trackBlockSubclasses(String className, String superClassName) {
|
||||
// It works because the deepest subclasses are always transformed first due to
|
||||
// ForgeEventTransformer recursive superclass loading
|
||||
if (blockSubclasses.contains(superClassName)) {
|
||||
blockSubclasses.add(className);
|
||||
if (!isVanillaBlockSubclass(className) && isBlockSubclass(superClassName)) {
|
||||
moddedBlockSubclasses.add(className);
|
||||
|
||||
if (blockSubclassExclusions.contains(superClassName)) {
|
||||
LOGGER.info("Class {} extends a class with shadowed block bounds fields and will be skipped from redirecting", className);
|
||||
@@ -110,9 +108,6 @@ public final class CeleritasBlockTransform implements Opcodes {
|
||||
|
||||
/** @return Was the class changed? */
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn) {
|
||||
if (!isCeleritasEnabled()) {
|
||||
return false;
|
||||
}
|
||||
boolean changed = false;
|
||||
if (BlockClass.equals(cn.name)) {
|
||||
changed = cn.fields.removeIf(field -> fieldNameToRedirect.containsKey(field.name));
|
||||
@@ -124,7 +119,8 @@ public final class CeleritasBlockTransform implements Opcodes {
|
||||
}
|
||||
|
||||
private void trackBlockShadowingFields(ClassNode cn) {
|
||||
if (blockSubclasses.contains(cn.name)) {
|
||||
// Only modded subclasses can shadow; vanilla declares these fields solely in Block.
|
||||
if (moddedBlockSubclasses.contains(cn.name)) {
|
||||
for (FieldNode field : cn.fields) {
|
||||
if (fieldNameToRedirect.containsKey(field.name)) {
|
||||
LOGGER.info("Class '{}' shadows one or more block bounds fields, these accesses won't be redirected!", cn.name);
|
||||
@@ -141,7 +137,7 @@ public final class CeleritasBlockTransform implements Opcodes {
|
||||
for (AbstractInsnNode node = mn.instructions.getFirst(); node != null; node = node.getNext()) {
|
||||
if ((node.getOpcode() == GETFIELD || node.getOpcode() == PUTFIELD) && node instanceof FieldInsnNode fNode) {
|
||||
String newFieldName = fieldNameToRedirect.get(fNode.name);
|
||||
if (newFieldName != null && blockSubclasses.contains(fNode.owner) && !blockSubclassExclusions.contains(fNode.owner)) {
|
||||
if (newFieldName != null && isBlockSubclass(fNode.owner) && !blockSubclassExclusions.contains(fNode.owner)) {
|
||||
if (LOG_SPAM) {
|
||||
LOGGER.info("Redirecting Block.{} in {} to thread-safe wrapper", fNode.name, transformedName);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
),
|
||||
|
||||
@@ -329,6 +356,15 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
IRIS_SHADERS_CHISEL_BEACON(new MixinBuilder("Iris Chisel Beacon Beam")
|
||||
.setPhase(Phase.LATE)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addRequiredMod(TargetedMod.CHISEL)
|
||||
.addClientMixins(
|
||||
"chisel.MixinRenderCarvableBeacon"
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_TEXTURE(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris || AngelicaConfig.enableCeleritas)
|
||||
@@ -381,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)
|
||||
|
||||
@@ -10,12 +10,14 @@ public enum TargetedMod implements ITargetMod {
|
||||
ARCHAICFIX("org.embeddedt.archaicfix.ArchaicCore", "archaicfix"),
|
||||
BACKHAND("xonin.backhand.coremod.BackhandLoadingPlugin", "backhand"),
|
||||
CAMPFIRE_BACKPORT(null, "campfirebackport"),
|
||||
CHISEL(null, "chisel"),
|
||||
COFHCORE( "cofh.asm.LoadingPlugin", "CoFHCore"),
|
||||
DYNAMIC_SURROUNDINGS_MIST("org.blockartistry.mod.DynSurround.mixinplugin.DynamicSurroundingsEarlyMixins", "dsurround"),
|
||||
DYNAMIC_SURROUNDINGS_ORIGINAL("org.blockartistry.mod.DynSurround.asm.TransformLoader", "dsurround"),
|
||||
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"),
|
||||
@@ -27,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();
|
||||
}
|
||||
+4
@@ -1,6 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
|
||||
public interface IChunkTileEntityMapHolder {
|
||||
ConcurrentTileEntityMap angelica$getConcurrentTEMap();
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
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.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.client.rendering.GlUniformFloat2v;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class SelectionBoxRenderer {
|
||||
|
||||
private static GlProgram<SelectionBoxUniforms> program;
|
||||
private static int emptyVao;
|
||||
private static IVertexArrayObject vao;
|
||||
private static final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private static final float[] colorBuf = new float[4];
|
||||
|
||||
private static final int VERT_COUNT = 24;
|
||||
private static final float[] UNIT_EDGES = {
|
||||
0,0,0, 1,0,0, 1,0,0, 1,0,1,
|
||||
1,0,1, 0,0,1, 0,0,1, 0,0,0,
|
||||
0,1,0, 1,1,0, 1,1,0, 1,1,1,
|
||||
1,1,1, 0,1,1, 0,1,1, 0,1,0,
|
||||
0,0,0, 0,1,0, 1,0,0, 1,1,0,
|
||||
1,0,1, 1,1,1, 0,0,1, 0,1,1
|
||||
};
|
||||
|
||||
private SelectionBoxRenderer() {}
|
||||
|
||||
public static void init() {
|
||||
@@ -40,8 +57,6 @@ public final class SelectionBoxRenderer {
|
||||
|
||||
program = builder.link(ctx -> new SelectionBoxUniforms(
|
||||
ctx.bindUniform("u_MVP", GlUniformMatrix4f::new),
|
||||
ctx.bindUniform("u_Min", GlUniformFloat3v::new),
|
||||
ctx.bindUniform("u_Max", GlUniformFloat3v::new),
|
||||
ctx.bindUniform("u_Color", GlUniformFloat4v::new),
|
||||
needsGS ? ctx.bindUniform("u_ViewportSize", GlUniformFloat2v::new) : null,
|
||||
needsGS ? ctx.bindUniform("u_LineWidth", GlUniformFloat::new) : null));
|
||||
@@ -50,20 +65,34 @@ public final class SelectionBoxRenderer {
|
||||
fs.destroy();
|
||||
if (gs != null) gs.destroy();
|
||||
|
||||
emptyVao = GLStateManager.glGenVertexArrays();
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(UNIT_EDGES.length * Float.BYTES);
|
||||
buf.asFloatBuffer().put(UNIT_EDGES);
|
||||
vao = VertexBufferType.IMMUTABLE.allocate(DefaultVertexFormat.POSITION, GL11.GL_LINES, buf, VERT_COUNT);
|
||||
}
|
||||
|
||||
public static void draw(AxisAlignedBB aabb, int color) {
|
||||
if (program == null) return;
|
||||
if (program == null || vao == null) return;
|
||||
|
||||
final float minX = (float) aabb.minX, minY = (float) aabb.minY, minZ = (float) aabb.minZ;
|
||||
final float extX = (float) (aabb.maxX - aabb.minX);
|
||||
final float extY = (float) (aabb.maxY - aabb.minY);
|
||||
final float extZ = (float) (aabb.maxZ - aabb.minZ);
|
||||
|
||||
if (AngelicaConfig.enableIris && IrisApi.getInstance().isShaderPackInUse()) {
|
||||
drawThroughShader(minX, minY, minZ, extX, extY, extZ, color);
|
||||
} else {
|
||||
drawFast(minX, minY, minZ, extX, extY, extZ, color);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawFast(float minX, float minY, float minZ, float extX, float extY, float extZ, int color) {
|
||||
final SelectionBoxUniforms uniforms = program.getInterface();
|
||||
|
||||
program.bind();
|
||||
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), mvpMatrix);
|
||||
mvpMatrix.translate(minX, minY, minZ).scale(extX, extY, extZ);
|
||||
uniforms.mvp.set(mvpMatrix);
|
||||
uniforms.min.set((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ);
|
||||
uniforms.max.set((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
|
||||
|
||||
if (color != -1) {
|
||||
colorBuf[0] = ((color >> 16) & 0xFF) / 255.0f;
|
||||
@@ -84,25 +113,42 @@ public final class SelectionBoxRenderer {
|
||||
uniforms.lineWidth.setFloat(GLStateManager.getLineState().getWidth());
|
||||
}
|
||||
|
||||
GLStateManager.glBindVertexArray(emptyVao);
|
||||
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 24);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
vao.render();
|
||||
|
||||
program.unbind();
|
||||
}
|
||||
|
||||
private static void drawThroughShader(float minX, float minY, float minZ, float extX, float extY, float extZ, int color) {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(minX, minY, minZ);
|
||||
GLStateManager.glScalef(extX, extY, extZ);
|
||||
|
||||
final Color4 prev = GLStateManager.getColor();
|
||||
final boolean overrideColor = color != -1;
|
||||
if (overrideColor) {
|
||||
GLStateManager.glColor4f(((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
vao.render();
|
||||
|
||||
if (overrideColor) {
|
||||
GLStateManager.glColor4f(prev.getRed(), prev.getGreen(), prev.getBlue(), prev.getAlpha());
|
||||
}
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
if (program != null) {
|
||||
program.destroy();
|
||||
program = null;
|
||||
}
|
||||
if (emptyVao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(emptyVao);
|
||||
emptyVao = 0;
|
||||
if (vao != null) {
|
||||
vao.delete();
|
||||
vao = null;
|
||||
}
|
||||
}
|
||||
|
||||
private record SelectionBoxUniforms(GlUniformMatrix4f mvp, GlUniformFloat3v min, GlUniformFloat3v max, GlUniformFloat4v color, GlUniformFloat2v viewportSize, GlUniformFloat lineWidth) {
|
||||
private record SelectionBoxUniforms(GlUniformMatrix4f mvp, GlUniformFloat4v color, GlUniformFloat2v viewportSize, GlUniformFloat lineWidth) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Loaded 100 of 408 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user