Compare commits

..
18 Commits
Author SHA1 Message Date
Jason Mitchell 87aa853f81 Attempt to fix deferred particle batching (#1871) 2026-06-16 22:16:26 -07:00
Jason Mitchell ab69eea654 Announce releases (#1867) 2026-06-15 14:50:47 -07:00
Jason Mitchell edccf63b66 Bump glsl-transformation-lib and buildscript (#1865) 2026-06-14 18:09:05 -07:00
MalTeeez 80413fb3de Add policy for AI contributions & a PR template (#1864) 2026-06-15 01:40:03 +02:00
Jason Mitchell f3ed9aa82b Force invalidate dh depth tex when reconnecting DH Textures (#1862) 2026-06-14 14:59:01 -07:00
Jason Mitchell 307c563b6d Rework DH matrix handling (#1861) 2026-06-14 14:20:19 -07:00
Eclipse-Sol f92ce1d24e Fix enchant glint fix so that it looks proper (#1856) 2026-06-13 17:28:12 -07:00
sisyphussy a7fac16d97 VAO stuff (#1858) 2026-06-13 17:26:09 -07:00
Jason Mitchell 20c3caaed4 A few redirects for calls that DH was using that we weren't redirecting (#1859) 2026-06-13 16:50:44 -07:00
Jason Mitchell bf08cd50d9 Support glPixelStore in display lists (#1854) 2026-06-11 20:29:14 -07:00
Eclipse-Sol 805a266059 Support extended metas for NEID and EID (#1853) 2026-06-11 17:58:22 -07:00
Eclipse-Sol d3c9963280 Fix ctm crash due to null (#1852) 2026-06-11 18:02:08 -04:00
Eclipse-Sol 4630bc4fd8 Fix shaders not loading the correct dim (#1844) 2026-06-09 23:12:13 -07:00
Jason Mitchell ce32a3d3df Emulate glTexParameter(pname=GL_GENERATE_MIPMAP) on core (#1845) 2026-06-09 23:03:42 -07:00
Eclipse-Sol 1991dc58ec Fix Steadfast not loading right (#1838) 2026-06-08 20:06:29 -07:00
Eclipse-Sol ae19236e1a Remove force hand animation (#1839) 2026-06-08 20:05:49 -07:00
Kotmatross 7ad368c937 Be less lazy (#1834) 2026-06-08 17:03:36 -07:00
Eclipse-Sol 5c86c87bcf Allow devs to target the dragon death rays as its own entity id (#1836) 2026-06-08 17:03:05 -07:00
59 changed files with 1604 additions and 782 deletions

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# Description or your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- Add a screenshot or a video demonstration for new features -->
<details open><summary>Template</summary> <!-- OPEN is intentional -->
### Before submitting a *pull request* make sure you have:
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
</details>
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
: "${REPO:?REPO required}"
: "${TAG:?TAG required}"
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
echo "DISCORD_WEBHOOK_URL not set, skipping"
exit 0
fi
if ! release_json=$(gh release view "$TAG" --repo "$REPO" --json body,url,publishedAt 2>&1); then
echo "Could not read release $TAG in $REPO; skipping:"
printf '%s\n' "$release_json"
exit 0
fi
body=$(jq -r '.body // ""' <<<"$release_json")
url=$(jq -r '.url' <<<"$release_json")
ts=$(jq -r '.publishedAt // ""' <<<"$release_json")
[ -n "$ts" ] || ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
payload=$(jq -n \
--arg title "$TAG" \
--arg url "$url" \
--arg desc "$body" \
--arg ts "$ts" \
'{
embeds: [{
title: ("Angelica " + $title + " released"),
url: $url,
description: (if ($desc | length) > 3900
then $desc[0:3900] + "\n\n...[full changelog](" + $url + ")"
else $desc end),
color: 3066993,
footer: { text: "GitHub Releases" },
timestamp: $ts
}]
}')
curl -fsS -X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
+19
View File
@@ -12,3 +12,22 @@ jobs:
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
notify-discord:
needs: [release-tags]
runs-on: ubuntu-24.04
if: >-
${{ !endsWith(github.ref_name, '-snapshot')
&& !contains(github.event.head_commit.message, '[snapshot]')
&& !endsWith(github.ref_name, '-pre') }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Announce release to Discord
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/announce-release.sh
+55
View File
@@ -0,0 +1,55 @@
# AI Usage Policy
This project has strict rules for AI usage:
* **The human-in-the-loop must fully understand all code.** If you
can't explain what your changes do and how they interact with the
greater system without the aid of AI tools, do not contribute
those changes.
* **Issues and discussions can use AI assistance but must have a full
human-in-the-loop.** This means that any content generated with AI
must have been reviewed *and edited* by a human before submission.
The author must do their research and trim this down.
* **No AI-generated media is allowed (textures, videos, audio, etc.).**
Text and code are the only acceptable AI-generated content, per the
other rules in this policy.
* **Commit messages, PR descriptions must read in your own voice.**
No AI-authored or AI co-authored commits, including Co-authored-by AI tags.
* **Only contribute fixes for bugs you have personally hit in-game.**
If you haven’t experienced or reproduced a bug firsthand, and don’t
already know the relevant code well, don’t throw AI at it and forward it to us.
Patches written for problems the author never actually experienced themselves
tend to fix the wrong thing and/or produce another.
* **The voice and intent of written content must be yours.**
For documentation and especially quests, the tone and the point you're trying to make
has to come from you, not an AI. Use AI to help with phrasing or cleanup if you like,
but if the substance and voice aren't yours, don't contribute it.
* **Bad AI use may be blocked.** People who produce bad contributions
that are clearly AI (slop) may be blocked from making future contributions.
We love to help junior developers learn and grow, but if you're interested
in that, don't use AI and we'll help you. We're sorry that bad AI
users have ruined this for you.
## There are Humans Here
Please remember that this project is maintained by humans.
Every discussion, issue, and pull request is read and reviewed by
humans (and sometimes machines, too). It is a boundary point at which
people interact with each other and work is done. It is rude and
disrespectful to approach this boundary with low-effort, unqualified
work, since it puts the burden of validation on the maintainer.
In a perfect world, AI would produce high-quality, accurate work
every time. But today, that reality depends on the user of the AI.
And today, many users of AI do not use it well. So, until that
changes, we need to have strict rules to protect maintainers.
Based on https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
+1 -1
View File
@@ -83,7 +83,7 @@ dependencies {
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")
@@ -59,7 +59,10 @@ public class CompatUniformManager {
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -89,6 +92,8 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
@@ -229,10 +234,15 @@ public class CompatUniformManager {
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
}
}
}
@@ -266,10 +276,15 @@ public class CompatUniformManager {
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
if (locs[LOC_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
}
}
}
@@ -111,6 +111,7 @@ public class DisplayListManager {
int listMode,
CommandRecorder recorder,
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
@@ -131,6 +132,19 @@ public class DisplayListManager {
return currentRecorder != null && Thread.currentThread() == recordingThread;
}
public static UnsupportedOperationException unsupportedInList(String what) {
return new UnsupportedOperationException(what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
}
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 +173,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 +764,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);
@@ -784,9 +800,21 @@ public class DisplayListManager {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
}
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 +831,9 @@ public class DisplayListManager {
final boolean hasCommands = !commandBuffer.isEmpty();
if (hasCommands) {
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
DisplayListVBO compiledBuffers = null;
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
@@ -824,16 +853,23 @@ public class DisplayListManager {
}
}
compiled = new CompiledDisplayList(commandBuffer.finish(), commandBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
currentRecorder = null;
} catch (Exception e) {
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
e.printStackTrace();
commandBuffer.delete();
currentRecorder = null;
if (compiledBuffers != null) {
compiledBuffers.delete();
}
return;
}
} else {
// Free the recorder even if empty
commandBuffer.delete();
currentRecorder = null;
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
compiled = CompiledDisplayList.EMPTY;
}
@@ -846,6 +882,10 @@ public class DisplayListManager {
if (LOG_DISPLAY_LIST_COMPILATION) {
logCompiledDisplayList(glListId, compiled, compilationStackTrace);
}
}
private static void popCompilationContext() {
final boolean isNested = !compilationStack.isEmpty();
// Handle nested compilation - restore parent context
if (isNested) {
@@ -856,6 +896,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;
@@ -869,6 +910,7 @@ public class DisplayListManager {
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
@@ -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) {
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.bytebuf.Pointer;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
@@ -8,6 +9,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
@@ -39,13 +41,11 @@ import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -56,13 +56,13 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.Display;
@@ -97,10 +97,10 @@ import java.util.Set;
import java.util.function.IntSupplier;
import static com.gtnewhorizons.angelica.glsm.Vendor.AMD;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizons.angelica.glsm.Vendor.INTEL;
import static com.gtnewhorizons.angelica.glsm.Vendor.MESA;
import static com.gtnewhorizons.angelica.glsm.Vendor.NVIDIA;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* OpenGL State Manager - Provides cached state tracking and management for Backend Renderer Actions
@@ -189,25 +189,25 @@ public class GLStateManager {
// vertexFlags bits mark attribs the VBO already supplies; FFP supplies the rest via u_Current* uniforms.
// Skip the backend vertexAttrib call in either case.
public static void flushDeferredVertexAttribs(int vertexFlags) {
if (dirtyColorAttrib && (vertexFlags & VertexFlags.COLOR_BIT) == 0) {
public static void flushDeferredVertexAttribs(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
if (!hasColor && dirtyColorAttrib) {
RENDER_BACKEND.vertexAttrib4f(Usage.COLOR.getAttributeLocation(), color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
dirtyColorAttrib = false;
}
if (dirtyLightmapAttrib && (vertexFlags & VertexFlags.BRIGHTNESS_BIT) == 0) {
RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f);
dirtyLightmapAttrib = false;
}
if (dirtyNormalAttrib && (vertexFlags & VertexFlags.NORMAL_BIT) == 0) {
if (!hasNormal && dirtyNormalAttrib) {
final var n = ShaderManager.getCurrentNormal();
RENDER_BACKEND.vertexAttrib3f(Usage.NORMAL.getAttributeLocation(), n.x, n.y, n.z);
dirtyNormalAttrib = false;
}
if (dirtyTexCoordAttrib && (vertexFlags & VertexFlags.TEXTURE_BIT) == 0) {
if (!hasTexCoord && dirtyTexCoordAttrib) {
final var tc = ShaderManager.getCurrentTexCoord();
RENDER_BACKEND.vertexAttrib4f(Usage.PRIMARY_UV.getAttributeLocation(), tc.x, tc.y, tc.z, tc.w);
dirtyTexCoordAttrib = false;
}
if (!hasLightmap && dirtyLightmapAttrib) {
RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f);
dirtyLightmapAttrib = false;
}
}
// Highest texture unit index that has ever had a non-zero binding; limits onDeleteTexture scan range
@@ -456,18 +456,16 @@ public class GLStateManager {
@Getter protected static int listBase = 0;
@Getter protected static int boundVBO;
@Getter protected static int boundEBO;
@Getter protected static int boundVAO;
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VertexAttribState.MAX_ATTRIBS];
public static int getBoundEBO() {
return VAOManager.boundEBO;
}
private static int boundPixelUnpackBuffer;
private static int boundPixelPackBuffer;
private static final Int2IntOpenHashMap vaoEboMap = new Int2IntOpenHashMap();
private static PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
static {
vaoEboMap.defaultReturnValue(0);
}
@Getter private static int defaultVAO; // Non-zero on core profile
public static void reset() {
@@ -489,8 +487,6 @@ public class GLStateManager {
modelViewMatrix.clear();
projectionMatrix.clear();
vaoEboMap.clear();
VertexAttribState.reset();
}
/**
@@ -626,18 +622,22 @@ public class GLStateManager {
LOGGER.info("GLStateManager cache bypassed");
}
if (initConfig != null && initConfig.isLwjglDebug()) {
LOGGER.info("Enabling additional LWJGL debug output");
if (RENDER_BACKEND.supportsDebugOutput()) {
LOGGER.info("Enabling additional LWJGL debug output");
GLDebug.setupDebugMessageCallback();
GLDebug.initDebugState();
GLDebug.setupDebugMessageCallback();
GLDebug.initDebugState();
GLDebug.debugMessage("Angelica Debug Annotator Initialized");
GLDebug.debugMessage("Angelica Debug Annotator Initialized");
} else {
LOGGER.info("LWJGL debug output requested but unavailable in this GL context (requires GL 4.3 or GL_KHR_debug); skipping");
}
}
defaultVAO = RENDER_BACKEND.genVertexArrays();
RENDER_BACKEND.bindVertexArray(defaultVAO);
boundVAO = defaultVAO;
VertexAttribState.init(defaultVAO);
VAOManager.init(defaultVAO);
if (initCallback != null) {
initCallback.run();
}
@@ -1015,7 +1015,8 @@ public class GLStateManager {
case GL11.GL_LIST_MODE -> DisplayListManager.getListMode();
case GL11.GL_MATRIX_MODE -> matrixMode.getMode();
case GL11.GL_SHADE_MODEL -> shadeModelState.getValue();
case GL11.GL_TEXTURE_BINDING_2D -> getBoundTextureForServerState();
// GL_TEXTURE_2D makes no sense here, but some mod still queries it...
case GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BINDING_2D -> getBoundTextureForServerState();
case GL11.GL_COLOR_MATERIAL_FACE -> colorMaterialFace.getValue();
case GL11.GL_COLOR_MATERIAL_PARAMETER -> colorMaterialParameter.getValue();
case GL11.GL_MODELVIEW_STACK_DEPTH -> getMatrixStackDepth(modelViewMatrix);
@@ -1043,7 +1044,7 @@ public class GLStateManager {
case GL11.GL_STENCIL_CLEAR_VALUE -> stencilState.getClearValue();
case GL15.GL_ARRAY_BUFFER_BINDING -> boundVBO;
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> boundEBO;
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> VAOManager.boundEBO;
case GL21.GL_PIXEL_UNPACK_BUFFER_BINDING -> boundPixelUnpackBuffer;
case GL21.GL_PIXEL_PACK_BUFFER_BINDING -> boundPixelPackBuffer;
@@ -1192,6 +1193,22 @@ public class GLStateManager {
};
}
private static final FloatBuffer glGetFloatScratch = BufferUtils.createFloatBuffer(16);
public static void glGetFloat(int pname, float[] params) {
final FloatBuffer buf;
if (params.length <= glGetFloatScratch.capacity()) {
buf = glGetFloatScratch;
buf.clear();
buf.limit(params.length);
} else {
buf = BufferUtils.createFloatBuffer(params.length);
}
glGetFloat(pname, buf);
buf.position(0);
buf.get(params);
}
// GLStateManager Functions
public static void glBlendColor(float red, float green, float blue, float alpha) {
@@ -1316,7 +1333,7 @@ public class GLStateManager {
public static void glBlendEquation(int mode) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glBlendEquation in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glBlendEquation");
}
final boolean caching = isCachingEnabled();
final boolean bypass = BYPASS_CACHE || !caching;
@@ -1331,7 +1348,7 @@ public class GLStateManager {
public static void glBlendEquationSeparate(int modeRGB, int modeAlpha) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glBlendEquationSeparate in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glBlendEquationSeparate");
}
final boolean caching = isCachingEnabled();
final boolean bypass = BYPASS_CACHE || !caching;
@@ -1851,7 +1868,7 @@ public class GLStateManager {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromIntBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromIntBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1860,13 +1877,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromFloatBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromFloatBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1875,13 +1893,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromDoubleBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromDoubleBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1890,13 +1909,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1905,15 +1925,17 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage2D with buffer offset in display lists not yet supported");
throw DisplayListManager.unsupportedInList("glTexImage2D with buffer offset");
}
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
maybeGenerateMipmap(target, level);
}
public static void glTexCoord1f(float s) {
@@ -2195,72 +2217,79 @@ public class GLStateManager {
}
}
public static void preDraw(int drawMode) {
prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
}
public static void preDraw() {
disableWideLineEmulation();
ShaderManager.getInstance().preDraw();
prepareClientArrays();
}
public static void glDrawElements(int mode, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, IntBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
RENDER_BACKEND.drawElements(mode, indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, ShortBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
RENDER_BACKEND.drawElements(mode, indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices);
}
public static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, count, type, indices);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
} else {
RENDER_BACKEND.drawElements(mode, count, type, indices);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, count, type, indices);
}
public static void glDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
@@ -2269,7 +2298,7 @@ public class GLStateManager {
if (recordMode != RecordMode.NONE) {
// Core profile: a default VAO is generated at init and glBindVertexArray(0)
// is redirected to it, so a VAO is always bound here — no fallback branches.
final IndexedDrawCapture capture = IndexedDrawCapture.create(mode, indices_count, type, indices_buffer_offset, boundEBO);
final IndexedDrawCapture capture = IndexedDrawCapture.create(mode, indices_count, type, indices_buffer_offset, VAOManager.boundEBO);
if (capture != null) {
DisplayListManager.recordIndexedDrawCapture(capture);
}
@@ -2281,14 +2310,13 @@ public class GLStateManager {
FeedbackManager.processDrawElements(mode, indices_count, type, indices_buffer_offset);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
} else {
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
@@ -2317,62 +2345,21 @@ public class GLStateManager {
}
public static void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
preDraw(mode);
RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex);
}
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
preDraw(mode);
RENDER_BACKEND.drawElementsInstanced(mode, count, type, indices, primcount);
}
private static void prepareClientArrays() {
if (ShaderManager.getInstance().isEnabled() && VertexAttribState.hasAnyClientSideEnabledAttrib()) {
uploadClientArraysToVBO();
if (VAOManager.hasAnyClientSideEnabledAttrib()) {
VAOManager.uploadClientArraysToVBO();
}
}
/**
* 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 void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!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 = boundVBO;
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 < VertexAttribState.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VertexAttribState.Attrib a = VertexAttribState.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]);
}
glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static void glDrawArrays(int mode, int first, int count) {
CommandRecorder savedRecorder = null;
final RecordMode recordMode = DisplayListManager.getRecordMode();
@@ -2389,16 +2376,16 @@ public class GLStateManager {
FeedbackManager.processDrawArrays(mode, first, count);
return;
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
preDraw(mode);
RENDER_BACKEND.drawArrays(mode, first, count);
}
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
@@ -2545,14 +2532,14 @@ public class GLStateManager {
final int location = clientStateToAttributeLocation(cap);
if (location >= 0) glEnableVertexAttribArray(location);
final int flag = clientStateToVertexFlag(cap);
if (flag != 0) ShaderManager.getInstance().enableClientVertexFlag(flag);
if (flag != 0) VAOManager.enableClientVertexFlag(flag);
}
public static void glDisableClientState(int cap) {
final int location = clientStateToAttributeLocation(cap);
if (location >= 0) glDisableVertexAttribArray(location);
final int flag = clientStateToVertexFlag(cap);
if (flag != 0) ShaderManager.getInstance().disableClientVertexFlag(flag);
if (flag != 0) VAOManager.disableClientVertexFlag(flag);
}
public static void glVertexAttrib2f(int index, float v0, float v1) {
@@ -2572,7 +2559,7 @@ public class GLStateManager {
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long offset) {
VertexAttribState.set(index, size, type, normalized, stride, offset, boundVBO);
VAOManager.setAttribute(index, size, type, normalized, stride, offset, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, offset);
}
@@ -2581,40 +2568,40 @@ public class GLStateManager {
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, FloatBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, DoubleBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, IntBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ShortBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, pointer, 0);
VAOManager.setAttribute(index, size, type, normalized, stride, pointer);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, long offset) {
VertexAttribState.set(index, size, type, false, stride, offset, boundVBO);
VAOManager.setAttribute(index, size, type, false, stride, offset, boundVBO);
RENDER_BACKEND.vertexAttribIPointer(index, size, type, stride, offset);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, false, stride, pointer, 0);
VAOManager.setAttribute(index, size, type, false, stride, pointer);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, IntBuffer pointer) {
VertexAttribState.set(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ShortBuffer pointer) {
VertexAttribState.set(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer), 0);
VAOManager.setAttribute(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexAttribDivisor(int index, int divisor) { RENDER_BACKEND.vertexAttribDivisor(index, divisor); }
@@ -2625,12 +2612,12 @@ public class GLStateManager {
public static void glVertexAttribBinding(int attribindex, int bindingindex) { RENDER_BACKEND.vertexAttribBinding(attribindex, bindingindex); }
public static void glEnableVertexAttribArray(int index) {
VertexAttribState.setEnabled(index, true);
VAOManager.enableAttribute(index);
RENDER_BACKEND.enableVertexAttribArray(index);
}
public static void glDisableVertexAttribArray(int index) {
VertexAttribState.setEnabled(index, false);
VAOManager.disableAttribute(index);
RENDER_BACKEND.disableVertexAttribArray(index);
}
@@ -2665,7 +2652,7 @@ public class GLStateManager {
public static void glPushClientAttrib(int mask) {
if (clientAttribStackPointer < CLIENT_ATTRIB_STACK_DEPTH) {
clientAttribSavedTextureUnit[clientAttribStackPointer] = clientActiveTextureUnit;
clientAttribSavedVertexFlags[clientAttribStackPointer] = ShaderManager.getInstance().getCurrentVertexFlags();
clientAttribSavedVertexFlags[clientAttribStackPointer] = VAOManager.getCurrentVertexFlags();
clientAttribStackPointer++;
}
}
@@ -2674,7 +2661,7 @@ public class GLStateManager {
if (clientAttribStackPointer > 0) {
clientAttribStackPointer--;
clientActiveTextureUnit = clientAttribSavedTextureUnit[clientAttribStackPointer];
ShaderManager.getInstance().setCurrentVertexFlags(clientAttribSavedVertexFlags[clientAttribStackPointer]);
VAOManager.setCurrentVertexFlags(clientAttribSavedVertexFlags[clientAttribStackPointer]);
}
}
@@ -3451,6 +3438,21 @@ public class GLStateManager {
}
}
public static void setModelViewMatrix(Matrix4fc m) {
if (isCachingEnabled()) {
modelViewMatrix.set(m);
mvGeneration++;
mvLinearGeneration++;
}
}
public static void setProjectionMatrix(Matrix4fc m) {
if (isCachingEnabled()) {
projectionMatrix.set(m);
projGeneration++;
}
}
public static Matrix4fStack getMatrixStack() {
switch (matrixMode.getMode()) {
case GL11.GL_MODELVIEW -> {
@@ -3734,6 +3736,25 @@ public class GLStateManager {
return DisplayListManager.getListMode();
}
private static boolean handleRemovedTexParam(int target, int pname, int value) {
if (pname == GL14.GL_GENERATE_MIPMAP) {
if (target == GL11.GL_TEXTURE_2D) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(getBoundTextureForServerState());
if (info != null) info.setGenerateMipmap(value != 0);
}
return true;
}
return false;
}
private static void maybeGenerateMipmap(int target, int level) {
if (target != GL11.GL_TEXTURE_2D) return;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(getBoundTextureForServerState());
if (info != null && info.isGenerateMipmap() && level == info.getBaseLevel() && level < info.getMaxLevel()) {
RENDER_BACKEND.generateMipmap(target);
}
}
public static boolean updateTexParameteriCache(int target, int texture, int pname, int param) {
if (target != GL11.GL_TEXTURE_2D) {
return true;
@@ -3759,6 +3780,10 @@ public class GLStateManager {
if (info.getWrapT() == param && !shouldBypassCache()) return false;
info.setWrapT(param);
}
case GL12.GL_TEXTURE_BASE_LEVEL -> {
if (info.getBaseLevel() == param && !shouldBypassCache()) return false;
info.setBaseLevel(param);
}
case GL12.GL_TEXTURE_MAX_LEVEL -> {
if (info.getMaxLevel() == param && !shouldBypassCache()) return false;
info.setMaxLevel(param);
@@ -3776,6 +3801,7 @@ public class GLStateManager {
}
public static void glTexParameter(int target, int pname, IntBuffer params) {
if (handleRemovedTexParam(target, pname, params.remaining() >= 1 ? params.get(params.position()) : 0)) return;
if (params.remaining() >= 1) {
final int val = params.get(params.position());
final int remapped = remapTexClamp(pname, val);
@@ -3786,6 +3812,7 @@ public class GLStateManager {
}
public static void glTexParameter(int target, int pname, FloatBuffer params) {
if (handleRemovedTexParam(target, pname, params.remaining() >= 1 ? (int) params.get(params.position()) : 0)) return;
if (params.remaining() >= 1) {
final float val = params.get(params.position());
final float remapped = remapTexClamp(pname, val);
@@ -3796,6 +3823,7 @@ public class GLStateManager {
}
public static void glTexParameteri(int target, int pname, int param) {
if (handleRemovedTexParam(target, pname, param)) return;
param = remapTexClamp(pname, param);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3835,6 +3863,7 @@ public class GLStateManager {
}
public static void glTexParameterf(int target, int pname, float param) {
if (handleRemovedTexParam(target, pname, param > 0 ? (int) (param + 0.5f) : (int) (param - 0.5f))) return;
param = remapTexClamp(pname, param);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3867,9 +3896,11 @@ public class GLStateManager {
case GL11.GL_TEXTURE_MAG_FILTER -> info.getMagFilter();
case GL11.GL_TEXTURE_WRAP_S -> info.getWrapS();
case GL11.GL_TEXTURE_WRAP_T -> info.getWrapT();
case GL12.GL_TEXTURE_BASE_LEVEL -> info.getBaseLevel();
case GL12.GL_TEXTURE_MAX_LEVEL -> info.getMaxLevel();
case GL12.GL_TEXTURE_MIN_LOD -> info.getMinLod();
case GL12.GL_TEXTURE_MAX_LOD -> info.getMaxLod();
case GL14.GL_GENERATE_MIPMAP -> info.isGenerateMipmap() ? GL11.GL_TRUE : GL11.GL_FALSE;
default -> {
if (isRecordingDisplayList()) {
throw new IllegalStateException(String.format(
@@ -4405,7 +4436,7 @@ public class GLStateManager {
public static void glTexImage1D(int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage1D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage1D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
@@ -4415,7 +4446,7 @@ public class GLStateManager {
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
@@ -4425,7 +4456,7 @@ public class GLStateManager {
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
@@ -4434,7 +4465,7 @@ public class GLStateManager {
public static void glTexSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage1D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexSubImage1D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage1D(target, level, xoffset, width, format, type, pixels);
@@ -4459,9 +4490,14 @@ public class GLStateManager {
}
public static void prepareWideLineEmulation(int drawMode) {
wideLineEmulationActive = wideLineEmulationEnabled
&& (drawMode == GL11.GL_LINES || drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP)
&& lineState.getWidth() > 1.0f;
wideLineEmulationActive =
(drawMode == GL11.GL_LINES || drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP)
&& lineState.getWidth() > 1.0f
&& wideLineEmulationEnabled;
}
public static void disableWideLineEmulation() {
wideLineEmulationActive = false;
}
public static void glFlush() {
@@ -4514,6 +4550,25 @@ public class GLStateManager {
glShaderSource(shader, sb.toString());
}
public static void nglShaderSource(int shader, int count, long strings, long lengths) {
if (count <= 0) return;
final CharSequence source;
if (count == 1) {
source = decodeShaderSourceString(strings, lengths, 0);
} else {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) sb.append(decodeShaderSourceString(strings, lengths, i));
source = sb.toString();
}
glShaderSource(shader, source);
}
private static String decodeShaderSourceString(long strings, long lengths, int i) {
final long strAddr = MemoryUtilities.memGetAddress(strings + (long) i * Pointer.POINTER_SIZE);
final int len = lengths == MemoryUtilities.NULL ? -1 : MemoryUtilities.memGetInt(lengths + (long) i * 4);
return len < 0 ? MemoryUtilities.memUTF8(strAddr) : MemoryUtilities.memUTF8(strAddr, len);
}
/** Check shader type to determine if fragment output transformation is needed. */
private static boolean isFragmentShader(int shader) {
return RENDER_BACKEND.getShaderi(shader, GL20.GL_SHADER_TYPE) == GL20.GL_FRAGMENT_SHADER;
@@ -4523,7 +4578,7 @@ public class GLStateManager {
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromByteBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromByteBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -4531,12 +4586,13 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromIntBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromIntBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -4544,18 +4600,20 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage2D with buffer offset in display lists not yet supported");
throw DisplayListManager.unsupportedInList("glTexSubImage2D with buffer offset");
}
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage3D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glTexSubImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
@@ -4564,35 +4622,37 @@ public class GLStateManager {
public static void glCopyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexImage1D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexImage1D");
}
RENDER_BACKEND.copyTexImage1D(target, level, internalFormat, x, y, width, border);
}
public static void glCopyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexImage2D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexImage2D");
}
RENDER_BACKEND.copyTexImage2D(target, level, internalFormat, x, y, width, height, border);
maybeGenerateMipmap(target, level);
}
public static void glCopyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage1D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage1D");
}
RENDER_BACKEND.copyTexSubImage1D(target, level, xoffset, x, y, width);
}
public static void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage2D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage2D");
}
RENDER_BACKEND.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
maybeGenerateMipmap(target, level);
}
public static void glCopyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage3D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage3D");
}
RENDER_BACKEND.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height);
}
@@ -4633,6 +4693,12 @@ public class GLStateManager {
}
public static void glHint(int target, int hint) {
// These hint targets are removed in core profile, ignore them
switch (target) {
case GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_POINT_SMOOTH_HINT, GL11.GL_FOG_HINT, GL14.GL_GENERATE_MIPMAP_HINT -> {
return;
}
}
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordHint(target, hint);
@@ -4721,7 +4787,7 @@ public class GLStateManager {
public static void glReadBuffer(int mode) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glReadBuffer in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glReadBuffer");
}
RENDER_BACKEND.readBuffer(mode);
}
@@ -4790,18 +4856,16 @@ public class GLStateManager {
}
}
// glPixelStore* isn'tcompiled into display lists, it executes immediately
public static void glPixelStorei(int pname, int param) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glPixelStorei in display lists not yet implemented - if you see this, please report!");
if (isCachingEnabled()) {
pixelUnpackState = pixelUnpackState.with(pname, param);
}
RENDER_BACKEND.pixelStorei(pname, param);
}
public static void glPixelStoref(int pname, float param) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glPixelStoref in display lists not yet implemented - if you see this, please report!");
}
RENDER_BACKEND.pixelStoref(pname, param);
glPixelStorei(pname, param < 0 ? -Math.round(-param) : Math.round(param));
}
// Display List Commands
@@ -4896,7 +4960,7 @@ public class GLStateManager {
// Multisample Commands
public static void glSampleCoverage(float value, boolean invert) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glSampleCoverage in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glSampleCoverage");
}
RENDER_BACKEND.sampleCoverage(value, invert);
}
@@ -5021,13 +5085,13 @@ public class GLStateManager {
private static void invalidateDeletedBuffer(int buffer) {
if (buffer == 0) return;
if (boundVBO == buffer) boundVBO = 0;
if (boundEBO == buffer) boundEBO = 0;
if (VAOManager.boundEBO == buffer) VAOManager.boundEBO = 0;
if (boundPixelUnpackBuffer == buffer) boundPixelUnpackBuffer = 0;
if (boundPixelPackBuffer == buffer) boundPixelPackBuffer = 0;
// Sweep all VAOs: an unbound VAO may later rebind with a dangling EBO id.
for (Int2IntMap.Entry e : vaoEboMap.int2IntEntrySet()) {
if (e.getIntValue() == buffer) e.setValue(0);
for (VAOManager.VAOData data : VAOManager.vaoMap.values()) {
if (data.ebo == buffer) data.ebo = 0;
}
}
@@ -5036,8 +5100,7 @@ public class GLStateManager {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
boundVBO = buffer;
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
boundEBO = buffer;
vaoEboMap.put(boundVAO, buffer);
VAOManager.onBindEBO(buffer);
} else if (target == GL21.GL_PIXEL_UNPACK_BUFFER) {
boundPixelUnpackBuffer = buffer;
} else if (target == GL21.GL_PIXEL_PACK_BUFFER) {
@@ -5046,6 +5109,14 @@ public class GLStateManager {
RENDER_BACKEND.bindBuffer(target, buffer);
}
public static void forcePixelUnpackState(PixelUnpackState target) {
PixelUnpackState.applyDiff(pixelUnpackState, target);
}
public static void restorePixelUnpackState(PixelUnpackState applied) {
PixelUnpackState.applyDiff(applied, pixelUnpackState);
}
static void suspendPixelUnpackBuffer() {
if (boundPixelUnpackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
@@ -5113,24 +5184,17 @@ public class GLStateManager {
}
if (boundVAO != array) {
boundVAO = array;
boundEBO = vaoEboMap.get(array);
VertexAttribState.onBindVertexArray(array);
VAOManager.onBindVertexArrayPre(array);
RENDER_BACKEND.bindVertexArray(array);
if (ShaderManager.getInstance().isEnabled()) {
ShaderManager.getInstance().onBindVertexArray(array);
}
}
}
public static void glDeleteVertexArrays(int array) {
ShaderManager.getInstance().onDeleteVertexArray(array);
VertexAttribState.onDeleteVertexArray(array);
vaoEboMap.remove(array);
VAOManager.onDeleteVertexArray(array);
if (array == boundVAO) {
// Deleting the bound VAO implicitly unbinds it. Rebind the default VAO.
boundVAO = defaultVAO;
boundEBO = vaoEboMap.get(defaultVAO);
VertexAttribState.onBindVertexArray(defaultVAO);
VAOManager.onBindVertexArrayPre(defaultVAO);
RENDER_BACKEND.bindVertexArray(defaultVAO);
}
RENDER_BACKEND.deleteVertexArrays(array);
@@ -5140,6 +5204,10 @@ public class GLStateManager {
return RENDER_BACKEND.genVertexArrays();
}
public static boolean glIsVertexArray(int array) {
return RENDER_BACKEND.isVertexArray(array);
}
public static void glBindVertexArrayAPPLE(int array) {
glBindVertexArray(array);
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -9,7 +9,6 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
@@ -17,6 +16,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
@@ -83,6 +83,7 @@ public final class QuadConverter {
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
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;
@@ -106,6 +107,7 @@ public final class QuadConverter {
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
@@ -144,7 +146,7 @@ public final class QuadConverter {
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);
@@ -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;
@@ -507,11 +507,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);
@@ -1043,6 +1038,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);
@@ -125,7 +125,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) {}
@@ -247,6 +246,7 @@ public abstract class RenderBackend {
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract boolean isVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
@@ -1,15 +1,13 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
@@ -31,7 +29,6 @@ public final class ShaderManager {
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
@Getter
private boolean active = false;
@@ -57,10 +54,8 @@ public final class ShaderManager {
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
@@ -73,12 +68,6 @@ public final class ShaderManager {
public void enable() {
enabled = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
currentVertexFlags = format.getVertexFlags();
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
return false;
});
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -99,7 +88,7 @@ public final class ShaderManager {
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
public void preDraw() {
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
@@ -111,7 +100,12 @@ public final class ShaderManager {
}
}
GLStateManager.flushDeferredVertexAttribs(currentVertexFlags);
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
@@ -127,20 +121,6 @@ public final class ShaderManager {
uploadUniforms();
}
public void preDraw(int vertexFlags) {
currentVertexFlags = vertexFlags;
preDraw(
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
);
}
public void preDraw() {
preDraw(currentVertexFlags);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
@@ -177,22 +157,6 @@ public final class ShaderManager {
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
public int getCurrentVertexFlags() {return currentVertexFlags;}
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
public void onBindVertexArray(int vaoId) {
final int flags = vaoVertexFlags.get(vaoId);
if (flags != -1) {
currentVertexFlags = flags;
}
}
public void onDeleteVertexArray(int vaoId) {
vaoVertexFlags.remove(vaoId);
}
public void destroy() {
cache.destroy();
uniforms.destroy();
@@ -0,0 +1,312 @@
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;
}
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;
recomputeClientSideCount();
}
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 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;
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;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
}
}
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];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (isClientSideAttrib(i)) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* 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 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;
};
}
}
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
@@ -15,11 +15,11 @@ public final class AttribSnapshot {
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
return stride != 0 ? stride : size * VAOManager.Attrib.glTypeSizeBytes(type);
}
public int typeSizeBytes() {
return VertexAttribState.Attrib.glTypeSizeBytes(type);
return VAOManager.Attrib.glTypeSizeBytes(type);
}
}
@@ -56,7 +56,7 @@ public final class AttribSnapshot {
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
@@ -67,11 +67,11 @@ public final class AttribSnapshot {
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.vboId == 0) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
final long typeBytes = (long) a.size * VAOManager.Attrib.glTypeSizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
@@ -103,9 +103,9 @@ public final class AttribSnapshot {
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
@@ -653,12 +653,19 @@ public final class CommandRecorder {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
memFree(buffer);
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
@@ -666,7 +673,10 @@ public final class CommandRecorder {
}
final int size = size();
buffer.limit(size);
return memRealloc(buffer, size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public DisplayListCommand[] getComplexObjects() {
@@ -5,7 +5,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -293,7 +293,39 @@ public final class ImmediateModeRecorder {
}
private static float readComponent(AttribReader r, int base, int component) {
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
final int typeSize = VAOManager.Attrib.glTypeSizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
@@ -3,14 +3,14 @@ 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 java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -68,7 +68,7 @@ public final class IndexedDrawCapture {
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();
@@ -106,7 +106,7 @@ 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) {
@@ -118,7 +118,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;
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
final class PixelDataSnapshot {
private PixelDataSnapshot() {}
static @Nullable ByteBuffer copy(@Nullable ByteBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining()).order(ByteOrder.nativeOrder());
out.put(pixels);
out.flip();
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable IntBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asIntBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable FloatBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asFloatBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable DoubleBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 8).order(ByteOrder.nativeOrder());
out.asDoubleBuffer().put(pixels);
pixels.position(pos);
return out;
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexImage2DCmd(
int border,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexSubImage2DCmd(
int height,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexSubImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -126,6 +126,7 @@ public class GLSMRedirector {
.add("glFrustum")
.add("glGetBoolean")
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetLight")
.add("glGetMaterial")
@@ -296,6 +297,7 @@ public class GLSMRedirector {
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("nglShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
@@ -321,6 +323,7 @@ public class GLSMRedirector {
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
@@ -342,6 +345,7 @@ public class GLSMRedirector {
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -587,18 +591,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 +619,7 @@ public class GLSMRedirector {
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null;
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,198 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
private static int clientSideEnabledCount;
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
clientSideEnabledCount = 0;
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
clientSideEnabledCount = 0;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (was) clientSideEnabledCount--;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
final boolean now = a.isClientSide();
if (was != now) clientSideEnabledCount += now ? 1 : -1;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].isClientSide()) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -7,7 +7,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -204,14 +203,8 @@ public class TessellatorStreamingDrawer {
firstVertex = 0;
}
GLStateManager.prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw(flags);
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_NORMAL_ARRAY);
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
@@ -17,6 +17,7 @@ public class TextureInfo {
@Getter @Setter protected int magFilter = GL11.GL_LINEAR;
@Getter @Setter protected int wrapS = GL11.GL_REPEAT;
@Getter @Setter protected int wrapT = GL11.GL_REPEAT;
@Getter @Setter protected int baseLevel = 0;
@Getter @Setter protected int maxLevel = 1000;
@Getter @Setter protected int minLod = -1000;
@Getter @Setter protected int maxLod = 1000;
@@ -24,6 +25,8 @@ public class TextureInfo {
@Getter @Setter protected float maxAnisotropy = 1.0f;
@Getter @Setter protected boolean generateMipmap = false;
protected TextureInfo(int id) {
this.id = id;
}
@@ -0,0 +1,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;
@@ -133,7 +133,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);
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(0);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, org.lwjgl.opengl.GL11.GL_COMPILE);
@@ -3,10 +3,10 @@ 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.ValueSource;
@@ -307,10 +307,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);
@@ -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));
}
}
@@ -544,11 +544,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);
@@ -1081,6 +1076,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30C.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20C.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -1371,11 +1371,14 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public boolean supportsDebugOutput() {
return caps != null && caps.OpenGL43;
return caps != null && (caps.OpenGL43 || caps.GL_KHR_debug);
}
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
debugCallback = GLDebugMessageCallback.create(
(source, type, id, severity, length, message, userParam) -> {
final String msg = GLDebugMessageCallback.getMessage(length, message);
@@ -1403,6 +1406,9 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int disableDebugOutput() {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
GL43C.glDebugMessageCallback(null, 0L);
if (debugCallback != null) {
debugCallback.free();
+1 -1
View File
@@ -16,7 +16,7 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.25")
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.26")
}
include("glsm")
@@ -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,5 +1,6 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
@@ -14,6 +15,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;
@@ -35,9 +38,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");
@@ -65,5 +68,15 @@ public class ModStatus {
isNEIDMetadataExtended = true;
}
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
eidBlockItemExtended = GeneralConfig.extendBlockItem;
eidBiomeExtended = GeneralConfig.extendBiome;
}
isEIDBiomeLoaded = eidBiomeExtended || Loader.isModLoaded("endlessids_biome");
isMetadataExtended = isNEIDMetadataExtended || eidBlockItemExtended;
}
}
@@ -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",
@@ -117,7 +117,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")
@@ -14,6 +14,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.light.VanillaDiffuseProvid
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import lombok.Getter;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
@@ -184,6 +185,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
animatedSprites.add(sprite);
}
final boolean isFluid = blockRenderContext.renderType == ExtendedDataHelper.FLUID_RENDER_TYPE;
// If block used vanilla AO, then we can safely compute our own lighting data and ignore what vanilla
// computed. We suppress vanilla emitting the brightness/color in MixinRenderBlocks.
// Otherwise, we need to use the block's lightmap values as they are as the ISBRH may have
@@ -213,6 +216,17 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
for (int vIdx = 0; vIdx < 4; vIdx++) {
vertices[vIdx].trueNormal = trueNormal;
}
if (useAoCalculation && isFluid) {
quadView.setup(trueNormal, blockX, blockY, blockZ);
final ModelQuadFacing lightFace = quadView.getLightFace();
final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline;
final ModelQuadFacing cullFace = quadView.getCullFace();
pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true);
for (int vIdx = 0; vIdx < 4; vIdx++) {
vertices[vIdx].light = quadLightData.lm[vIdx];
}
}
}
// Apply dynamic lights after AO calculation so they're not overwritten
@@ -7,6 +7,7 @@ import static com.gtnewhorizons.angelica.rendering.celeritas.iris.IrisExtendedCh
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Setter;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.coderbot.iris.vertices.NormalHelper;
@@ -55,7 +56,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
public void prepareToRenderBlock(BlockRenderContext ctx, Block block, int metadata, short renderType, byte lightValue) {
this.context = ctx;
Int2IntMap metaMap = blockMetaMatches != null ? blockMetaMatches.get(block) : null;
ctx.blockId = (short) (metaMap != null ? metaMap.get(metadata) : -1);
ctx.blockId = (short) (metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1);
ctx.renderType = renderType;
ctx.lightValue = lightValue;
}
@@ -84,7 +85,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
private int lookupFluidMeta(Block block, int metadata) {
final Int2IntMap metaMap = blockMetaMatches.get(block);
return metaMap != null ? metaMap.get(metadata) : -1;
return metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1;
}
private static Block liquidCounterpart(Block block) {
+2 -1
View File
@@ -13,6 +13,7 @@ import cpw.mods.fml.common.gameevent.InputEvent;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Getter;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.celeritas.IrisCeleritasShaderProvider;
import net.coderbot.iris.compat.dh.DHCompat;
@@ -948,7 +949,7 @@ public class Iris {
return;
final Int2IntMap metaMap = blockMetaMatches.get(block);
final int blockId = metaMap != null ? metaMap.get(meta) : -1;
final int blockId = metaMap != null ? BlockMaterialMapping.resolveId(metaMap, meta) : -1;
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) blockId);
@@ -1,5 +1,6 @@
package net.coderbot.iris.block_rendering;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -27,10 +28,12 @@ public class BlockMaterialMapping {
public record BlockIdMaps(Reference2ObjectMap<Block, Int2IntMap> blockMetaMap, NbtConditionalIdMap<Block> tileEntityMap) {}
// Meta-key bits OR'd in at runtime
public static final int SNOWY_META_BIT = 0x10;
public static final int SNOWY_META_BIT = 1 << 16;
public static final int DOUBLE_PLANT_TOP_BIT = 0x8;
public static final int WILDCARD_META_KEY = 1 << 30;
/**
* Creates the standard block meta ID map, the TileEntity NBT-conditional map, and registers
* snowy blocks on {@link BlockRenderingSettings}.
@@ -168,11 +171,17 @@ public class BlockMaterialMapping {
if (metas.isEmpty()) {
for (int meta = 0; meta < 16; meta++) metaMap.putIfAbsent(meta | extraBits, intId);
if (ModStatus.isMetadataExtended) metaMap.putIfAbsent(WILDCARD_META_KEY | extraBits, intId);
} else {
for (int meta : metas) metaMap.putIfAbsent(meta | extraBits, intId);
}
}
public static int resolveId(Int2IntMap metaMap, int metaKey) {
final int id = metaMap.get(metaKey);
return id != -1 ? id : metaMap.get(WILDCARD_META_KEY | (metaKey & SNOWY_META_BIT));
}
/**
* Returns the registered Block for an id, or null if unknown.
* The registry returns Blocks.air (its default) for missing keys, which we coerce to null.
@@ -161,6 +161,7 @@ public class DHCompatInternal {
if (((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion() != cachedVersion) {
cachedVersion = ((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion();
createDepthTex(Minecraft.getMinecraft().getFramebuffer().framebufferWidth, Minecraft.getMinecraft().getFramebuffer().framebufferHeight);
storedDepthTex = -1;
}
if (storedDepthTex != depthTex && dhTerrainFramebuffer != null) {
storedDepthTex = depthTex;
@@ -31,7 +31,6 @@ import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
@@ -46,12 +45,6 @@ import java.util.Map;
public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgram {
// Uniforms
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
private final int id;
private final ProgramUniforms uniforms;
private final CustomUniforms customUniforms;
@@ -62,7 +55,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
private final Matrix4f tempModel = new Matrix4f();
private final Matrix4f tempProj = new Matrix4f();
private final Matrix4f tempMat4 = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
private final int instancedShaderOffsetChunkUniform;
private final int instancedShaderOffsetSubChunkUniform;
@@ -143,12 +135,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GLStateManager.glEnableVertexAttribArray(0);
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
this.instancedShaderOffsetChunkUniform = this.tryGetUniformLocation2("uOffsetChunk");
this.instancedShaderOffsetSubChunkUniform = this.tryGetUniformLocation2("uOffsetSubChunk");
this.instancedShaderCameraChunkPosUniform = this.tryGetUniformLocation2("uCameraPosChunk");
@@ -213,18 +199,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind(DhApiRenderParam renderParam) {
GLStateManager.glBindVertexArray(va);
@@ -238,14 +212,8 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
toJOML(tempModel, renderParam.dhModelViewMatrix);
toJOML(tempProj, renderParam.dhProjectionMatrix);
setUniform(modelViewUniform, tempModel);
setUniform(projectionUniform, tempProj);
this.setUniform(this.instancedShaderProjectionModelViewMatrixUniform, tempMat4.set(tempProj).mul(tempModel));
tempModel.invert();
tempProj.invert();
setUniform(modelViewInverseUniform, tempModel);
setUniform(projectionInverseUniform, tempProj);
setUniform(normalMatrix3fUniform, tempModel.transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(tempProj, tempModel);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
@@ -259,6 +227,7 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
public void unbind() {
LodRendererEvents.popDhMatrices();
GLStateManager.glBindVertexArray(0);
GLStateManager.glUseProgram(0);
ProgramUniforms.clearActiveUniforms();
@@ -3,7 +3,6 @@ package net.coderbot.iris.compat.dh;
import com.google.common.primitives.Ints;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import com.mitchej123.lwjgl.MemoryStack;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import net.coderbot.iris.gl.blending.BlendModeOverride;
@@ -26,15 +25,12 @@ import net.coderbot.iris.uniforms.builtin.BuiltinReplacementUniforms;
import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -44,11 +40,6 @@ public class IrisLodRenderProgram {
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int mircoOffsetUniform;
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
public final int clipDistanceUniform;
private final int id;
@@ -58,9 +49,6 @@ public class IrisLodRenderProgram {
private final ProgramImages images;
private final BlendModeOverride blend;
private final BufferBlendOverride[] bufferBlendOverrides;
private final Matrix4f tempMat4a = new Matrix4f();
private final Matrix4f tempMat4b = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
// This will bind AbstractVertexAttribute
private IrisLodRenderProgram(String name, boolean isShadowPass, boolean translucent, BlendModeOverride override, BufferBlendOverride[] bufferBlendOverrides, String vertex, String tessControl, String tessEval, String geometry, String fragment, CustomUniforms customUniforms, DeferredWorldRenderingPipeline pipeline) {
@@ -132,11 +120,6 @@ public class IrisLodRenderProgram {
modelOffsetUniform = tryGetUniformLocation2("modelOffset");
worldYOffsetUniform = tryGetUniformLocation2("worldYOffset");
mircoOffsetUniform = tryGetUniformLocation2("mircoOffset");
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
// Fog/Clip Uniforms
clipDistanceUniform = tryGetUniformLocation2("clipDistance");
@@ -176,30 +159,6 @@ public class IrisLodRenderProgram {
return GLStateManager.glGetUniformLocation(this.id, name);
}
public void setUniform(int index, Matrix4fc matrix) {
if (index == -1 || matrix == null) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(16);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix4(index, false, buffer);
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind() {
GLStateManager.glUseProgram(id);
@@ -227,11 +186,8 @@ public class IrisLodRenderProgram {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
setUniform(modelViewUniform, modelView);
setUniform(modelViewInverseUniform, modelView.invert(tempMat4a));
setUniform(projectionUniform, projection);
setUniform(projectionInverseUniform, projection.invert(tempMat4b));
setUniform(normalMatrix3fUniform, tempMat4a.set(modelView).invert().transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(projection, modelView);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
@@ -33,6 +33,7 @@ import net.coderbot.iris.shadows.ShadowRenderingState;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.irisshaders.iris.api.v0.IrisApi;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
public class LodRendererEvents {
@@ -46,6 +47,28 @@ public class LodRendererEvents {
private static int textureHeight;
private static final Matrix4f tempMat4 = new Matrix4f();
private static boolean dhMatricesActive = false;
private static final Matrix4f savedModelView = new Matrix4f();
private static final Matrix4f savedProjection = new Matrix4f();
static void pushDhMatrices(Matrix4fc projection, Matrix4fc modelView) {
if (!dhMatricesActive) {
savedModelView.set(GLStateManager.getModelViewMatrix());
savedProjection.set(GLStateManager.getProjectionMatrix());
dhMatricesActive = true;
}
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.setProjectionMatrix(projection);
}
static void popDhMatrices() {
if (dhMatricesActive) {
GLStateManager.setModelViewMatrix(savedModelView);
GLStateManager.setProjectionMatrix(savedProjection);
dhMatricesActive = false;
}
}
// constructor //
@@ -181,6 +204,7 @@ public class LodRendererEvents {
DhApiBeforeRenderCleanupEvent beforeCleanupEvent = new DhApiBeforeRenderCleanupEvent() {
@Override
public void beforeCleanup(DhApiEventParam<DhApiRenderParam> event) {
popDhMatrices();
if (getInstance().shouldOverride) {
if (ShadowRenderingState.areShadowsCurrentlyBeingRendered()) {
getInstance().getShadowShader().unbind();
@@ -0,0 +1,21 @@
package net.coderbot.iris.shaderpack;
import java.util.HashMap;
import java.util.Map;
public class DimensionFlatteningMap {
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
static {
MODERN_TO_LEGACY.put("minecraft:overworld", "Overworld");
MODERN_TO_LEGACY.put("minecraft:the_nether", "Nether");
MODERN_TO_LEGACY.put("minecraft:the_end", "The End");
}
/**
* Maps a modern dimension ID to its 1.7.10 string name.
*/
public static String toLegacyName(String dimensionName) {
return MODERN_TO_LEGACY.getOrDefault(dimensionName, dimensionName);
}
}
@@ -23,14 +23,12 @@ import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuContainer;
import net.coderbot.iris.shaderpack.option.values.MutableOptionValues;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import net.coderbot.iris.shaderpack.parsing.BooleanParser;
import net.coderbot.iris.shaderpack.preprocessor.JcppProcessor;
import net.coderbot.iris.shaderpack.preprocessor.PropertiesPreprocessor;
import net.coderbot.iris.shaderpack.texture.CustomTextureData;
import net.coderbot.iris.shaderpack.texture.TextureFilteringData;
import net.coderbot.iris.shaderpack.texture.TextureStage;
import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.api.v0.IrisApi;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
@@ -242,12 +240,8 @@ public class ShaderPack {
List<String> invalidFeatureFlags = invalidFlagList.stream().map(FeatureFlags::getHumanReadableName).collect(Collectors.toList());
if (!invalidFeatureFlags.isEmpty()) {
// TODO: GUI
// if (Minecraft.getMinecraft().screen instanceof ShaderPackScreen) {
// Minecraft.getMinecraft().setScreen(new FeatureMissingErrorScreen(Minecraft.getMinecraft().screen, I18n.format("iris.unsupported.pack"), I18n.format("iris.unsupported.pack.description", FeatureFlags.getInvalidStatus(invalidFlagList), invalidFeatureFlags.stream()
// .collect(Collectors.joining(", ", ": ", ".")))));
// }
IrisApi.getInstance().getConfig().setShadersEnabledAndApply(false);
// TODO: GUI?
Iris.logger.warn("Shader pack requires unsupported feature flags, loading anyway: {}", String.join(", ", invalidFeatureFlags));
}
ProfileSet profiles = ProfileSet.fromTree(shaderProperties.getProfiles(), this.shaderPackOptions.getOptionSet());
@@ -258,9 +252,7 @@ public class ShaderPack {
this.profile.current.ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms));
// Add programs that are disabled by shader options
shaderProperties.getConditionallyEnabledPrograms().forEach((program, shaderOption) -> {
if ("true".equals(shaderOption)) return;
if ("false".equals(shaderOption) || !this.shaderPackOptions.getOptionValues().getBooleanValueOrDefault(shaderOption)) {
if (!BooleanParser.parse(shaderOption, this.shaderPackOptions.getOptionValues())) {
disabledPrograms.add(program);
}
});
@@ -438,7 +430,8 @@ public class ShaderPack {
folderNames.add(folderName);
for (String dimensionName : dimensionNames) {
dimensionMap.put(dimensionName, folderName);
// Convert modern dimension IDs to 1.7.10 names
dimensionMap.put(DimensionFlatteningMap.toLegacyName(dimensionName), folderName);
}
});
@@ -668,6 +661,7 @@ public class ShaderPack {
*/
public ProgramSet getProgramSet(String dimensionName) {
int dimensionId = Iris.getCurrentDimensionId();
dimensionName = DimensionFlatteningMap.toLegacyName(dimensionName);
// First, try to find an exact match in the dimension map
String folderName = dimensionMap.get(dimensionName);
@@ -11,6 +11,9 @@ public interface OptionValues {
Optional<String> getStringValue(String name);
default boolean getBooleanValueOrDefault(String name) {
if ("0".equals(name)) return false;
if ("1".equals(name)) return true;
return getBooleanValue(name).orElseGet(() -> {
if (!getOptionSet().getBooleanOptions().containsKey(name)) {
Iris.logger.warn("Tried to get boolean value for unknown option: " + name + ", defaulting to true!");
@@ -0,0 +1,124 @@
package net.coderbot.iris.shaderpack.parsing;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import java.util.EmptyStackException;
import java.util.Stack;
public class BooleanParser {
public static boolean parse(String expression, OptionValues valueLookup) {
try {
StringBuilder option = new StringBuilder();
Stack<Operation> operationStack = new Stack<>();
Stack<Boolean> valueStack = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
switch (c) {
case '!' -> operationStack.push(Operation.NOT);
case '&' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (operationStack.isEmpty() || !operationStack.peek().equals(Operation.AND)) {
operationStack.push(Operation.OPEN);
}
i++;
operationStack.push(Operation.AND);
}
case '|' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) {
evaluate(operationStack, valueStack, true);
}
i++;
operationStack.push(Operation.OR);
}
case '(' -> operationStack.push(Operation.OPEN);
case ')' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) {
evaluate(operationStack, valueStack, true);
}
evaluate(operationStack, valueStack, true);
}
case ' ' -> {}
default -> option.append(c);
}
}
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
}
evaluate(operationStack, valueStack, false);
boolean result = valueStack.pop();
if (!valueStack.isEmpty() || !operationStack.isEmpty()) {
Iris.logger.warn("Failed to parse boolean expression, stack isn't empty, defaulting to true: '{}'", expression);
return true;
}
return result;
} catch (EmptyStackException e) {
Iris.logger.warn("Failed to parse boolean expression, stack is empty unexpectedly, defaulting to true: '{}'", expression);
return true;
}
}
private static boolean processValue(String value, OptionValues valueLookup, Stack<Operation> operationStack) {
boolean booleanValue = switch (value) {
case "true", "1" -> true;
case "false", "0" -> false;
default -> valueLookup != null && valueLookup.getBooleanValueOrDefault(value);
};
if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) {
operationStack.pop();
return !booleanValue;
}
return booleanValue;
}
private static void evaluate(Stack<Operation> operationStack, Stack<Boolean> valueStack, boolean currentBracket) {
boolean value = valueStack.pop();
while (!operationStack.isEmpty() && (!currentBracket || operationStack.peek() != Operation.OPEN)) {
value = operationStack.pop().compute(value, valueStack);
}
if (!operationStack.isEmpty() && operationStack.peek() == Operation.OPEN) {
operationStack.pop();
if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) {
value = operationStack.pop().compute(value, valueStack);
}
}
valueStack.push(value);
}
private enum Operation {
AND {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return valueStack.pop() && value;
}
},
OR {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return valueStack.pop() || value;
}
},
NOT {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return !value;
}
},
OPEN;
boolean compute(boolean value, Stack<Boolean> valueStack) {
return value;
}
}
}
@@ -116,6 +116,7 @@ public final class CommonUniforms {
final SmoothedVec2f eyeBrightnessSmooth = new SmoothedVec2f(directives.getEyeBrightnessHalfLife(), directives.getEyeBrightnessHalfLife(), CommonUniforms::getEyeBrightness, updateNotifier);
uniforms
.uniform1f(ONCE, "pi", () -> (float) Math.PI)
.uniform1f(ONCE, "darknessFactor", () -> 0.0F) // This is PER_FRAME in modern, it is an effect added by The Warden. We're just setting to 0 because 1.7.10 doesn't have it.
.uniform1f(ONCE, "darknessLightFactor", () -> 0.0F) // Warden darkness current light factor - 1.7.10 doesn't have it so hardcode to 0
.uniform1b(PER_FRAME, "hideGUI", () -> client.gameSettings.hideGUI)
@@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
@@ -96,7 +97,7 @@ public class ItemMaterialHelper {
if (blockMetaMatches != null) {
Int2IntMap metaMap = blockMetaMatches.get(block);
if (metaMap != null) {
int id = metaMap.get(itemBlock.getMetadata(metadata));
int id = BlockMaterialMapping.resolveId(metaMap, itemBlock.getMetadata(metadata));
if (id != -1) {
return id;
}
@@ -72,6 +72,11 @@ public class VanillaBiomeList {
biomes.add(new BiomeEntry(BiomeGenBase.sky, "END_HIGHLANDS"));
biomes.add(new BiomeEntry(BiomeGenBase.sky, "END_BARRENS"));
// Modern biome variants (1.20+)
biomes.add(new BiomeEntry(BiomeGenBase.jungle, "BIOME_SPARSE_JUNGLE"));
biomes.add(new BiomeEntry(BiomeGenBase.mushroomIsland, "BIOME_MUSHROOM_FIELDS"));
biomes.add(new BiomeEntry(BiomeGenBase.swampland, "BIOME_MANGROVE_SWAMP"));
return Collections.unmodifiableList(biomes);
}
@@ -25,7 +25,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* 2. During each glint renderItemIn2D call:
* a. Pre-pass: no color/depth writes, GL_LEQUAL depth test, increment the stencil 1→2
* where depth passes. This determines pixel ownership without corrupting the depth buffer (damn potions...)
* b. Color pass: draw only where stencil == 2, GL_ALWAYS depth test, decrement the stencil
* b. Color pass: draw only where stencil == 2, GL_LEQUAL depth test, decrement the stencil
* 2→1 to reset for the next glint pass.
*
* For the shadow pass, all stencil operations are skipped. Shadow FBOs have no stencil attachment.
@@ -127,7 +127,7 @@ public class MixinItemRenderer_EdgeDepth {
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 2, 0x03);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
original.call(tess, minU, minV, maxU, maxV, w, h, thickness);
// Restore state for the next glint renderItemIn2D call
@@ -254,6 +254,6 @@ public class MixinRenderBlocks_CrackFix {
}
@Unique
private boolean angelica$crackFixOff() {
return !AngelicaConfig.fixBlockCrack || angelica$disableCrackFix;
return !AngelicaConfig.blockCrackFix || angelica$disableCrackFix;
}
}
@@ -193,7 +193,7 @@ public abstract class MixinRenderBlocks {
CompactCtmQuadProcessor processor = ctx.compact().getProcessor();
RenderBlockState renderBlockState = ctx.renderBlockState();
CTMUtils.clearCurrentCompact();
if (this.blockAccess == null) {
if (this.blockAccess == null || renderBlockState.getBlockAccess() == null) {
return;
}
@@ -6,15 +6,12 @@ import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.HandRenderer;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.item.ItemStack;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
@@ -44,31 +41,6 @@ public class MixinItemRenderer {
ItemIdManager.setItemId(this.itemToRender);
}
/**
* Force the equip lower/raise animation whenever the held stack isn't an exact match for
* the one being rendered.
*/
@Redirect(
method = "updateEquippedItem",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItemDamage()I", ordinal = 0)
)
private int iris$animateStackSwap(ItemStack incoming) {
if (this.itemToRender != null
&& !ItemStack.areItemStacksEqual(incoming, this.itemToRender)) {
return Integer.MIN_VALUE;
}
return incoming.getItemDamage();
}
@Redirect(
method = "updateEquippedItem",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/ItemRenderer;itemToRender:Lnet/minecraft/item/ItemStack;", opcode = Opcodes.PUTFIELD, ordinal = 0)
)
private void iris$commitSlotOnInstantSwap(ItemRenderer self, ItemStack value) {
this.itemToRender = value;
int equippedItemSlot = Minecraft.getMinecraft().thePlayer.inventory.currentItem;
}
/**
* Reset the item ID after rendering first-person items.
*/
@@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Mixin allows devs to target the ender crystal beams as a separate entity.
* Mixin allows devs to target the ender crystal beams and dragon death rays as separate entities.
* Also sets the special condition "lightning" on the dragon's death beams.
*/
@Mixin(RenderDragon.class)
@@ -28,9 +28,15 @@ public abstract class MixinRenderDragon {
@Unique
private static final NamespacedId END_CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
@Unique
private static final NamespacedId DRAGON_DEATH_RAY = new NamespacedId("minecraft", "dragon_death_rays");
@Unique
private int angelica$previousEntityId = -1;
@Unique
private int angelica$previousDeathRayEntityId = -1;
@Unique
private boolean angelica$deathBeamsActive = false;
@@ -83,6 +89,13 @@ public abstract class MixinRenderDragon {
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
angelica$deathBeamsActive = true;
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entityIdMap != null) {
angelica$previousDeathRayEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
int deathRayId = entityIdMap.applyAsInt(DRAGON_DEATH_RAY);
CapturedRenderingState.INSTANCE.setCurrentEntity(deathRayId);
}
angelica$depthPassReplay++;
GLStateManager.glColorMask(false, false, false, false);
angelica$invokeRenderEquippedItems(dragon, partialTicks);
@@ -118,6 +131,10 @@ public abstract class MixinRenderDragon {
private void angelica$endDeathBeamsLighting(EntityDragon dragon, float partialTicks, CallbackInfo ci) {
if (angelica$depthPassReplay > 0) return;
if (angelica$deathBeamsActive) {
if (angelica$previousDeathRayEntityId != -1) {
CapturedRenderingState.INSTANCE.setCurrentEntity(angelica$previousDeathRayEntityId);
angelica$previousDeathRayEntityId = -1;
}
GbufferPrograms.teardownSpecialRenderCondition();
angelica$deathBeamsActive = false;
}