mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580b72829d | ||
|
|
f31161fcc7 | ||
|
|
6e6d90aceb | ||
|
|
d96c10d52f | ||
|
|
ef3f500b1c | ||
|
|
be2f76e23e | ||
|
|
6ef0c77848 | ||
|
|
67656e6342 | ||
|
|
09f1cc7f01 | ||
|
|
c8619a5e27 | ||
|
|
bca80d5518 | ||
|
|
4c0c0970aa | ||
|
|
e1d150f4d5 | ||
|
|
28f62ca04b | ||
|
|
75c5b0f26f | ||
|
|
a62fae73c3 | ||
|
|
16a0e1b8a4 | ||
|
|
91a45c2b69 | ||
|
|
3deec73d3e | ||
|
|
c0ea254d62 | ||
|
|
dea84d4279 | ||
|
|
7a2286f3d5 | ||
|
|
c4c44bd14e | ||
|
|
936d6f82b3 | ||
|
|
6225fcd344 |
No files matched your search
@@ -12,6 +12,14 @@
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
@@ -56,9 +64,9 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
@@ -85,3 +93,8 @@
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
@@ -1,3 +1,5 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
@@ -45,6 +47,18 @@ tasks.register<Copy>("copyDependencies") {
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
// Downgrade embedOnly jars for the tests
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
dependsOn(downgradeEmbedOnlyForTest)
|
||||
classpath = classpath
|
||||
.minus(embedOnly)
|
||||
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
from(embedOnly.map(::zipTree))
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
|
||||
@@ -53,6 +53,7 @@ configurations {
|
||||
embedOnly.canBeResolved = true
|
||||
compileClasspath.extendsFrom(embedOnly)
|
||||
testCompileClasspath.extendsFrom(embedOnly)
|
||||
testRuntimeClasspath.extendsFrom(embedOnly)
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -16,5 +16,5 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.19")
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.20")
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
@@ -12,8 +13,9 @@ import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -24,7 +26,13 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memByteBuffer;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
@@ -49,8 +57,11 @@ public class BatchingFontRenderer {
|
||||
|
||||
private final int AAMode;
|
||||
private final int AAStrength;
|
||||
private final int texBoundAttrLocation;
|
||||
private final int mvpMatrixLocation;
|
||||
private final int fontShaderId;
|
||||
private static int fontVAO = 0;
|
||||
private static int vboPositions, vboColors, vboTexCoords, vboTexBounds, vboIndices;
|
||||
private static int capPositions, capColors, capTexCoords, capTexBounds, capIndices;
|
||||
|
||||
final boolean isSGA;
|
||||
final boolean isSplash;
|
||||
@@ -95,7 +106,7 @@ public class BatchingFontRenderer {
|
||||
fontShaderId = FontAAShader.getProgram().getProgramId();
|
||||
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
|
||||
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
|
||||
texBoundAttrLocation = GL20.glGetAttribLocation(fontShaderId, "texBounds");
|
||||
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
|
||||
}
|
||||
|
||||
// === Batched rendering
|
||||
@@ -104,7 +115,7 @@ public class BatchingFontRenderer {
|
||||
|
||||
private int vtxWriterIndex = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
private static final int INITIAL_BATCH_SIZE = 256;
|
||||
private static final int INITIAL_BATCH_SIZE = 2048;
|
||||
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
|
||||
"this is invalid!");
|
||||
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
|
||||
@@ -271,9 +282,21 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
int lastActiveProgram;
|
||||
int fontAAModeLast = -1;
|
||||
int fontAAStrengthLast = -1;
|
||||
private static int streamUpload(int target, int vbo, long address, int bytes, int cap) {
|
||||
GL15.glBindBuffer(target, vbo);
|
||||
final ByteBuffer data = memByteBuffer(address, bytes);
|
||||
if (bytes > cap) {
|
||||
GL15.glBufferData(target, data, GL15.GL_STREAM_DRAW);
|
||||
return bytes;
|
||||
}
|
||||
GL15.glBufferData(target, cap, GL15.GL_STREAM_DRAW);
|
||||
GL15.glBufferSubData(target, 0, data);
|
||||
return cap;
|
||||
}
|
||||
|
||||
private static final Matrix4f scratchMvp = new Matrix4f();
|
||||
private int fontAAModeLast = -1;
|
||||
private int fontAAStrengthLast = -1;
|
||||
private void flushBatch() {
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
@@ -291,11 +314,8 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
|
||||
final boolean canUseAA = FontConfig.fontAAMode != 0 && prevProgram == 0;
|
||||
if (canUseAA) {
|
||||
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
|
||||
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
|
||||
lastActiveProgram = prevProgram;
|
||||
final boolean useFontShader = prevProgram == 0;
|
||||
if (useFontShader) {
|
||||
GLStateManager.glUseProgram(fontShaderId);
|
||||
if (FontConfig.fontAAMode != fontAAModeLast) {
|
||||
fontAAModeLast = FontConfig.fontAAMode;
|
||||
@@ -305,19 +325,56 @@ public class BatchingFontRenderer {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
}
|
||||
}
|
||||
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
|
||||
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
|
||||
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GL11.glVertexPointer(2, 0, batchVtxPositions);
|
||||
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = org.lwjgl.opengl.GL30.glGenVertexArrays();
|
||||
vboPositions = GL15.glGenBuffers();
|
||||
vboColors = GL15.glGenBuffers();
|
||||
vboTexCoords = GL15.glGenBuffers();
|
||||
vboTexBounds = GL15.glGenBuffers();
|
||||
vboIndices = GL15.glGenBuffers();
|
||||
}
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
|
||||
final int vtxCount = vtxWriterIndex;
|
||||
|
||||
// Location 0: position (vec2, float)
|
||||
batchVtxPositions.position(0).limit(vtxCount * 2);
|
||||
capPositions = streamUpload(GL15.GL_ARRAY_BUFFER, vboPositions, memAddress(batchVtxPositions), batchVtxPositions.remaining() * 4, capPositions);
|
||||
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0L);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
// Location 1: color (vec4, unsigned byte normalized)
|
||||
batchVtxColors.position(0).limit(vtxCount * 4);
|
||||
capColors = streamUpload(GL15.GL_ARRAY_BUFFER, vboColors, memAddress(batchVtxColors), batchVtxColors.remaining(), capColors);
|
||||
GL20.glVertexAttribPointer(1, 4, GL11.GL_UNSIGNED_BYTE, true, 0, 0L);
|
||||
GL20.glEnableVertexAttribArray(1);
|
||||
|
||||
// Location 2: texcoord0 (vec2, float)
|
||||
batchVtxTexCoords.position(0).limit(vtxCount * 2);
|
||||
capTexCoords = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexCoords, memAddress(batchVtxTexCoords), batchVtxTexCoords.remaining() * 4, capTexCoords);
|
||||
GL20.glVertexAttribPointer(2, 2, GL11.GL_FLOAT, false, 0, 0L);
|
||||
GL20.glEnableVertexAttribArray(2);
|
||||
|
||||
// Location 3: texbounds (vec4, float)
|
||||
batchVtxTexBounds.position(0).limit(vtxCount * 4);
|
||||
capTexBounds = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexBounds, memAddress(batchVtxTexBounds), batchVtxTexBounds.remaining() * 4, capTexBounds);
|
||||
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, 0, 0L);
|
||||
GL20.glEnableVertexAttribArray(3);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
|
||||
GL11.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
// Stream upload index buffer
|
||||
batchIndices.position(0).limit(idxWriterIndex);
|
||||
capIndices = streamUpload(GL15.GL_ELEMENT_ARRAY_BUFFER, vboIndices, memAddress(batchIndices), batchIndices.remaining() * 4, capIndices);
|
||||
|
||||
// Use plain for loop to avoid allocations
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
@@ -336,19 +393,14 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
batchIndices.limit(cmd.startVtx + cmd.idxCount);
|
||||
batchIndices.position(cmd.startVtx);
|
||||
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_INT, (long) cmd.startVtx * 4L);
|
||||
}
|
||||
if (canUseAA) {
|
||||
GLStateManager.glUseProgram(lastActiveProgram);
|
||||
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
|
||||
if (useFontShader) {
|
||||
GLStateManager.glUseProgram(prevProgram);
|
||||
}
|
||||
|
||||
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
@@ -362,8 +414,11 @@ public class BatchingFontRenderer {
|
||||
batchCommands.clear();
|
||||
vtxWriterIndex = 0;
|
||||
idxWriterIndex = 0;
|
||||
batchVtxPositions.limit(batchVtxPositions.capacity());
|
||||
batchVtxColors.limit(batchVtxColors.capacity());
|
||||
batchVtxTexCoords.limit(batchVtxTexCoords.capacity());
|
||||
batchVtxTexBounds.limit(batchVtxTexBounds.capacity());
|
||||
batchIndices.limit(batchIndices.capacity());
|
||||
batchIndices.position(0);
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
@@ -429,6 +484,7 @@ public class BatchingFontRenderer {
|
||||
boolean curUnderline = false;
|
||||
|
||||
float glyphScaleY = getGlyphScaleY();
|
||||
float glyphScaleX = getGlyphScaleX();
|
||||
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
|
||||
|
||||
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
|
||||
@@ -534,8 +590,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
final float uStart = fontProvider.getUStart(chr);
|
||||
final float vStart = fontProvider.getVStart(chr);
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * getGlyphScaleX();
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * getGlyphScaleX();
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
final float uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
|
||||
@@ -28,16 +28,14 @@ public final class FontProviderMC implements FontProvider {
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
public int lookupMcFontPosition(char ch) {
|
||||
if (isGlyphAvailable(ch)) {
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
return -1;
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -123,7 +123,7 @@ public class SliderClone extends GuiButton {
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
}
|
||||
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ChunkProviderClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.world.chunk.EmptyChunk;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
@@ -97,17 +98,18 @@ public class ChunkDebugMinimap {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glEnd();
|
||||
tess.draw();
|
||||
|
||||
// Restore GL state
|
||||
GLStateManager.enableLighting();
|
||||
@@ -117,7 +119,7 @@ public class ChunkDebugMinimap {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
private void drawChunk(int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
boolean initialized = false;
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
@@ -143,16 +145,16 @@ public class ChunkDebugMinimap {
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glColor4f(r, g, b, 0.8f);
|
||||
tess.setColorRGBA_F(r, g, b, 0.8f);
|
||||
|
||||
final int xStart = chunkX - playerChunkX;
|
||||
final int zStart = chunkZ - playerChunkZ;
|
||||
final int xEnd = xStart + 1;
|
||||
final int zEnd = zStart + 1;
|
||||
|
||||
GLStateManager.glVertex3d(xStart, zStart, 0);
|
||||
GLStateManager.glVertex3d(xStart, zEnd, 0);
|
||||
GLStateManager.glVertex3d(xEnd, zEnd, 0);
|
||||
GLStateManager.glVertex3d(xEnd, zStart, 0);
|
||||
tess.addVertex(xStart, zStart, 0);
|
||||
tess.addVertex(xStart, zEnd, 0);
|
||||
tess.addVertex(xEnd, zEnd, 0);
|
||||
tess.addVertex(xEnd, zStart, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -10,31 +11,31 @@ import org.lwjgl.opengl.GL11;
|
||||
public class F3Direction {
|
||||
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
|
||||
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
|
||||
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int width = scaledresolution.getScaledWidth();
|
||||
int height = scaledresolution.getScaledHeight();
|
||||
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
final int width = scaledresolution.getScaledWidth();
|
||||
final int height = scaledresolution.getScaledHeight();
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
GL11.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
GLStateManager.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
|
||||
Entity entity = mc.renderViewEntity;
|
||||
GL11.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GL11.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
final Entity entity = mc.renderViewEntity;
|
||||
GLStateManager.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GLStateManager.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GL11.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
GLStateManager.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
|
||||
renderWorldDirections();
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
public static void renderWorldDirections() {
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDepthMask(false);
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDepthMask(false);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
|
||||
GL11.glLineWidth(2.0F);
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
tessellator.startDrawing(GL11.GL_LINES);
|
||||
|
||||
//X
|
||||
@@ -54,8 +55,8 @@ public class F3Direction {
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GL11.glLineWidth(1.0F);
|
||||
GL11.glDepthMask(true);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_ALPHA_TEST;
|
||||
import static org.lwjgl.opengl.GL11.GL_BLEND;
|
||||
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
|
||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
|
||||
import static org.lwjgl.opengl.GL11.glBlendFunc;
|
||||
import static org.lwjgl.opengl.GL11.glColor4f;
|
||||
import static org.lwjgl.opengl.GL11.glDisable;
|
||||
import static org.lwjgl.opengl.GL11.glDisableClientState;
|
||||
import static org.lwjgl.opengl.GL11.glDrawArrays;
|
||||
import static org.lwjgl.opengl.GL11.glEnable;
|
||||
import static org.lwjgl.opengl.GL11.glEnableClientState;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glDisableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.*;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
|
||||
import java.nio.FloatBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
|
||||
public abstract class F3Graph {
|
||||
private static final int NUM_SAMPLES = 240;
|
||||
@@ -63,6 +52,7 @@ public abstract class F3Graph {
|
||||
private int uHeadIdx;
|
||||
private int uSamples;
|
||||
private int vertBuf;
|
||||
private int vao;
|
||||
private final ResourceLocation texture;
|
||||
private final float pxPerNs;
|
||||
private final boolean left;
|
||||
@@ -117,8 +107,8 @@ public abstract class F3Graph {
|
||||
"shaders/debug_graph.frag.glsl");
|
||||
shader.use();
|
||||
|
||||
// Register attributes
|
||||
aPos = shader.getAttribLocation("pos");
|
||||
// Attribute location is declared in shader via layout(location = 0)
|
||||
aPos = 0;
|
||||
|
||||
// Register uniforms
|
||||
uFBWidth = shader.getUniformLocation("fbWidth");
|
||||
@@ -129,7 +119,9 @@ public abstract class F3Graph {
|
||||
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
|
||||
final int uLeft = shader.getUniformLocation("left");
|
||||
|
||||
// Load vertex buffer
|
||||
vao = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
vertBuf = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
try (final MemoryStack stack = stackPush()) {
|
||||
@@ -146,7 +138,13 @@ public abstract class F3Graph {
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Configure vertex attribute in the VAO
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
@@ -186,21 +184,21 @@ public abstract class F3Graph {
|
||||
|
||||
// Setup GL state
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Draw background
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
tess.startDrawingQuads();
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
|
||||
}
|
||||
tess.draw();
|
||||
glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
|
||||
// Draw samples
|
||||
@@ -213,21 +211,16 @@ public abstract class F3Graph {
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
// Left side graphs are inverted, so temporarily disable culling
|
||||
if (!left) glDisable(GL_CULL_FACE);
|
||||
if (!left) GLStateManager.disableCull();
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
|
||||
if (!left) glEnable(GL_CULL_FACE);
|
||||
if (!left) GLStateManager.enableCull();
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableVertexAttribArray(aPos);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
ShaderProgram.clear();
|
||||
|
||||
@@ -242,9 +235,9 @@ public abstract class F3Graph {
|
||||
tess.draw();
|
||||
|
||||
// Reset GL state
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
// Draw text
|
||||
// Ensure running counters are reset on first sample
|
||||
|
||||
@@ -160,7 +160,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
UniversalVAO.deleteVertexArrays(array);
|
||||
GLStateManager.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Compat transformation for mod shaders running under core profile.
|
||||
*
|
||||
* <p>Handles:
|
||||
* <ul>
|
||||
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
|
||||
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
|
||||
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
|
||||
* <li>Texture function renames (texture2D → texture, etc.)</li>
|
||||
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
|
||||
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
|
||||
* <li>gl_FrontColor → local variable in vertex shaders</li>
|
||||
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
|
||||
* <li>Version upgrade to 330 core minimum</li>
|
||||
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CompatShaderTransformer {
|
||||
|
||||
private static final Pattern VERSION_PATTERN =
|
||||
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
|
||||
|
||||
private static final Pattern DEFINE_PATTERN =
|
||||
Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
|
||||
|
||||
/** Compat builtins that trigger AST transformation. */
|
||||
private static final Set<String> COMPAT_BUILTINS = Set.of(
|
||||
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
|
||||
);
|
||||
|
||||
private static final Map<String, String> MATRIX_RENAMES = Map.of(
|
||||
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
|
||||
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
|
||||
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
|
||||
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
|
||||
"gl_NormalMatrix", "angelica_NormalMatrix"
|
||||
);
|
||||
|
||||
private static final Path DUMP_DIR;
|
||||
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
|
||||
static {
|
||||
boolean isDev = false;
|
||||
try {
|
||||
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
|
||||
isDev = Boolean.TRUE.equals(deobfEnv);
|
||||
} catch (Exception ignored) {}
|
||||
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
|
||||
? Paths.get("compat_shaders") : null;
|
||||
}
|
||||
|
||||
private static final int CACHE_SIZE = 32;
|
||||
private record CacheKey(String source, boolean isFragment) {}
|
||||
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(32, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
public static void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a mod shader source for core profile compatibility.
|
||||
*/
|
||||
public static String transform(String source, boolean isFragment) {
|
||||
final boolean needsTransform = needsTransformation(source);
|
||||
String result;
|
||||
if (!needsTransform) {
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
final CacheKey key = new CacheKey(source, isFragment);
|
||||
final String cached = cache.get(key);
|
||||
if (cached != null) {
|
||||
dumpShader(source, cached, isFragment, needsTransform);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
|
||||
dumpShader(source, result, isFragment, needsTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
// Shaders already at 330+ core don't need compat transformation
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
if (version >= 330 && "core".equals(vm.group(2))) return false;
|
||||
}
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
|
||||
private static String transformInternal(String source, boolean isFragment) {
|
||||
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
|
||||
int declaredVersion = 110;
|
||||
if (versionMatcher.find()) {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, 330);
|
||||
|
||||
// Pre-parse reserved word renaming — prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
|
||||
|
||||
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
|
||||
final Transformer transformer = new Transformer(parsedShader.full());
|
||||
|
||||
injectMatrixUniforms(transformer);
|
||||
|
||||
transformFog(transformer, isFragment, source);
|
||||
|
||||
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
|
||||
if (source.contains("gl_FrontLightModelProduct")) {
|
||||
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_FrontColor", "angelica_FrontColor");
|
||||
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
|
||||
|
||||
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
|
||||
transformVertexAttributes(transformer, source);
|
||||
} else {
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("in vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_Color", "angelica_FrontColor");
|
||||
}
|
||||
}
|
||||
|
||||
// gl_TexCoord[N] varying array → per-index in/out declarations
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
|
||||
for (Integer i : texCoordIndices) {
|
||||
final String qualifier = isFragment ? "in" : "out";
|
||||
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
|
||||
}
|
||||
|
||||
// Fragment output handling + alpha test discard
|
||||
if (isFragment) {
|
||||
transformFragmentOutputs(transformer);
|
||||
}
|
||||
|
||||
// texture-as-variable collision handling
|
||||
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
|
||||
transformer.rename("texture", "gtexture");
|
||||
}
|
||||
if (transformer.hasVariable("angelica_renamed_texture")) {
|
||||
transformer.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
|
||||
transformer.renameAndWrapShadow("shadow2D", "texture");
|
||||
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
final String versionDirective = "#version " + targetVersion + " core\n";
|
||||
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
// Preserve #define directives
|
||||
final StringBuilder defines = new StringBuilder();
|
||||
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
|
||||
while (defineMatcher.find()) {
|
||||
defines.append(defineMatcher.group().trim()).append('\n');
|
||||
}
|
||||
|
||||
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
|
||||
// Restore pre-parse renames
|
||||
String output = GlslTransformUtils.restoreReservedWords(result.toString());
|
||||
|
||||
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
|
||||
output = fixupQualifiers(output, isFragment);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename compat builtins.
|
||||
*/
|
||||
private static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
|
||||
|
||||
transformer.rename(MATRIX_RENAMES);
|
||||
|
||||
// Expression replacements
|
||||
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
|
||||
transformer.replaceExpression("gl_TextureMatrix", "mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform fragment outputs for core profile.
|
||||
*/
|
||||
private static void transformFragmentOutputs(Transformer transformer) {
|
||||
if (transformer.containsCall("gl_FragColor")) {
|
||||
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
final Set<Integer> found = new HashSet<>();
|
||||
transformer.renameArray("gl_FragData", "angelica_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
|
||||
if (found.contains(0)) {
|
||||
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
|
||||
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
|
||||
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
|
||||
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out float angelica_FogFragCoord;");
|
||||
transformer.prependMain("angelica_FogFragCoord = 0.0;");
|
||||
} else {
|
||||
if (source.contains("gl_FogFragCoord")) {
|
||||
transformer.injectVariable("in float angelica_FogFragCoord;");
|
||||
}
|
||||
}
|
||||
|
||||
transformer.rename("gl_Fog", "angelica_Fog");
|
||||
transformer.injectVariable("uniform float angelica_FogDensity;");
|
||||
transformer.injectVariable("uniform float angelica_FogStart;");
|
||||
transformer.injectVariable("uniform float angelica_FogEnd;");
|
||||
transformer.injectVariable("uniform vec4 angelica_FogColor;");
|
||||
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
transformer.injectFunction(
|
||||
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
|
||||
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
|
||||
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
|
||||
*/
|
||||
private static void transformVertexAttributes(Transformer transformer, String source) {
|
||||
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
|
||||
transformer.rename("gl_Vertex", "angelica_Vertex");
|
||||
}
|
||||
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
|
||||
transformer.rename("gl_Color", "angelica_Color");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord0")) {
|
||||
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
|
||||
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord1")) {
|
||||
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
|
||||
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
|
||||
}
|
||||
if (source.contains("gl_Normal")) {
|
||||
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
|
||||
transformer.rename("gl_Normal", "angelica_Normal");
|
||||
}
|
||||
if (source.contains("ftransform")) {
|
||||
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
|
||||
if (DUMP_DIR == null) return;
|
||||
final int id = dumpCounter.getAndIncrement();
|
||||
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
|
||||
try {
|
||||
Files.createDirectories(DUMP_DIR);
|
||||
// Capture caller info for identification
|
||||
final String caller = identifyCaller();
|
||||
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
|
||||
+ "\n// Transformed: " + wasTransformed
|
||||
+ "\n// Caller: " + caller + "\n\n";
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifyCaller() {
|
||||
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
|
||||
final String cls = frame.getClassName();
|
||||
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
|
||||
&& !cls.startsWith("java.")
|
||||
&& !cls.equals("org.lwjgl.opengl.GL20")) {
|
||||
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
|
||||
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
|
||||
|
||||
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
|
||||
public static String fixupQualifiers(String source, boolean isFragment) {
|
||||
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
|
||||
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
|
||||
return source;
|
||||
}
|
||||
|
||||
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
|
||||
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
|
||||
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
|
||||
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
|
||||
|
||||
/**
|
||||
* Generate a passthrough vertex shader for a fragment-only program.
|
||||
*
|
||||
* @param fragmentSource the transformed fragment shader source
|
||||
* @return a complete vertex shader source string
|
||||
*/
|
||||
public static String generatePassthroughVertexShader(String fragmentSource) {
|
||||
final StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
// Always have position
|
||||
sb.append("layout(location = 0) in vec4 a_Position;\n");
|
||||
|
||||
// Matrix uniforms for position transform
|
||||
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
|
||||
|
||||
final StringBuilder varyings = new StringBuilder();
|
||||
final StringBuilder assignments = new StringBuilder();
|
||||
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
|
||||
sb.append("layout(location = 1) in vec4 a_Color;\n");
|
||||
varyings.append("out vec4 angelica_FrontColor;\n");
|
||||
assignments.append(" angelica_FrontColor = a_Color;\n");
|
||||
}
|
||||
|
||||
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
while (texCoordMatcher.find()) {
|
||||
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
|
||||
}
|
||||
for (int i : texCoordIndices) {
|
||||
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
|
||||
final int location = i == 0 ? 2 : 3;
|
||||
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
|
||||
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
|
||||
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
|
||||
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
|
||||
varyings.append("out float angelica_FogFragCoord;\n");
|
||||
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
|
||||
}
|
||||
|
||||
sb.append(varyings);
|
||||
sb.append("\nvoid main() {\n");
|
||||
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
|
||||
sb.append(assignments);
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
|
||||
private static String fixupVersion(String source) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (!m.find()) {
|
||||
return "#version 330 core\n" + source;
|
||||
}
|
||||
|
||||
final int version = Integer.parseInt(m.group(1));
|
||||
final String profile = m.group(2);
|
||||
|
||||
if (version >= 330 && !"compatibility".equals(profile)) {
|
||||
if ("core".equals(profile)) return source;
|
||||
return m.replaceFirst("#version " + version + " core");
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(version, 330);
|
||||
return m.replaceFirst("#version " + targetVersion + " core");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
|
||||
*/
|
||||
public class CompatUniformManager {
|
||||
|
||||
// Uniform location indices within the cached array (package-private for tests)
|
||||
static final int LOC_MODELVIEW = 0;
|
||||
static final int LOC_MODELVIEW_INVERSE = 1;
|
||||
static final int LOC_PROJECTION = 2;
|
||||
static final int LOC_PROJECTION_INVERSE = 3;
|
||||
static final int LOC_NORMAL = 4;
|
||||
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
|
||||
static final int LOC_FOG_DENSITY = 6;
|
||||
static final int LOC_FOG_START = 7;
|
||||
static final int LOC_FOG_END = 8;
|
||||
static final int LOC_FOG_COLOR = 9;
|
||||
static final int LOC_IRIS_MODELVIEW = 10;
|
||||
static final int LOC_IRIS_PROJECTION = 11;
|
||||
static final int LOC_IRIS_NORMAL = 12;
|
||||
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
|
||||
static final int LOC_ALPHA_TEST_REF = 14;
|
||||
static final int LOC_SCENE_COLOR = 15;
|
||||
static final int LOC_CLIP_PLANES = 16;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 17;
|
||||
static final int LOC_COUNT = 18;
|
||||
|
||||
private static final String[] UNIFORM_NAMES = {
|
||||
"angelica_ModelViewMatrix",
|
||||
"angelica_ModelViewMatrixInverse",
|
||||
"angelica_ProjectionMatrix",
|
||||
"angelica_ProjectionMatrixInverse",
|
||||
"angelica_NormalMatrix",
|
||||
"angelica_LightmapTextureMatrix",
|
||||
"angelica_FogDensity",
|
||||
"angelica_FogStart",
|
||||
"angelica_FogEnd",
|
||||
"angelica_FogColor",
|
||||
"iris_ModelViewMatrix",
|
||||
"iris_ProjectionMatrix",
|
||||
"iris_NormalMatrix",
|
||||
"iris_LightmapTextureMatrix",
|
||||
"angelica_currentAlphaTest",
|
||||
"angelica_SceneColor",
|
||||
"angelica_ClipPlane[0]",
|
||||
"angelica_ClipPlanesEnabled",
|
||||
};
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
|
||||
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
|
||||
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
|
||||
private static final Matrix3f normalMatrix = new Matrix3f();
|
||||
private static final Matrix4f scratchMatrix = new Matrix4f();
|
||||
|
||||
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
|
||||
private static final FloatBuffer lightmapMatrixBuf;
|
||||
static {
|
||||
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// Dirty tracking: skip uploads when state hasn't changed and program is the same
|
||||
private static int lastProgram = -1;
|
||||
private static int lastMvGen = -1;
|
||||
private static int lastProjGen = -1;
|
||||
private static int lastTexMatGen = -1;
|
||||
private static int lastFragmentGen = -1;
|
||||
private static int lastLightingGen = -1;
|
||||
private static int lastClipPlaneGen = -1;
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
public static void onLinkProgram(int program) {
|
||||
int[] locs = new int[LOC_COUNT];
|
||||
boolean hasAny = false;
|
||||
|
||||
for (int i = 0; i < LOC_COUNT; i++) {
|
||||
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
|
||||
if (locs[i] != -1) hasAny = true;
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programLocations.put(program, locs);
|
||||
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUseProgram(int program) {
|
||||
if (program == 0) return;
|
||||
|
||||
int[] locs = programLocations.get(program);
|
||||
if (locs == null) return;
|
||||
|
||||
final boolean programChanged = program != lastProgram;
|
||||
lastProgram = program;
|
||||
|
||||
// Matrix uniforms — skip if generation unchanged and same program
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color) — skip if lighting state unchanged
|
||||
if (locs[LOC_SCENE_COLOR] != -1) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
uploadSceneColor(locs);
|
||||
}
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
if (mvChanged) {
|
||||
// ModelView
|
||||
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
mv.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// ModelView Inverse
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
|
||||
mv.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
normalMatrix.get(mat3Buf);
|
||||
if (locs[LOC_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projChanged) {
|
||||
// Projection
|
||||
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
proj.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection Inverse
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1) {
|
||||
proj.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (texMatChanged) {
|
||||
// Iris lightmap texture matrix (constant)
|
||||
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
|
||||
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
if (locs[LOC_FOG_DENSITY] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
|
||||
}
|
||||
if (locs[LOC_FOG_START] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
|
||||
}
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y)
|
||||
.put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
|
||||
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
|
||||
if (locs[LOC_ALPHA_TEST_REF] != -1) {
|
||||
final float ref;
|
||||
if (!GLStateManager.getAlphaTest().isEnabled()
|
||||
|| GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
|
||||
ref = -1.0f;
|
||||
} else {
|
||||
ref = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
|
||||
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
|
||||
*/
|
||||
private static void uploadSceneColor(int[] locs) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final LightModelState lm = GLStateManager.getLightModel();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
|
||||
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
|
||||
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
|
||||
.put(mat.diffuse.w);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programLocations.remove(program);
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programLocations.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
return programLocations.get(program);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
* Copyright 2017 Valve Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Original author: Samuel Pitoiset <[email protected]>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
}
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,14 @@ import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
|
||||
/**
|
||||
* VBO-based display list emulation with command recording, transform collapsing, and format-based batching.
|
||||
@@ -103,9 +103,6 @@ public class DisplayListManager {
|
||||
private static List<List<String>> multMatrixSources = null; // Source ops for each MULT_MATRIX in raw buffer
|
||||
private static List<String> drawRangeSources = null; // Source type for each DRAW_RANGE in final buffer
|
||||
|
||||
// For flushMatrix() - reusable buffer for executing collapsed transforms
|
||||
private static final FloatBuffer flushMatrixBuffer = org.lwjgl.BufferUtils.createFloatBuffer(16);
|
||||
|
||||
// Nested compilation support - stack of parent contexts
|
||||
private static final Deque<CompilationContext> compilationStack = new ArrayDeque<>();
|
||||
|
||||
@@ -177,11 +174,8 @@ public class DisplayListManager {
|
||||
stateGeneration++;
|
||||
}
|
||||
|
||||
// COMPILE_AND_EXECUTE: execute now. Bypass GLSM to avoid re-entering recording path.
|
||||
// COMPILE_AND_EXECUTE: apply to GLSM software stack
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
flushMatrixBuffer.clear();
|
||||
relativeTransform.get(flushMatrixBuffer);
|
||||
GL11.glMultMatrix(flushMatrixBuffer);
|
||||
GLStateManager.getMatrixStack().mul(relativeTransform);
|
||||
}
|
||||
|
||||
@@ -358,6 +352,12 @@ public class DisplayListManager {
|
||||
currentRecorder.recordViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
public static void recordScissor(int x, int y, int width, int height) {
|
||||
if (currentRecorder == null) return;
|
||||
drawBarrier();
|
||||
currentRecorder.recordScissor(x, y, width, height);
|
||||
}
|
||||
|
||||
public static void recordPointSize(float size) {
|
||||
if (currentRecorder == null) return;
|
||||
drawBarrier();
|
||||
@@ -497,6 +497,12 @@ public class DisplayListManager {
|
||||
currentRecorder.recordMaterial(face, pname, params);
|
||||
}
|
||||
|
||||
public static void recordClipPlane(int plane, double a, double b, double c, double d) {
|
||||
if (currentRecorder == null) return;
|
||||
drawBarrier();
|
||||
currentRecorder.recordClipPlane(plane, a, b, c, d);
|
||||
}
|
||||
|
||||
public static void recordStencilFunc(int func, int ref, int mask) {
|
||||
if (currentRecorder == null) return;
|
||||
drawBarrier();
|
||||
@@ -619,9 +625,6 @@ public class DisplayListManager {
|
||||
applyTransformOp(singleTransform, x, y, z, op, rotationAxis);
|
||||
if (currentRecorder != null) currentRecorder.recordMultMatrix(singleTransform);
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
flushMatrixBuffer.clear();
|
||||
singleTransform.get(flushMatrixBuffer);
|
||||
GL11.glMultMatrix(flushMatrixBuffer);
|
||||
GLStateManager.getMatrixStack().mul(singleTransform);
|
||||
}
|
||||
return;
|
||||
@@ -659,9 +662,6 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
flushMatrixBuffer.clear();
|
||||
matrix.get(flushMatrixBuffer);
|
||||
GL11.glMultMatrix(flushMatrixBuffer);
|
||||
GLStateManager.getMatrixStack().mul(matrix);
|
||||
}
|
||||
return;
|
||||
@@ -686,9 +686,6 @@ public class DisplayListManager {
|
||||
if (DEBUG_DISPLAY_LISTS) {
|
||||
if (currentRecorder != null) currentRecorder.recordMultMatrix(orthoFrustumTemp);
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
flushMatrixBuffer.clear();
|
||||
orthoFrustumTemp.get(flushMatrixBuffer);
|
||||
GL11.glMultMatrix(flushMatrixBuffer);
|
||||
GLStateManager.getMatrixStack().mul(orthoFrustumTemp);
|
||||
}
|
||||
return;
|
||||
@@ -709,9 +706,6 @@ public class DisplayListManager {
|
||||
if (DEBUG_DISPLAY_LISTS) {
|
||||
if (currentRecorder != null) currentRecorder.recordMultMatrix(orthoFrustumTemp);
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
flushMatrixBuffer.clear();
|
||||
orthoFrustumTemp.get(flushMatrixBuffer);
|
||||
GL11.glMultMatrix(flushMatrixBuffer);
|
||||
GLStateManager.getMatrixStack().mul(orthoFrustumTemp);
|
||||
}
|
||||
return;
|
||||
@@ -956,11 +950,8 @@ public class DisplayListManager {
|
||||
vbo.render();
|
||||
}
|
||||
// Per OpenGL spec: if list is undefined, glCallList has no effect
|
||||
} else {
|
||||
// Positive IDs - fall back to native GL display lists
|
||||
// This happens for lists allocated but never compiled via glNewList
|
||||
GL11.glCallList(list);
|
||||
}
|
||||
// An uncached positive-ID list = no-op (per OpenGL spec)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -971,8 +962,6 @@ public class DisplayListManager {
|
||||
CompiledDisplayList compiled = displayListCache.remove(i);
|
||||
if (compiled != null) {
|
||||
compiled.delete();
|
||||
} else {
|
||||
GL11.glDeleteLists(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@@ -87,22 +88,20 @@ public class Feature {
|
||||
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
|
||||
, GLStateManager.alphaState // Alpha test function and reference value
|
||||
, GLStateManager.blendMode // GL_BLEND enable bit
|
||||
, GLStateManager.blendState // Blending source and destination functions
|
||||
// Constant blend color
|
||||
// Blending equation
|
||||
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
|
||||
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
|
||||
, GLStateManager.ditherState // GL_DITHER enable bit
|
||||
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
|
||||
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
|
||||
// Logic op function
|
||||
, GLStateManager.logicOpMode // Logic op function
|
||||
, GLStateManager.colorMask // Color-mode and index-mode writemasks
|
||||
, GLStateManager.clearColor // Color-mode and index-mode clear values
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
|
||||
GLStateManager.color // Current RGBA color
|
||||
, ShaderManager.getNormalStack() // Current normal vector
|
||||
, ShaderManager.getTexCoordStack() // Current texture coordinates
|
||||
// Current color index
|
||||
// Current normal vector
|
||||
// Current texture coordinates
|
||||
// Current raster position
|
||||
// GL_CURRENT_RASTER_POSITION_VALID flag
|
||||
// RGBA color associated with current raster position
|
||||
@@ -300,6 +299,8 @@ public class Feature {
|
||||
));
|
||||
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.activeTextureUnit // Active texture unit
|
||||
, GLStateManager.texEnvMode // GL_TEXTURE_ENV_MODE
|
||||
, GLStateManager.texEnvColor // GL_TEXTURE_ENV_COLOR
|
||||
// Enable bits for the four texture coordinates
|
||||
|
||||
// Border color for each texture image
|
||||
@@ -307,7 +308,6 @@ public class Feature {
|
||||
// Magnification function for each texture image
|
||||
// Texture coordinates and wrap mode for each texture image
|
||||
|
||||
// Color and mode for each texture environment
|
||||
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
|
||||
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
|
||||
// glTexGen plane equations for S, T, R, and Q
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shared GLSL shader transformation utilities.
|
||||
*/
|
||||
public class GlslTransformUtils {
|
||||
|
||||
private static final String RENAMED_PREFIX = "angelica_renamed_";
|
||||
|
||||
/** Texture function renames */
|
||||
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
|
||||
Map.entry("texture2D", "texture"),
|
||||
Map.entry("texture3D", "texture"),
|
||||
Map.entry("texture2DLod", "textureLod"),
|
||||
Map.entry("texture3DLod", "textureLod"),
|
||||
Map.entry("texture2DProj", "textureProj"),
|
||||
Map.entry("texture3DProj", "textureProj"),
|
||||
Map.entry("texture2DGrad", "textureGrad"),
|
||||
Map.entry("texture2DGradARB", "textureGrad"),
|
||||
Map.entry("texture3DGrad", "textureGrad"),
|
||||
Map.entry("texelFetch2D", "texelFetch"),
|
||||
Map.entry("texelFetch3D", "texelFetch"),
|
||||
Map.entry("textureSize2D", "textureSize")
|
||||
);
|
||||
|
||||
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
||||
|
||||
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
|
||||
private record ReservedWordRename(Pattern pattern, String replacement) {}
|
||||
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
|
||||
400, List.of(new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"))
|
||||
);
|
||||
|
||||
public static String replaceTexture(String input) {
|
||||
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
|
||||
} else {
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String renameReservedWords(String source, int targetVersion) {
|
||||
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
|
||||
if (targetVersion < entry.getKey()) {
|
||||
for (var rename : entry.getValue()) {
|
||||
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
|
||||
}
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String restoreReservedWords(String source) {
|
||||
source = source.replace(RENAMED_PREFIX + "texture", "texture");
|
||||
source = source.replace(RENAMED_PREFIX + "sample", "sample");
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
|
||||
if (tree instanceof TerminalNode) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
}
|
||||
|
||||
if (text.equals("}")) {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
|
||||
*
|
||||
* <p>Maintains a single growable element buffer with pre-computed indices mapping
|
||||
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
|
||||
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
|
||||
* so both emitted triangles share the quad's provoking vertex (v3).
|
||||
*/
|
||||
public final class QuadConverter {
|
||||
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
|
||||
|
||||
private static int eboId;
|
||||
private static int maxQuads;
|
||||
|
||||
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
|
||||
private static int scratchEboId;
|
||||
private static int scratchEboCapacity; // in bytes
|
||||
|
||||
private QuadConverter() {}
|
||||
|
||||
private static void ensureCapacity(int quadCount) {
|
||||
if (quadCount <= maxQuads) return;
|
||||
|
||||
// Round up to power of 2 for amortized growth
|
||||
int newMaxQuads = Math.max(256, maxQuads);
|
||||
while (newMaxQuads < quadCount) {
|
||||
newMaxQuads *= 2;
|
||||
}
|
||||
|
||||
if (eboId == 0) {
|
||||
eboId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
// Generate index data: 6 ints per quad
|
||||
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
|
||||
long ptr = memAddress0(indexData);
|
||||
for (int i = 0; i < newMaxQuads; i++) {
|
||||
final int base = i * 4;
|
||||
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
|
||||
// Triangle 1: 0, 1, 3
|
||||
memPutInt(ptr, base);
|
||||
memPutInt(ptr + 4, base + 1);
|
||||
memPutInt(ptr + 8, base + 3);
|
||||
// Triangle 2: 1, 2, 3
|
||||
memPutInt(ptr + 12, base + 1);
|
||||
memPutInt(ptr + 16, base + 2);
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
memFree(indexData);
|
||||
maxQuads = newMaxQuads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
|
||||
* Binds the shared EBO to the current VAO and issues glDrawElements.
|
||||
*
|
||||
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(int first, int vertexCount) {
|
||||
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
|
||||
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
|
||||
final int quadCount = vertexCount / 4;
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload converted triangle indices to the scratch EBO and draw.
|
||||
* Saves/restores the caller's EBO binding so subsequent indexed draws
|
||||
* (or glGetBufferSubData reads) still see the original EBO.
|
||||
*
|
||||
* @param dst off-heap buffer with triangle index data (freed by this method)
|
||||
* @param triIndexCount number of triangle indices
|
||||
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth — allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
|
||||
* building a triangle index buffer, and issuing the converted draw.
|
||||
*
|
||||
* @param indexCount number of quad indices (must be multiple of 4)
|
||||
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
|
||||
* @param offset byte offset into the currently bound EBO
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final int bytesPerIndex;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT: bytesPerIndex = 4; break;
|
||||
case GL11.GL_UNSIGNED_SHORT: bytesPerIndex = 2; break;
|
||||
case GL11.GL_UNSIGNED_BYTE: bytesPerIndex = 1; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
// Read source indices from caller's EBO
|
||||
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
|
||||
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
|
||||
final long srcAddr = memAddress0(src);
|
||||
|
||||
// Allocate output as GL_UNSIGNED_INT
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
default /* GL_UNSIGNED_BYTE */ -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
}
|
||||
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a);
|
||||
memPutInt(dstAddr + o + 4, b);
|
||||
memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b);
|
||||
memPutInt(dstAddr + o + 16, c);
|
||||
memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side IntBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ShortBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 2);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 12;
|
||||
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
|
||||
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
|
||||
if (count == 0) return;
|
||||
assert count % 4 == 0;
|
||||
final int quadCount = count / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
final long srcAddr = memAddress0(indices) + indices.position();
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
default -> { memFree(dst); return; }
|
||||
}
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the shared EBO.
|
||||
*/
|
||||
public static void destroy() {
|
||||
if (eboId != 0) {
|
||||
GL15.glDeleteBuffers(eboId);
|
||||
eboId = 0;
|
||||
maxQuads = 0;
|
||||
}
|
||||
if (scratchEboId != 0) {
|
||||
GL15.glDeleteBuffers(scratchEboId);
|
||||
scratchEboId = 0;
|
||||
scratchEboCapacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
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.texture.TextureInfoCache;
|
||||
import net.coderbot.iris.gl.shader.StandardMacros;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -22,6 +25,7 @@ import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
@@ -29,8 +33,6 @@ import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
import org.lwjgl.opengl.NVXGpuMemoryInfo;
|
||||
|
||||
import net.coderbot.iris.gl.shader.StandardMacros;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
@@ -62,7 +64,7 @@ public class RenderSystem {
|
||||
public static void initRenderer() {
|
||||
try {
|
||||
if (GLStateManager.capabilities.OpenGL45) {
|
||||
dsaState = new DSACore();
|
||||
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
|
||||
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
|
||||
@@ -124,6 +126,15 @@ public class RenderSystem {
|
||||
LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
|
||||
LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
|
||||
LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
|
||||
|
||||
if (GLStateManager.capabilities.OpenGL32) {
|
||||
final int profileMask = GL11.glGetInteger(GL32.GL_CONTEXT_PROFILE_MASK);
|
||||
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
|
||||
LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.getInstance().enable();
|
||||
GLStateManager.lineWidthMax = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
@@ -136,10 +147,7 @@ public class RenderSystem {
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
@@ -204,20 +212,12 @@ public class RenderSystem {
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,30 +16,21 @@ public class DSAARB extends DSAUnsupported {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
|
||||
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
|
||||
// For simplicity, use the fallback which binds, uploads, and restores
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
|
||||
|
||||
ARBDirectStateAccess.glTextureParameteri(texture, pname, param);
|
||||
@@ -47,6 +38,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void texParameterf(int texture, int target, int pname, float param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
|
||||
|
||||
ARBDirectStateAccess.glTextureParameterf(texture, pname, param);
|
||||
@@ -54,6 +46,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.remapTexClampBuffer(pname, params);
|
||||
ARBDirectStateAccess.glTextureParameter(texture, pname, params);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@ public interface DSAAccess {
|
||||
void generateMipmaps(int texture, int target);
|
||||
|
||||
// Texture image upload - DSA allows specifying texture directly without binding
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset);
|
||||
|
||||
void texParameteri(int texture, int target, int pname, int param);
|
||||
void texParameterf(int texture, int target, int pname, float param);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import org.lwjgl.opengl.EXTDirectStateAccess;
|
||||
|
||||
public class DSAEXT extends DSACore {
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -24,36 +23,19 @@ public class DSAUnsupported implements DSAAccess {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
if(cachedBinding != texture) GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
if(cachedBinding != texture) GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
/**
|
||||
* Compact key capturing the fragment shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*/
|
||||
public final class FragmentKey {
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_FOG_MODE = 0;
|
||||
private static final int BIT_ALPHA_TEST = 2;
|
||||
private static final int BIT_ALPHA_FUNC = 3;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_TEXTURE = 7;
|
||||
private static final int BIT_LIGHTMAP = 8;
|
||||
private static final int BIT_TEX_ENV_MODE = 9; // 3 bits: 0=MODULATE, 1=REPLACE, 2=ADD, 3=DECAL, 4=BLEND
|
||||
|
||||
public static final int FOG_NONE = 0;
|
||||
public static final int FOG_LINEAR = 1;
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private FragmentKey(long packed) {
|
||||
this.packed = packed;
|
||||
}
|
||||
|
||||
public long pack() { return packed; }
|
||||
|
||||
public int fogMode() { return (int)(packed & 0x3); }
|
||||
public boolean alphaTestEnabled() { return ((packed >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int)((packed >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public boolean textureEnabled() { return ((packed >> BIT_TEXTURE) & 1) != 0; }
|
||||
public boolean lightmapEnabled() { return ((packed >> BIT_LIGHTMAP) & 1) != 0; }
|
||||
public int texEnvMode() { return (int)((packed >> BIT_TEX_ENV_MODE) & 0x7); }
|
||||
|
||||
public static final int TEX_ENV_MODULATE = 0;
|
||||
public static final int TEX_ENV_REPLACE = 1;
|
||||
public static final int TEX_ENV_ADD = 2;
|
||||
public static final int TEX_ENV_DECAL = 3;
|
||||
public static final int TEX_ENV_BLEND = 4;
|
||||
|
||||
public static long packFromState() {
|
||||
long bits = 0;
|
||||
|
||||
// Fog
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
final int glFogMode = GLStateManager.getFogState().getFogMode();
|
||||
final int fogMode = switch (glFogMode) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
bits |= ((long) fogMode & 0x3);
|
||||
}
|
||||
|
||||
// Alpha test
|
||||
if (GLStateManager.getAlphaTest().isEnabled()) {
|
||||
bits |= (1L << BIT_ALPHA_TEST);
|
||||
final int func = encodeAlphaFunc(GLStateManager.getAlphaState().getFunction());
|
||||
bits |= ((long) func & 0x7) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Separate specular
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
|
||||
// Texture unit 0
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
|
||||
// Tex env mode only matters when texture is enabled
|
||||
final int envMode = encodeTexEnvMode(GLStateManager.getTexEnvMode().getValue());
|
||||
bits |= ((long) envMode & 0x7) << BIT_TEX_ENV_MODE;
|
||||
}
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static FragmentKey fromState() {
|
||||
return new FragmentKey(packFromState());
|
||||
}
|
||||
|
||||
static FragmentKey fromPacked(long packed) {
|
||||
return new FragmentKey(packed);
|
||||
}
|
||||
|
||||
private static int encodeAlphaFunc(int glFunc) {
|
||||
return glFunc & 0x7;
|
||||
}
|
||||
|
||||
private static int encodeTexEnvMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
|
||||
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
|
||||
case GL11.GL_ADD -> TEX_ENV_ADD;
|
||||
case GL11.GL_DECAL -> TEX_ENV_DECAL;
|
||||
case GL11.GL_BLEND -> TEX_ENV_BLEND;
|
||||
default -> TEX_ENV_MODULATE;
|
||||
};
|
||||
}
|
||||
|
||||
public static int decodeAlphaFunc(int encoded) {
|
||||
return 0x0200 | encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FragmentKey other)) return false;
|
||||
return packed == other.packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String fogName = switch (fogMode()) {
|
||||
case FOG_NONE -> "NONE";
|
||||
case FOG_LINEAR -> "LINEAR";
|
||||
case FOG_EXP -> "EXP";
|
||||
case FOG_EXP2 -> "EXP2";
|
||||
default -> "?";
|
||||
};
|
||||
final String envName = switch (texEnvMode()) {
|
||||
case TEX_ENV_MODULATE -> "MOD";
|
||||
case TEX_ENV_REPLACE -> "REPL";
|
||||
case TEX_ENV_ADD -> "ADD";
|
||||
case TEX_ENV_DECAL -> "DEC";
|
||||
case TEX_ENV_BLEND -> "BL";
|
||||
default -> "?";
|
||||
};
|
||||
return String.format("FFPFragmentKey[0x%03X: fog=%s alpha=%b(%s) specSep=%b tex=%b(env=%s) lm=%b]",
|
||||
packed, fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), textureEnabled(), envName, lightmapEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core fragment shaders for FFP emulation.
|
||||
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
|
||||
*/
|
||||
public final class FragmentShaderGenerator {
|
||||
|
||||
private FragmentShaderGenerator() {}
|
||||
|
||||
public static String generate(FragmentKey key) {
|
||||
final StringBuilder sb = new StringBuilder(1024);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitInputs(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
sb.append("out vec4 fragColor;\n\n");
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitTextureSampling(sb, key);
|
||||
emitColorCombine(sb, key);
|
||||
emitSpecularAdd(sb, key);
|
||||
emitAlphaTest(sb, key);
|
||||
emitFog(sb, key);
|
||||
sb.append(" fragColor = color;\n");
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("in float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("uniform sampler2D u_Sampler0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("uniform sampler2D u_Sampler1;\n");
|
||||
}
|
||||
if (key.alphaTestEnabled()) {
|
||||
sb.append("uniform float u_AlphaRef;\n");
|
||||
}
|
||||
if (key.textureEnabled() && key.texEnvMode() == FragmentKey.TEX_ENV_BLEND) {
|
||||
sb.append("uniform vec4 u_TexEnvColor;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("uniform vec4 u_FogParams;\n");
|
||||
sb.append("uniform vec4 u_FogColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
if (key.textureEnabled()) {
|
||||
sb.append(" vec4 texColor = texture(u_Sampler0, v_TexCoord0.st);\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" vec4 lightmapColor = texture(u_Sampler1, v_TexCoord1.st);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitColorCombine(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.textureEnabled()) {
|
||||
sb.append(" // No texture — vertex color only\n");
|
||||
sb.append(" vec4 color = v_Color;\n");
|
||||
} else {
|
||||
final int envMode = key.texEnvMode();
|
||||
switch (envMode) {
|
||||
case FragmentKey.TEX_ENV_REPLACE -> {
|
||||
sb.append(" // Color combine (REPLACE)\n");
|
||||
sb.append(" vec4 color = texColor;\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_ADD -> {
|
||||
sb.append(" // Color combine (ADD)\n");
|
||||
sb.append(" vec4 color = vec4(clamp(v_Color.rgb + texColor.rgb, 0.0, 1.0), v_Color.a * texColor.a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_DECAL -> {
|
||||
sb.append(" // Color combine (DECAL)\n");
|
||||
sb.append(" vec4 color = vec4(mix(v_Color.rgb, texColor.rgb, texColor.a), v_Color.a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_BLEND -> {
|
||||
sb.append(" // Color combine (BLEND)\n");
|
||||
sb.append(" vec4 color = vec4(v_Color.rgb * (vec3(1.0) - texColor.rgb) + u_TexEnvColor.rgb * texColor.rgb, v_Color.a * texColor.a);\n");
|
||||
}
|
||||
default -> {
|
||||
sb.append(" // Color combine (MODULATE)\n");
|
||||
sb.append(" vec4 color = v_Color * texColor;\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" color *= lightmapColor;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" // Add separate specular\n");
|
||||
sb.append(" color.rgb += v_SpecularColor;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.alphaTestEnabled()) return;
|
||||
|
||||
sb.append(" // Alpha test\n");
|
||||
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
|
||||
|
||||
switch (func) {
|
||||
case GL11.GL_NEVER ->
|
||||
sb.append(" discard;\n");
|
||||
case GL11.GL_LESS ->
|
||||
sb.append(" if (color.a >= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_EQUAL ->
|
||||
sb.append(" if (color.a != u_AlphaRef) discard;\n");
|
||||
case GL11.GL_LEQUAL ->
|
||||
sb.append(" if (color.a > u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GREATER ->
|
||||
sb.append(" if (color.a <= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_NOTEQUAL ->
|
||||
sb.append(" if (color.a == u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GEQUAL ->
|
||||
sb.append(" if (color.a < u_AlphaRef) discard;\n");
|
||||
case GL11.GL_ALWAYS -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitFog(StringBuilder sb, FragmentKey key) {
|
||||
final int fogMode = key.fogMode();
|
||||
if (fogMode == FragmentKey.FOG_NONE) return;
|
||||
|
||||
sb.append(" // Fog\n");
|
||||
sb.append(" float f;\n");
|
||||
|
||||
switch (fogMode) {
|
||||
case FragmentKey.FOG_LINEAR ->
|
||||
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
|
||||
case FragmentKey.FOG_EXP ->
|
||||
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
|
||||
case FragmentKey.FOG_EXP2 -> {
|
||||
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
|
||||
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" f = clamp(f, 0.0, 1.0);\n");
|
||||
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import lombok.Getter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
|
||||
/**
|
||||
* A compiled and linked FFP emulation program (vertex + fragment shader pair). Owns the GL program handle and caches uniform locations.
|
||||
*/
|
||||
public class Program {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPProgram");
|
||||
|
||||
@Getter private final int programId;
|
||||
@Getter private final VertexKey vertexKey;
|
||||
@Getter private final FragmentKey fragmentKey;
|
||||
|
||||
// Uniform locations (-1 = not present in this variant)
|
||||
// Matrices
|
||||
public int locModelViewMatrix = -1;
|
||||
public int locProjectionMatrix = -1;
|
||||
public int locMVPMatrix = -1;
|
||||
public int locNormalMatrix = -1;
|
||||
public int locTextureMatrix0 = -1;
|
||||
|
||||
// Current state defaults
|
||||
public int locCurrentNormal = -1;
|
||||
public int locCurrentColor = -1;
|
||||
public int locCurrentTexCoord = -1;
|
||||
public int locCurrentLightmapCoord = -1;
|
||||
public int locLightmapTextureMatrix = -1;
|
||||
|
||||
// Lighting - color material path (raw values)
|
||||
public int locLightModelAmbient = -1;
|
||||
public int locMaterialEmission = -1;
|
||||
public int locMaterialAmbient = -1;
|
||||
public int locMaterialDiffuse = -1;
|
||||
public int locMaterialSpecular = -1;
|
||||
public int locMaterialShininess = -1;
|
||||
public int locLight0Ambient = -1;
|
||||
public int locLight0Diffuse = -1;
|
||||
public int locLight0Specular = -1;
|
||||
public int locLight1Ambient = -1;
|
||||
public int locLight1Diffuse = -1;
|
||||
public int locLight1Specular = -1;
|
||||
|
||||
// Lighting - pre-computed path
|
||||
public int locSceneColor = -1;
|
||||
public int locLightProd0Ambient = -1;
|
||||
public int locLightProd0Diffuse = -1;
|
||||
public int locLightProd0Specular = -1;
|
||||
public int locLightProd1Ambient = -1;
|
||||
public int locLightProd1Diffuse = -1;
|
||||
public int locLightProd1Specular = -1;
|
||||
|
||||
// Light positions
|
||||
public int locLight0Position = -1;
|
||||
public int locLight1Position = -1;
|
||||
|
||||
// Normal scale (for rescale normals)
|
||||
public int locNormalScale = -1;
|
||||
|
||||
// TexGen plane uniforms
|
||||
public int locTexGenObjPlaneS = -1;
|
||||
public int locTexGenObjPlaneT = -1;
|
||||
public int locTexGenObjPlaneR = -1;
|
||||
public int locTexGenObjPlaneQ = -1;
|
||||
public int locTexGenEyePlaneS = -1;
|
||||
public int locTexGenEyePlaneT = -1;
|
||||
public int locTexGenEyePlaneR = -1;
|
||||
public int locTexGenEyePlaneQ = -1;
|
||||
|
||||
// Clip planes
|
||||
public int locClipPlanes = -1;
|
||||
|
||||
// Fragment uniforms
|
||||
public int locSampler0 = -1;
|
||||
public int locSampler1 = -1;
|
||||
public int locAlphaRef = -1;
|
||||
public int locTexEnvColor = -1;
|
||||
public int locFogParams = -1;
|
||||
public int locFogColor = -1;
|
||||
|
||||
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
|
||||
this.programId = programId;
|
||||
this.vertexKey = vertexKey;
|
||||
this.fragmentKey = fragmentKey;
|
||||
resolveLocations();
|
||||
}
|
||||
|
||||
private void resolveLocations() {
|
||||
locModelViewMatrix = loc("u_ModelViewMatrix");
|
||||
locProjectionMatrix = loc("u_ProjectionMatrix");
|
||||
locMVPMatrix = loc("u_MVPMatrix");
|
||||
locNormalMatrix = loc("u_NormalMatrix");
|
||||
locTextureMatrix0 = loc("u_TextureMatrix0");
|
||||
|
||||
locCurrentNormal = loc("u_CurrentNormal");
|
||||
locCurrentColor = loc("u_CurrentColor");
|
||||
locCurrentTexCoord = loc("u_CurrentTexCoord");
|
||||
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
|
||||
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
|
||||
|
||||
// Color material path
|
||||
locLightModelAmbient = loc("u_LightModelAmbient");
|
||||
locMaterialEmission = loc("u_MaterialEmission");
|
||||
locMaterialAmbient = loc("u_MaterialAmbient");
|
||||
locMaterialDiffuse = loc("u_MaterialDiffuse");
|
||||
locMaterialSpecular = loc("u_MaterialSpecular");
|
||||
locMaterialShininess = loc("u_MaterialShininess");
|
||||
locLight0Ambient = loc("u_Light0Ambient");
|
||||
locLight0Diffuse = loc("u_Light0Diffuse");
|
||||
locLight0Specular = loc("u_Light0Specular");
|
||||
locLight1Ambient = loc("u_Light1Ambient");
|
||||
locLight1Diffuse = loc("u_Light1Diffuse");
|
||||
locLight1Specular = loc("u_Light1Specular");
|
||||
|
||||
// Pre-computed path
|
||||
locSceneColor = loc("u_SceneColor");
|
||||
locLightProd0Ambient = loc("u_LightProd0Ambient");
|
||||
locLightProd0Diffuse = loc("u_LightProd0Diffuse");
|
||||
locLightProd0Specular = loc("u_LightProd0Specular");
|
||||
locLightProd1Ambient = loc("u_LightProd1Ambient");
|
||||
locLightProd1Diffuse = loc("u_LightProd1Diffuse");
|
||||
locLightProd1Specular = loc("u_LightProd1Specular");
|
||||
|
||||
// Light positions
|
||||
locLight0Position = loc("u_Light0Position");
|
||||
locLight1Position = loc("u_Light1Position");
|
||||
|
||||
locNormalScale = loc("u_NormalScale");
|
||||
|
||||
// TexGen
|
||||
locTexGenObjPlaneS = loc("u_TexGenObjPlaneS");
|
||||
locTexGenObjPlaneT = loc("u_TexGenObjPlaneT");
|
||||
locTexGenObjPlaneR = loc("u_TexGenObjPlaneR");
|
||||
locTexGenObjPlaneQ = loc("u_TexGenObjPlaneQ");
|
||||
locTexGenEyePlaneS = loc("u_TexGenEyePlaneS");
|
||||
locTexGenEyePlaneT = loc("u_TexGenEyePlaneT");
|
||||
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
|
||||
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
|
||||
|
||||
// Clip planes
|
||||
locClipPlanes = loc("u_ClipPlane[0]");
|
||||
|
||||
// Fragment
|
||||
locSampler0 = loc("u_Sampler0");
|
||||
locSampler1 = loc("u_Sampler1");
|
||||
locAlphaRef = loc("u_AlphaRef");
|
||||
locTexEnvColor = loc("u_TexEnvColor");
|
||||
locFogParams = loc("u_FogParams");
|
||||
locFogColor = loc("u_FogColor");
|
||||
}
|
||||
|
||||
private int loc(String name) {
|
||||
return GL20.glGetUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
GL20.glDeleteProgram(programId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a shader, link it into a program, and return the FFPProgram.
|
||||
*/
|
||||
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc) {
|
||||
final int vs = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + Long.toHexString(vk.pack()));
|
||||
final int fs = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + Long.toHexString(fk.pack()));
|
||||
|
||||
final int program = GL20.glCreateProgram();
|
||||
GL20.glAttachShader(program, vs);
|
||||
GL20.glAttachShader(program, fs);
|
||||
GL20.glLinkProgram(program);
|
||||
|
||||
final String log = RenderSystem.getProgramInfoLog(program);
|
||||
if (!log.isEmpty()) {
|
||||
LOGGER.warn("FFP program link log (vk=0x{}, fk=0x{}): {}", Long.toHexString(vk.pack()), Long.toHexString(fk.pack()), log);
|
||||
}
|
||||
|
||||
final int linkStatus = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
|
||||
if (linkStatus != GL11.GL_TRUE) {
|
||||
GL20.glDeleteProgram(program);
|
||||
GL20.glDeleteShader(vs);
|
||||
GL20.glDeleteShader(fs);
|
||||
throw new RuntimeException("FFP shader link failed (vk=0x" + Long.toHexString(vk.pack()) + ", fk=0x" + Long.toHexString(fk.pack()) + "): " + log);
|
||||
}
|
||||
|
||||
final String debugName = "FFP(v=0x" + Long.toHexString(vk.pack()) + ",f=0x" + Long.toHexString(fk.pack()) + ")";
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
|
||||
|
||||
// Detach and delete individual shaders — they're linked into the program
|
||||
GL20.glDetachShader(program, vs);
|
||||
GL20.glDetachShader(program, fs);
|
||||
GL20.glDeleteShader(vs);
|
||||
GL20.glDeleteShader(fs);
|
||||
|
||||
final Program ffpProgram = new Program(program, vk, fk);
|
||||
|
||||
final int previousProgram = GLStateManager.getActiveProgram();
|
||||
GL20.glUseProgram(program);
|
||||
if (ffpProgram.locSampler0 != -1) GL20.glUniform1i(ffpProgram.locSampler0, 0);
|
||||
if (ffpProgram.locSampler1 != -1) GL20.glUniform1i(ffpProgram.locSampler1, 1);
|
||||
GL20.glUseProgram(previousProgram);
|
||||
|
||||
return ffpProgram;
|
||||
}
|
||||
|
||||
private static int compileShader(int type, String src, String name) {
|
||||
final int shader = GL20.glCreateShader(type);
|
||||
GL20.glShaderSource(shader, src);
|
||||
GL20.glCompileShader(shader);
|
||||
|
||||
final String typeName = (type == GL20.GL_VERTEX_SHADER) ? "vertex" : "fragment";
|
||||
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
|
||||
|
||||
final String log = RenderSystem.getShaderInfoLog(shader);
|
||||
if (!log.isEmpty()) {
|
||||
LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
|
||||
}
|
||||
|
||||
final int result = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
|
||||
if (result != GL11.GL_TRUE) {
|
||||
LOGGER.error("FFP {} shader source:\n{}", typeName, src);
|
||||
GL20.glDeleteShader(shader);
|
||||
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Setter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Cache for FFP shader program variants. Keyed on (vertexKey, fragmentKey) pairs.
|
||||
*/
|
||||
public class ShaderCache {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPShaderCache");
|
||||
|
||||
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<String> fragmentSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<Program> programCache = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private long lastCombinedKey = Long.MIN_VALUE;
|
||||
private Program lastProgram = null;
|
||||
|
||||
@Setter private Path dumpDir = null;
|
||||
|
||||
/**
|
||||
* Get or create an FFP program for the given packed state keys.
|
||||
*/
|
||||
public Program getOrCreate(long vkPacked, long fkPacked) {
|
||||
final long combinedKey = (fkPacked << 32) | (vkPacked & 0xFFFFFFFFL);
|
||||
|
||||
if (combinedKey == lastCombinedKey && lastProgram != null) {
|
||||
return lastProgram;
|
||||
}
|
||||
|
||||
Program program = programCache.get(combinedKey);
|
||||
if (program != null) {
|
||||
lastCombinedKey = combinedKey;
|
||||
lastProgram = program;
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
final FragmentKey fk = FragmentKey.fromPacked(fkPacked);
|
||||
|
||||
final String vertSrc = getOrGenerateVertex(vk);
|
||||
final String fragSrc = getOrGenerateFragment(fk);
|
||||
|
||||
program = Program.create(vk, fk, vertSrc, fragSrc);
|
||||
programCache.put(combinedKey, program);
|
||||
lastCombinedKey = combinedKey;
|
||||
lastProgram = program;
|
||||
|
||||
LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, programCache.size());
|
||||
|
||||
// Debug dump
|
||||
if (dumpDir != null) {
|
||||
dumpShader(vkPacked, ".vert.glsl", vertSrc);
|
||||
dumpShader(fkPacked, ".frag.glsl", fragSrc);
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private String getOrGenerateVertex(VertexKey vk) {
|
||||
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private String getOrGenerateFragment(FragmentKey fk) {
|
||||
return fragmentSourceCache.computeIfAbsent(fk.pack(), k -> FragmentShaderGenerator.generate(fk));
|
||||
}
|
||||
|
||||
private void dumpShader(long key, String suffix, String source) {
|
||||
if (dumpDir == null) return;
|
||||
try {
|
||||
Files.createDirectories(dumpDir);
|
||||
final Path file = dumpDir.resolve("ffp_" + Long.toHexString(key) + suffix);
|
||||
if (!Files.exists(file)) {
|
||||
Files.writeString(file, source, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all cached shaders to disk. Useful for debugging.
|
||||
*/
|
||||
public void dumpAll(Path outputDir) {
|
||||
try {
|
||||
Files.createDirectories(outputDir);
|
||||
for (var entry : vertexSourceCache.entrySet()) {
|
||||
dumpShader(entry.getKey(), ".vert.glsl", entry.getValue());
|
||||
}
|
||||
for (var entry : fragmentSourceCache.entrySet()) {
|
||||
dumpShader(entry.getKey(), ".frag.glsl", entry.getValue());
|
||||
}
|
||||
LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}", vertexSourceCache.size(), fragmentSourceCache.size(), outputDir);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all cached programs and clear caches.
|
||||
*/
|
||||
public void destroy() {
|
||||
for (Program program : programCache.values()) {
|
||||
program.destroy();
|
||||
}
|
||||
programCache.clear();
|
||||
vertexSourceCache.clear();
|
||||
fragmentSourceCache.clear();
|
||||
lastProgram = null;
|
||||
lastCombinedKey = Long.MIN_VALUE;
|
||||
LOGGER.debug("FFP shader cache cleared");
|
||||
}
|
||||
|
||||
public int getProgramCount() {
|
||||
return programCache.size();
|
||||
}
|
||||
|
||||
public int getVertexVariantCount() {
|
||||
return vertexSourceCache.size();
|
||||
}
|
||||
|
||||
public int getFragmentVariantCount() {
|
||||
return fragmentSourceCache.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
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.stacks.Vec3fStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.gl.blending.BlendModeStorage;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* FFP shader manager
|
||||
*/
|
||||
public class ShaderManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPShaderManager");
|
||||
|
||||
private static final class Holder { static final ShaderManager INSTANCE = new ShaderManager(); }
|
||||
|
||||
private final ShaderCache cache = new ShaderCache();
|
||||
private final Uniforms uniforms = new Uniforms();
|
||||
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
|
||||
|
||||
@Getter
|
||||
private boolean active = false;
|
||||
private Program currentProgram = null;
|
||||
private long currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
private long currentFragmentKeyPacked = Long.MIN_VALUE;
|
||||
|
||||
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
|
||||
@Getter private static int normalGeneration;
|
||||
@Getter private static int texCoordGeneration;
|
||||
@Getter private boolean enabled = false;
|
||||
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
private ShaderManager() {
|
||||
vaoVertexFlags.defaultReturnValue(-1);
|
||||
boolean isDev = false;
|
||||
try {
|
||||
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
|
||||
isDev = Boolean.TRUE.equals(deobfEnv);
|
||||
} catch (Exception ignored) {}
|
||||
if (isDev || Boolean.parseBoolean(System.getProperty("angelica.ffp.dumpShaders", "false"))) {
|
||||
cache.setDumpDir(Paths.get("ffp_shaders"));
|
||||
}
|
||||
}
|
||||
|
||||
public static ShaderManager getInstance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
|
||||
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
|
||||
currentVertexFlags = format.getVertexFlags();
|
||||
final int boundVao = GLStateManager.getBoundVAO();
|
||||
if (boundVao != 0) {
|
||||
vaoVertexFlags.put(boundVao, currentVertexFlags);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
LOGGER.info("FFP shader emulation enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
active = true;
|
||||
updateVariant(true, true, true, true);
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
currentFragmentKeyPacked = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
GLStateManager.flushDeferredVertexAttribs();
|
||||
BlendModeStorage.flushDeferredBlend();
|
||||
|
||||
if (!active) {
|
||||
if (enabled) {
|
||||
final int currentProgramId = GLStateManager.getActiveProgram();
|
||||
if (currentProgramId != 0) {
|
||||
CompatUniformManager.onUseProgram(currentProgramId);
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final long fkPacked = FragmentKey.packFromState();
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || fkPacked != currentFragmentKeyPacked) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
currentFragmentKeyPacked = fkPacked;
|
||||
|
||||
currentProgram = cache.getOrCreate(vkPacked, fkPacked);
|
||||
GL20.glUseProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void preDraw(int vertexFlags) {
|
||||
currentVertexFlags = vertexFlags;
|
||||
preDraw(
|
||||
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
|
||||
);
|
||||
}
|
||||
|
||||
public void preDraw() {
|
||||
preDraw(currentVertexFlags);
|
||||
}
|
||||
|
||||
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
currentVertexKeyPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
currentFragmentKeyPacked = FragmentKey.packFromState();
|
||||
|
||||
currentProgram = cache.getOrCreate(currentVertexKeyPacked, currentFragmentKeyPacked);
|
||||
GL20.glUseProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
private void uploadUniforms() {
|
||||
if (currentProgram != null) {
|
||||
uniforms.upload(currentProgram);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCurrentNormal(float x, float y, float z) {
|
||||
currentNormal.set(x, y, z);
|
||||
normalGeneration++;
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(float s, float t, float r, float q) {
|
||||
currentTexCoord.set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
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();
|
||||
TessellatorStreamingDrawer.destroy();
|
||||
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
}
|
||||
|
||||
public String getDebugInfo() {
|
||||
return String.format("FFP: %d programs (%d vert, %d frag variants)",
|
||||
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.QuadConverter;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
/**
|
||||
* Replaces the vanilla Tessellator's FFP client-array draw path with a streaming VBO+VAO approach for GL 3.3 core profile compatibility.
|
||||
* <p>
|
||||
* Uses a persistent-mapped ring buffer on GL4.4+ hardware (zero-copy uploads with fence sync),
|
||||
* falling back to the classic orphan pattern on older hardware or on overflow.
|
||||
* Maintains two VAO sets (persistent + orphan) per vertex format (up to 16 combinations).
|
||||
*/
|
||||
public class TessellatorStreamingDrawer {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
|
||||
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
|
||||
|
||||
private static PersistentStreamingBuffer persistentBuffer;
|
||||
private static final OrphanStreamingBuffer[] orphanBuffers = new OrphanStreamingBuffer[FORMAT_COUNT];
|
||||
|
||||
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
|
||||
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
private static ByteBuffer repackBuffer;
|
||||
private static long repackAddress;
|
||||
private static int repackCapacity;
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
// Initial repack buffer: 64KB
|
||||
repackCapacity = 0x10000;
|
||||
repackBuffer = memAlloc(repackCapacity);
|
||||
repackAddress = memAddress0(repackBuffer);
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
if (RenderSystem.supportsBufferStorage()) {
|
||||
try {
|
||||
persistentBuffer = new PersistentStreamingBuffer();
|
||||
LOGGER.info("Persistent streaming buffer created ({}MB)", PersistentStreamingBuffer.DEFAULT_CAPACITY / (1024 * 1024));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to create persistent streaming buffer, using orphan fallback", e);
|
||||
persistentBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the vanilla Tessellator's data using streaming VBO+VAO instead of FFP client arrays.
|
||||
*/
|
||||
public static int draw(Tessellator tess) {
|
||||
if (!tess.isDrawing) {
|
||||
throw new IllegalStateException("Not tesselating!");
|
||||
}
|
||||
|
||||
tess.isDrawing = false;
|
||||
|
||||
final int vertexCount = tess.vertexCount;
|
||||
if (vertexCount == 0) {
|
||||
final int result = tess.rawBufferIndex * 4;
|
||||
tess.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Determine the optimal vertex format from the tessellator's flags
|
||||
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
|
||||
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
final int requiredBytes = vertexCount * vertexSize;
|
||||
ensureRepackCapacity(requiredBytes);
|
||||
|
||||
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit((int)(writePtr - repackAddress));
|
||||
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(repackBuffer, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(repackBuffer, vertexSize);
|
||||
firstVertex = 0;
|
||||
}
|
||||
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(tess.drawMode, firstVertex, vertexCount);
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
// Shrink rawBuffer if oversized
|
||||
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
|
||||
tess.rawBufferSize = 0x10000;
|
||||
tess.rawBuffer = new int[tess.rawBufferSize];
|
||||
}
|
||||
|
||||
final int result = tess.rawBufferIndex * 4;
|
||||
tess.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw DirectTessellator data via streaming VBO+VAO. Used for live immediate mode emulation.
|
||||
*/
|
||||
public static void drawDirect(DirectTessellator dt) {
|
||||
final VertexFormat format = dt.getVertexFormat();
|
||||
if (format == null) return;
|
||||
|
||||
final int vertexCount = dt.getVertexCount();
|
||||
if (vertexCount == 0) return;
|
||||
|
||||
final int drawMode = dt.getDrawMode();
|
||||
final int flags = format.getVertexFlags();
|
||||
final ByteBuffer buffer = dt.getWriteBuffer();
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(buffer, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(buffer, vertexSize);
|
||||
firstVertex = 0;
|
||||
}
|
||||
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static String getDebugInfo() {
|
||||
if (!initialized) return "Stream: not initialized";
|
||||
|
||||
int orphanCount = 0;
|
||||
int orphanBytes = 0;
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (orphanBuffers[i] != null) {
|
||||
orphanCount++;
|
||||
orphanBytes += orphanBuffers[i].getCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
return String.format("Stream: Persistent %s (%s free) + %d orphan (%s)",
|
||||
formatBytes(persistentBuffer.getCapacity()), formatBytes(persistentBuffer.getRemaining()),
|
||||
orphanCount, formatBytes(orphanBytes));
|
||||
}
|
||||
return String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes));
|
||||
}
|
||||
|
||||
private static String formatBytes(int bytes) {
|
||||
if (bytes >= 1024 * 1024) return String.format("%dMB", bytes / (1024 * 1024));
|
||||
if (bytes >= 1024) return String.format("%dKB", bytes / 1024);
|
||||
return bytes + "B";
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.postDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
|
||||
if (drawMode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
|
||||
} else {
|
||||
GL11.glDrawArrays(drawMode, firstVertex, vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureRepackCapacity(int requiredBytes) {
|
||||
if (requiredBytes <= repackCapacity) return;
|
||||
|
||||
int newCapacity = repackCapacity;
|
||||
while (newCapacity < requiredBytes) {
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
memFree(repackBuffer);
|
||||
repackBuffer = memAlloc(newCapacity);
|
||||
repackAddress = memAddress0(repackBuffer);
|
||||
repackCapacity = newCapacity;
|
||||
}
|
||||
|
||||
private static void ensureVAO(int flags, VertexFormat format) {
|
||||
init();
|
||||
|
||||
if (orphanVAOs[flags] == 0) {
|
||||
orphanBuffers[flags] = new OrphanStreamingBuffer();
|
||||
|
||||
orphanVAOs[flags] = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
|
||||
format.setupBufferState(0L);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
if (persistentBuffer != null && persistentVAOs[flags] == 0) {
|
||||
persistentVAOs[flags] = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
|
||||
format.setupBufferState(0L);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all VAOs, streaming buffers, and the repack buffer.
|
||||
*/
|
||||
public static void destroy() {
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
|
||||
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
|
||||
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
|
||||
}
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.destroy();
|
||||
persistentBuffer = null;
|
||||
}
|
||||
if (repackBuffer != null) {
|
||||
memFree(repackBuffer);
|
||||
repackBuffer = null;
|
||||
repackAddress = 0;
|
||||
repackCapacity = 0;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
/**
|
||||
* Uploads GLSM cached state to the active FFP program's uniforms.
|
||||
*
|
||||
* Follows Mesa's prog_statevars.c patterns:
|
||||
* - Pre-computed derived state (light products, scene color) when material is static
|
||||
* - Raw light/material values when color material is active
|
||||
* - Optimized fog params (pre-divided for single MAD in shader)
|
||||
*/
|
||||
public class Uniforms {
|
||||
// Reusable temp buffers for uniform upload
|
||||
private final FloatBuffer mat4Buf = memAllocFloat(16);
|
||||
private final FloatBuffer mat3Buf = memAllocFloat(9);
|
||||
private final FloatBuffer vec4Buf = memAllocFloat(4);
|
||||
private final FloatBuffer vec3Buf = memAllocFloat(3);
|
||||
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
|
||||
|
||||
// Derived matrices (computed on CPU)
|
||||
private final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private final Matrix3f normalMatrix = new Matrix3f();
|
||||
|
||||
// Temp vectors for light product and normal scale computation
|
||||
private final Vector4f tempVec4 = new Vector4f();
|
||||
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
|
||||
|
||||
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
|
||||
private static final double LN2 = Math.log(2.0);
|
||||
private static final double SQRT_LN2 = Math.sqrt(LN2);
|
||||
|
||||
// Dirty tracking: last-uploaded generation per category + program ID.
|
||||
// Program change forces full re-upload since uniform locations differ.
|
||||
private int lastProgramId = -1;
|
||||
private int lastMvGen = -1;
|
||||
private int lastProjGen = -1;
|
||||
private int lastTexMatGen = -1;
|
||||
private int lastLightingGen = -1;
|
||||
private int lastFragmentGen = -1;
|
||||
private int lastColorGen = -1;
|
||||
private int lastNormalGen = -1;
|
||||
private int lastTexCoordGen = -1;
|
||||
private float lastLightmapX = Float.NaN;
|
||||
private float lastLightmapY = Float.NaN;
|
||||
private int lastTexGenGen = -1;
|
||||
private int lastClipPlaneGen = -1;
|
||||
|
||||
/**
|
||||
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
|
||||
* Uses generation counters to skip categories that haven't changed since last upload.
|
||||
*/
|
||||
public void upload(Program program) {
|
||||
final boolean programChanged = program.getProgramId() != lastProgramId;
|
||||
lastProgramId = program.getProgramId();
|
||||
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightingEnabled()) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
uploadLighting(program);
|
||||
lastLightingGen = litGen;
|
||||
}
|
||||
}
|
||||
|
||||
// Current color/normal/texcoord — skip if generation unchanged
|
||||
if (!program.getVertexKey().hasVertexColor()) {
|
||||
final int colorGen = GLStateManager.colorGeneration;
|
||||
if (programChanged || colorGen != lastColorGen) {
|
||||
uploadCurrentColor(program);
|
||||
lastColorGen = colorGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
|
||||
final int normalGen = ShaderManager.getNormalGeneration();
|
||||
if (programChanged || normalGen != lastNormalGen) {
|
||||
uploadCurrentNormal(program);
|
||||
lastNormalGen = normalGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
|
||||
final int texGen = ShaderManager.getTexCoordGeneration();
|
||||
if (programChanged || texGen != lastTexCoordGen) {
|
||||
uploadCurrentTexCoord(program);
|
||||
lastTexCoordGen = texGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
|
||||
if (programChanged || OpenGlHelper.lastBrightnessX != lastLightmapX || OpenGlHelper.lastBrightnessY != lastLightmapY) {
|
||||
uploadCurrentLightmapCoord(program);
|
||||
lastLightmapX = OpenGlHelper.lastBrightnessX;
|
||||
lastLightmapY = OpenGlHelper.lastBrightnessY;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().texGenEnabled()) {
|
||||
final int tgGen = GLStateManager.texGenGeneration;
|
||||
if (programChanged || tgGen != lastTexGenGen) {
|
||||
uploadTexGen(program);
|
||||
lastTexGenGen = tgGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().clipPlanesEnabled()) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
uploadClipPlanes(program);
|
||||
lastClipPlaneGen = cpGen;
|
||||
}
|
||||
}
|
||||
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
uploadFragmentUniforms(program);
|
||||
lastFragmentGen = fragGen;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
// ModelView + derived (normal matrix, normal scale)
|
||||
if (mvChanged) {
|
||||
if (program.locModelViewMatrix != -1) {
|
||||
mv.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
|
||||
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
}
|
||||
if (program.locNormalMatrix != -1) {
|
||||
normalMatrix.get(mat3Buf);
|
||||
GL20.glUniformMatrix3(program.locNormalMatrix, false, mat3Buf);
|
||||
}
|
||||
|
||||
// Normal scale (for GL_RESCALE_NORMAL without GL_NORMALIZE)
|
||||
if (program.locNormalScale != -1) {
|
||||
// Scale factor = 1/length of first column of normal matrix
|
||||
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
|
||||
GL20.glUniform1f(program.locNormalScale, scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection
|
||||
if (projChanged) {
|
||||
if (program.locProjectionMatrix != -1) {
|
||||
proj.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// MVP = Projection * ModelView — needs recompute if either input changed
|
||||
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
|
||||
proj.mul(mv, mvpMatrix);
|
||||
mvpMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locMVPMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrices
|
||||
if (texMatChanged) {
|
||||
// Texture matrix unit 0
|
||||
if (program.locTextureMatrix0 != -1) {
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
texMat.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrix unit 1 (lightmap)
|
||||
if (program.locLightmapTextureMatrix != -1) {
|
||||
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
|
||||
lmTexMat.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadLighting(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
|
||||
if (vk.colorMaterialEnabled()) {
|
||||
uploadLightingColorMaterial(program, vk);
|
||||
} else {
|
||||
uploadLightingPreComputed(program, vk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Color material active: upload raw light and material values.
|
||||
* The shader multiplies them with vertex color at runtime.
|
||||
*/
|
||||
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
|
||||
uploadVec4(program.locMaterialEmission, mat.emission);
|
||||
uploadVec4(program.locMaterialAmbient, mat.ambient);
|
||||
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
|
||||
uploadVec4(program.locMaterialSpecular, mat.specular);
|
||||
if (program.locMaterialShininess != -1) {
|
||||
GL20.glUniform1f(program.locMaterialShininess, mat.shininess);
|
||||
}
|
||||
|
||||
if (vk.light0Enabled()) {
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
uploadVec4(program.locLight0Ambient, light0.ambient);
|
||||
uploadVec4(program.locLight0Diffuse, light0.diffuse);
|
||||
uploadVec4(program.locLight0Specular, light0.specular);
|
||||
uploadVec4(program.locLight0Position, light0.position);
|
||||
}
|
||||
if (vk.light1Enabled()) {
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
uploadVec4(program.locLight1Ambient, light1.ambient);
|
||||
uploadVec4(program.locLight1Diffuse, light1.diffuse);
|
||||
uploadVec4(program.locLight1Specular, light1.specular);
|
||||
uploadVec4(program.locLight1Position, light1.position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No color material: upload pre-computed light products and scene color.
|
||||
* Fewer shader multiplies since material is static.
|
||||
*/
|
||||
private void uploadLightingPreComputed(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
// Scene color = emission + ambient * lightModel.ambient
|
||||
// Alpha = material.diffuse.a
|
||||
if (program.locSceneColor != -1) {
|
||||
tempVec4.set(
|
||||
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
|
||||
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
|
||||
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
|
||||
mat.diffuse.w // alpha from diffuse
|
||||
);
|
||||
uploadVec4(program.locSceneColor, tempVec4);
|
||||
}
|
||||
|
||||
if (program.locMaterialShininess != -1) {
|
||||
GL20.glUniform1f(program.locMaterialShininess, mat.shininess);
|
||||
}
|
||||
|
||||
// Per-light products
|
||||
if (vk.light0Enabled()) {
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
uploadVec4(program.locLight0Position, light0.position);
|
||||
uploadLightProduct(program.locLightProd0Ambient, light0.ambient, mat.ambient);
|
||||
uploadLightProduct(program.locLightProd0Diffuse, light0.diffuse, mat.diffuse);
|
||||
uploadLightProduct(program.locLightProd0Specular, light0.specular, mat.specular);
|
||||
}
|
||||
if (vk.light1Enabled()) {
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
uploadVec4(program.locLight1Position, light1.position);
|
||||
uploadLightProduct(program.locLightProd1Ambient, light1.ambient, mat.ambient);
|
||||
uploadLightProduct(program.locLightProd1Diffuse, light1.diffuse, mat.diffuse);
|
||||
uploadLightProduct(program.locLightProd1Specular, light1.specular, mat.specular);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload pre-multiplied light product: light.X * material.X (RGB only).
|
||||
*/
|
||||
private void uploadLightProduct(int loc, Vector4f lightVal, Vector4f materialVal) {
|
||||
if (loc == -1) return;
|
||||
vec3Buf.clear();
|
||||
vec3Buf.put(lightVal.x * materialVal.x);
|
||||
vec3Buf.put(lightVal.y * materialVal.y);
|
||||
vec3Buf.put(lightVal.z * materialVal.z);
|
||||
vec3Buf.flip();
|
||||
GL20.glUniform3(loc, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentColor(Program program) {
|
||||
if (program.locCurrentColor == -1) return;
|
||||
// Upload the current color from GLSM
|
||||
final var color = GLStateManager.getColor();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(color.getRed());
|
||||
vec4Buf.put(color.getGreen());
|
||||
vec4Buf.put(color.getBlue());
|
||||
vec4Buf.put(color.getAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locCurrentColor, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentTexCoord(Program program) {
|
||||
if (program.locCurrentTexCoord == -1) return;
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
uploadVec4(program.locCurrentTexCoord, tc);
|
||||
}
|
||||
|
||||
private void uploadCurrentLightmapCoord(Program program) {
|
||||
if (program.locCurrentLightmapCoord == -1) return;
|
||||
GL20.glUniform2f(program.locCurrentLightmapCoord,
|
||||
OpenGlHelper.lastBrightnessX, OpenGlHelper.lastBrightnessY);
|
||||
}
|
||||
|
||||
private void uploadCurrentNormal(Program program) {
|
||||
if (program.locCurrentNormal == -1) return;
|
||||
// Upload current normal from FFPShaderManager's tracked normal
|
||||
final var normal = ShaderManager.getCurrentNormal();
|
||||
vec3Buf.clear();
|
||||
vec3Buf.put(normal.x);
|
||||
vec3Buf.put(normal.y);
|
||||
vec3Buf.put(normal.z);
|
||||
vec3Buf.flip();
|
||||
GL20.glUniform3(program.locCurrentNormal, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadTexGen(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
|
||||
|
||||
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
}
|
||||
|
||||
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
}
|
||||
|
||||
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
}
|
||||
|
||||
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadPlane(int loc, float[] plane) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(plane[0]);
|
||||
vec4Buf.put(plane[1]);
|
||||
vec4Buf.put(plane[2]);
|
||||
vec4Buf.put(plane[3]);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadClipPlanes(Program program) {
|
||||
if (program.locClipPlanes == -1) return;
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
GL20.glUniform4(program.locClipPlanes, clipPlaneBuf);
|
||||
}
|
||||
|
||||
private void uploadFragmentUniforms(Program program) {
|
||||
final FragmentKey fk = program.getFragmentKey();
|
||||
|
||||
// Alpha test reference
|
||||
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
|
||||
GL20.glUniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
|
||||
}
|
||||
|
||||
// Tex env color (only used by GL_BLEND mode)
|
||||
if (program.locTexEnvColor != -1) {
|
||||
final var envColor = GLStateManager.getTexEnvColor();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(envColor.getRed());
|
||||
vec4Buf.put(envColor.getGreen());
|
||||
vec4Buf.put(envColor.getBlue());
|
||||
vec4Buf.put(envColor.getAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locTexEnvColor, vec4Buf);
|
||||
}
|
||||
|
||||
// Fog
|
||||
if (fk.fogMode() != FragmentKey.FOG_NONE) {
|
||||
uploadFog(program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
|
||||
*/
|
||||
private void uploadFog(Program program) {
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
|
||||
if (program.locFogParams != -1) {
|
||||
final float start = fog.getStart();
|
||||
final float end = fog.getEnd();
|
||||
final float density = fog.getDensity();
|
||||
final float range = end - start;
|
||||
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
|
||||
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
|
||||
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
|
||||
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locFogParams, vec4Buf);
|
||||
}
|
||||
|
||||
if (program.locFogColor != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x);
|
||||
vec4Buf.put((float) fog.getFogColor().y);
|
||||
vec4Buf.put((float) fog.getFogColor().z);
|
||||
vec4Buf.put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locFogColor, vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadVec4(int loc, Vector4f v) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(v.x);
|
||||
vec4Buf.put(v.y);
|
||||
vec4Buf.put(v.z);
|
||||
vec4Buf.put(v.w);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
memFree(mat4Buf);
|
||||
memFree(mat3Buf);
|
||||
memFree(vec4Buf);
|
||||
memFree(vec3Buf);
|
||||
memFree(clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
/**
|
||||
* Compact key capturing the vertex shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*/
|
||||
public final class VertexKey {
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_LIGHTING = 0;
|
||||
private static final int BIT_LIGHT0 = 1;
|
||||
private static final int BIT_LIGHT1 = 2;
|
||||
private static final int BIT_LIGHT0_DIR = 3;
|
||||
private static final int BIT_LIGHT1_DIR = 4;
|
||||
private static final int BIT_COLOR_MATERIAL = 5;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_FOG = 7;
|
||||
private static final int BIT_FOG_DIST_MODE = 8;
|
||||
private static final int BIT_NORMALIZE = 10;
|
||||
private static final int BIT_RESCALE_NORMAL = 11;
|
||||
private static final int BIT_TEXTURE = 12;
|
||||
private static final int BIT_LIGHTMAP = 13;
|
||||
private static final int BIT_TEX_MATRIX = 14;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 15;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 16;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 17;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
|
||||
private static final int BIT_COLOR_MAT_MODE = 19;
|
||||
// bits 19-21: colorMaterialMode (3 bits)
|
||||
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
|
||||
private static final int BIT_TEXGEN_S = 22;
|
||||
private static final int BIT_TEXGEN_T = 25;
|
||||
private static final int BIT_TEXGEN_R = 28;
|
||||
private static final int BIT_TEXGEN_Q = 31;
|
||||
// bits 31-33: texGenModeQ (3 bits)
|
||||
private static final int BIT_CLIP_PLANES = 34;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
public static final int TG_EYE_LINEAR = 2;
|
||||
|
||||
public static final int CM_AMBIENT = 0;
|
||||
public static final int CM_DIFFUSE = 1;
|
||||
public static final int CM_SPECULAR = 2;
|
||||
public static final int CM_EMISSION = 3;
|
||||
public static final int CM_AMBIENT_AND_DIFFUSE = 4;
|
||||
|
||||
private VertexKey(long packed) {
|
||||
this.packed = packed;
|
||||
}
|
||||
|
||||
public long pack() { return packed; }
|
||||
|
||||
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
|
||||
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
|
||||
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
|
||||
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
|
||||
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
|
||||
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
|
||||
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
|
||||
public boolean fogEnabled() { return bit(BIT_FOG); }
|
||||
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
|
||||
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
|
||||
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
|
||||
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
|
||||
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
|
||||
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
|
||||
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
|
||||
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
|
||||
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
|
||||
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
|
||||
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
|
||||
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
|
||||
|
||||
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
|
||||
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
|
||||
/** TexGen mode for T coordinate. */
|
||||
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
|
||||
/** TexGen mode for R coordinate. */
|
||||
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
|
||||
/** TexGen mode for Q coordinate. */
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
/** Whether any texgen coordinate is enabled. */
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
/** Whether any clip plane is enabled. */
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
|
||||
/** Whether vertex color replaces material ambient in this variant. */
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material diffuse in this variant. */
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material specular in this variant. */
|
||||
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
|
||||
/** Whether vertex color replaces material emission in this variant. */
|
||||
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
|
||||
|
||||
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
|
||||
|
||||
static int encodeTexGenMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_OBJECT_LINEAR -> TG_OBJ_LINEAR;
|
||||
case GL11.GL_EYE_LINEAR -> TG_EYE_LINEAR;
|
||||
default -> TG_NONE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeColorMaterialMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_AMBIENT -> CM_AMBIENT;
|
||||
case GL11.GL_DIFFUSE -> CM_DIFFUSE;
|
||||
case GL11.GL_SPECULAR -> CM_SPECULAR;
|
||||
case GL11.GL_EMISSION -> CM_EMISSION;
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> CM_AMBIENT_AND_DIFFUSE;
|
||||
default -> CM_AMBIENT_AND_DIFFUSE;
|
||||
};
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
long bits = 0;
|
||||
|
||||
final boolean lighting = GLStateManager.getLightingState().isEnabled();
|
||||
if (lighting) {
|
||||
bits |= (1L << BIT_LIGHTING);
|
||||
|
||||
if (GLStateManager.getLightStates()[0].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT0);
|
||||
// position.w == 0 means directional
|
||||
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT0_DIR);
|
||||
}
|
||||
}
|
||||
if (GLStateManager.getLightStates()[1].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT1);
|
||||
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT1_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getColorMaterial().isEnabled()) {
|
||||
bits |= (1L << BIT_COLOR_MATERIAL);
|
||||
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
|
||||
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
bits |= (1L << BIT_FOG);
|
||||
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
|
||||
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
bits |= (1L << BIT_NORMALIZE);
|
||||
}
|
||||
if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
bits |= (1L << BIT_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
// Texture unit 0 (base texture)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
}
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
}
|
||||
|
||||
// Check if texture matrix unit 0 is non-identity
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
if (!DisplayListManager.isIdentity(texMat)) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
|
||||
final var texUnit = GLStateManager.getTextures();
|
||||
final TexGenState tg = texUnit.getTexGenState(0);
|
||||
boolean anyTexGen = false;
|
||||
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_S));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_S;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenTStates(0).isEnabled() && tg.getMode(GL11.GL_T) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_T));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_T;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenRStates(0).isEnabled() && tg.getMode(GL11.GL_R) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_R));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_R;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenQStates(0).isEnabled() && tg.getMode(GL11.GL_Q) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_Q));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
// Force texture matrix on when texgen is active (end portal pattern always uses it)
|
||||
if (anyTexGen) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// Vertex format flags
|
||||
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
|
||||
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
|
||||
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
|
||||
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
|
||||
|
||||
// Clip planes
|
||||
if (GLStateManager.anyClipPlaneEnabled()) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
|
||||
}
|
||||
|
||||
static VertexKey fromPacked(long packed) {
|
||||
return new VertexKey(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof VertexKey other)) return false;
|
||||
return packed == other.packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b]",
|
||||
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
|
||||
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
|
||||
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
|
||||
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core vertex shaders for FFP emulation.
|
||||
* Follows Mesa's ffvertex_prog.c build order.
|
||||
*
|
||||
* Attribute locations are sourced from {@link VertexFormatElement.Usage} to stay in sync with GTNHLib's vertex format setup.
|
||||
*/
|
||||
public final class VertexShaderGenerator {
|
||||
|
||||
private VertexShaderGenerator() {}
|
||||
|
||||
public static String generate(VertexKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitAttributes(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
emitOutputs(sb, key);
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitPositionTransform(sb, key);
|
||||
if (key.lightingEnabled()) {
|
||||
emitNormalTransform(sb, key);
|
||||
emitLighting(sb, key);
|
||||
} else {
|
||||
emitColorPassthrough(sb, key);
|
||||
}
|
||||
emitTexCoordPassthrough(sb, key);
|
||||
if (key.fogEnabled()) {
|
||||
emitFogDistance(sb, key);
|
||||
}
|
||||
if (key.clipPlanesEnabled()) {
|
||||
emitClipDistances(sb);
|
||||
}
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitAttributes(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
|
||||
}
|
||||
if (key.hasVertexTexCoord()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
}
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
|
||||
}
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Matrices\n");
|
||||
sb.append("uniform mat4 u_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 u_ProjectionMatrix;\n");
|
||||
sb.append("uniform mat4 u_MVPMatrix;\n");
|
||||
|
||||
if (key.lightingEnabled()) {
|
||||
sb.append("uniform mat3 u_NormalMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append("uniform mat4 u_TextureMatrix0;\n");
|
||||
}
|
||||
|
||||
// TexGen plane uniforms
|
||||
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
|
||||
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
|
||||
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
|
||||
if (key.texGenModeT() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneT;\n");
|
||||
if (key.texGenModeR() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneR;\n");
|
||||
if (key.texGenModeR() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneR;\n");
|
||||
if (key.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneQ;\n");
|
||||
if (key.texGenModeQ() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneQ;\n");
|
||||
|
||||
if (!key.hasVertexNormal() && key.lightingEnabled()) {
|
||||
sb.append("uniform vec3 u_CurrentNormal;\n");
|
||||
}
|
||||
|
||||
if (!key.hasVertexColor()) {
|
||||
sb.append("uniform vec4 u_CurrentColor;\n");
|
||||
}
|
||||
|
||||
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
|
||||
sb.append("uniform vec4 u_CurrentTexCoord;\n");
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
|
||||
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("uniform mat4 u_LightmapTextureMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.lightingEnabled()) {
|
||||
emitLightingUniforms(sb, key);
|
||||
}
|
||||
|
||||
if (key.lightingEnabled() && (key.normalizeEnabled() || key.rescaleNormalsEnabled())) {
|
||||
// normalize is done in shader; rescale uses normalScale
|
||||
if (key.rescaleNormalsEnabled() && !key.normalizeEnabled()) {
|
||||
sb.append("uniform float u_NormalScale;\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (key.clipPlanesEnabled()) {
|
||||
sb.append("uniform vec4 u_ClipPlane[8];\n");
|
||||
}
|
||||
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("\n// Lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// When color material is active, we can't pre-compute light products so we upload raw light and material values
|
||||
sb.append("uniform vec4 u_LightModelAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialEmission;\n");
|
||||
sb.append("uniform vec4 u_MaterialAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialDiffuse;\n");
|
||||
sb.append("uniform vec4 u_MaterialSpecular;\n");
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light0Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light0Specular;\n");
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light1Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light1Specular;\n");
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
}
|
||||
} else {
|
||||
// Pre-computed: sceneColor + lightProducts
|
||||
sb.append("uniform vec4 u_SceneColor;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Specular;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Specular;\n");
|
||||
}
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitOutputs(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Outputs\n");
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Position transform\n");
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
|
||||
|
||||
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
|
||||
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
|
||||
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Normal transform\n");
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
|
||||
} else {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n");
|
||||
}
|
||||
|
||||
if (key.normalizeEnabled()) {
|
||||
sb.append(" normal = normalize(normal);\n");
|
||||
} else if (key.rescaleNormalsEnabled()) {
|
||||
sb.append(" normal *= u_NormalScale;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLighting(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Gouraud lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
|
||||
sb.append(" vec4 matColor = ");
|
||||
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
|
||||
sb.append(";\n");
|
||||
|
||||
// Emission source: vertex color for CM_EMISSION, else material uniform
|
||||
final String emSrc = key.cmReplacesEmission() ? "matColor.rgb" : "u_MaterialEmission.rgb";
|
||||
// Ambient source for scene color: vertex color for CM_AMBIENT / CM_AMBIENT_AND_DIFFUSE
|
||||
final String amSrc = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 sceneColor = ").append(emSrc).append(" + ").append(amSrc).append(" * u_LightModelAmbient.rgb;\n");
|
||||
|
||||
// Alpha from diffuse source (per GL spec)
|
||||
sb.append(" float outAlpha = ").append(key.cmReplacesDiffuse() ? "matColor.a" : "u_MaterialDiffuse.a").append(";\n");
|
||||
} else {
|
||||
// Pre-computed scene color
|
||||
sb.append(" vec3 sceneColor = u_SceneColor.rgb;\n");
|
||||
sb.append(" float outAlpha = u_SceneColor.a;\n");
|
||||
}
|
||||
|
||||
sb.append(" vec3 color0 = sceneColor;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" vec3 specAccum = vec3(0.0);\n");
|
||||
}
|
||||
|
||||
// Per-light accumulation
|
||||
if (key.light0Enabled()) {
|
||||
emitLightContribution(sb, key, 0);
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
emitLightContribution(sb, key, 1);
|
||||
}
|
||||
|
||||
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = clamp(specAccum, 0.0, 1.0);\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
|
||||
final String li = String.valueOf(lightIndex);
|
||||
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
|
||||
final String posUniform = "u_Light" + li + "Position";
|
||||
|
||||
sb.append(" { // Light ").append(li).append('\n');
|
||||
|
||||
// Light vector
|
||||
if (directional) {
|
||||
// Directional: position is already normalized direction
|
||||
sb.append(" vec3 VPpli = normalize(").append(posUniform).append(".xyz);\n");
|
||||
} else {
|
||||
// Positional light
|
||||
sb.append(" vec3 VPpli = ").append(posUniform).append(".xyz - eyePos.xyz;\n");
|
||||
sb.append(" VPpli = normalize(VPpli);\n");
|
||||
}
|
||||
|
||||
// Dot products
|
||||
sb.append(" float NdotVP = max(dot(normal, VPpli), 0.0);\n");
|
||||
|
||||
// Half vector for specular (infinite viewer: H = normalize(L + (0,0,1)))
|
||||
sb.append(" vec3 halfVec = normalize(VPpli + vec3(0.0, 0.0, 1.0));\n");
|
||||
sb.append(" float NdotHV = max(dot(normal, halfVec), 0.0);\n");
|
||||
|
||||
// Specular power
|
||||
sb.append(" float spec = (NdotVP > 0.0 && u_MaterialShininess > 0.0) ? pow(NdotHV, u_MaterialShininess) : 0.0;\n");
|
||||
|
||||
// Accumulate — per-property source depends on color material mode
|
||||
if (key.colorMaterialEnabled()) {
|
||||
final String ambU = "u_Light" + li + "Ambient";
|
||||
final String difU = "u_Light" + li + "Diffuse";
|
||||
final String speU = "u_Light" + li + "Specular";
|
||||
// Ambient product: light.ambient * (vertex color or material)
|
||||
final String ambMat = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 ambient = ").append(ambU).append(".rgb * ").append(ambMat).append(";\n");
|
||||
// Diffuse product
|
||||
final String difMat = key.cmReplacesDiffuse() ? "matColor.rgb" : "u_MaterialDiffuse.rgb";
|
||||
sb.append(" vec3 diffuse = ").append(difU).append(".rgb * ").append(difMat).append(";\n");
|
||||
// Specular product
|
||||
final String speMat = key.cmReplacesSpecular() ? "matColor.rgb" : "u_MaterialSpecular.rgb";
|
||||
sb.append(" vec3 specular = ").append(speU).append(".rgb * ").append(speMat).append(";\n");
|
||||
} else {
|
||||
sb.append(" vec3 ambient = u_LightProd").append(li).append("Ambient;\n");
|
||||
sb.append(" vec3 diffuse = u_LightProd").append(li).append("Diffuse;\n");
|
||||
sb.append(" vec3 specular = u_LightProd").append(li).append("Specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" color0 += ambient + NdotVP * diffuse;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" specAccum += spec * specular;\n");
|
||||
} else {
|
||||
sb.append(" color0 += spec * specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" }\n");
|
||||
}
|
||||
|
||||
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Color passthrough (no lighting)\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append(" v_Color = a_Color;\n");
|
||||
} else {
|
||||
sb.append(" v_Color = u_CurrentColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
|
||||
if (key.texGenEnabled()) {
|
||||
emitTexGenCoordGeneration(sb, key);
|
||||
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
|
||||
sb.append(" // Texture coordinates\n");
|
||||
if (key.hasVertexTexCoord()) {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
|
||||
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
|
||||
* Coordinates without texgen use vertex attrib or default (0,0,0,1).
|
||||
*/
|
||||
private static void emitTexGenCoordGeneration(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // TexGen coordinate generation\n");
|
||||
sb.append(" vec4 texGenCoord = vec4(0.0, 0.0, 0.0, 1.0);\n");
|
||||
|
||||
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
|
||||
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
|
||||
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
|
||||
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
|
||||
|
||||
// Always apply texture matrix when texgen is active (forced on in VertexKey)
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
|
||||
}
|
||||
|
||||
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
|
||||
switch (mode) {
|
||||
case VertexKey.TG_OBJ_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(pos4, u_TexGenObjPlane").append(coordName).append(");\n");
|
||||
case VertexKey.TG_EYE_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(eyePos, u_TexGenEyePlane").append(coordName).append(");\n");
|
||||
// TG_NONE: keep default (0 for s/t/r, 1 for q) — already set in texGenCoord init
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean texGenNeedsEyePos(VertexKey key) {
|
||||
return key.texGenModeS() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeT() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeR() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeQ() == VertexKey.TG_EYE_LINEAR;
|
||||
}
|
||||
|
||||
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Fog distance\n");
|
||||
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
|
||||
final int fogDistMode = key.fogDistanceMode();
|
||||
switch (fogDistMode) {
|
||||
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
|
||||
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
|
||||
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
|
||||
sb.append(" v_FogCoord = eyePos.z;\n");
|
||||
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
default ->
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitClipDistances(StringBuilder sb) {
|
||||
sb.append(" // Clip distances\n");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,6 +263,15 @@ public final class CommandBuffer {
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeScissor(int x, int y, int width, int height) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.SCISSOR);
|
||||
writeInt(x);
|
||||
writeInt(y);
|
||||
writeInt(width);
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.BLEND_FUNC);
|
||||
@@ -545,6 +554,16 @@ public final class CommandBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeClipPlane(int plane, double a, double b, double c, double d) {
|
||||
ensureCapacity(40);
|
||||
writeInt(GLCommand.CLIP_PLANE);
|
||||
writeInt(plane);
|
||||
writeDouble(a);
|
||||
writeDouble(b);
|
||||
writeDouble(c);
|
||||
writeDouble(d);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
|
||||
public void writeDrawRange(int vboIndex) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -25,6 +26,8 @@ public final class CommandBufferExecutor {
|
||||
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
// Static reusable buffer for fog/light/material params
|
||||
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
|
||||
// Static reusable buffer for clip plane equation
|
||||
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(4);
|
||||
// Static reusable buffer for draw buffers
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
|
||||
@@ -196,6 +199,14 @@ public final class CommandBufferExecutor {
|
||||
ptr += 16;
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
case GLCommand.SCISSOR -> {
|
||||
final int x = memGetInt(ptr);
|
||||
final int y = memGetInt(ptr + 4);
|
||||
final int width = memGetInt(ptr + 8);
|
||||
final int height = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glScissor(x, y, width, height);
|
||||
}
|
||||
case GLCommand.BLEND_FUNC -> {
|
||||
final int srcRgb = memGetInt(ptr);
|
||||
final int dstRgb = memGetInt(ptr + 4);
|
||||
@@ -449,6 +460,19 @@ public final class CommandBufferExecutor {
|
||||
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
|
||||
}
|
||||
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = memGetInt(ptr);
|
||||
final double a = memGetDouble(ptr + 4);
|
||||
final double b = memGetDouble(ptr + 12);
|
||||
final double c = memGetDouble(ptr + 20);
|
||||
final double d = memGetDouble(ptr + 28);
|
||||
ptr += 36;
|
||||
PARAMS_DOUBLE_BUFFER.clear();
|
||||
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
|
||||
PARAMS_DOUBLE_BUFFER.flip();
|
||||
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
case GLCommand.DRAW_RANGE -> {
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
@@ -519,7 +543,7 @@ public final class CommandBufferExecutor {
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.BIND_VAO -> {
|
||||
UniversalVAO.bindVertexArray(memGetInt(ptr));
|
||||
GLStateManager.glBindVertexArray(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ public final class CommandBufferProcessor {
|
||||
|
||||
// Four int commands
|
||||
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.SCISSOR -> out.writeScissor(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
|
||||
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
@@ -142,6 +143,16 @@ public final class CommandBufferProcessor {
|
||||
out.writeMaterial(face, pname, fb);
|
||||
}
|
||||
|
||||
// Clip plane command (4 doubles)
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = raw.readInt();
|
||||
final double a = raw.readDouble();
|
||||
final double b = raw.readDouble();
|
||||
final double c = raw.readDouble();
|
||||
final double d = raw.readDouble();
|
||||
out.writeClipPlane(plane, a, b, c, d);
|
||||
}
|
||||
|
||||
// Draw/call commands
|
||||
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
|
||||
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
|
||||
|
||||
@@ -172,6 +172,11 @@ public final class CommandRecorder {
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordScissor(int x, int y, int width, int height) {
|
||||
buffer.writeScissor(x, y, width, height);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPointSize(float size) {
|
||||
buffer.writePointSize(size);
|
||||
commandCount++;
|
||||
@@ -287,6 +292,11 @@ public final class CommandRecorder {
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClipPlane(int plane, double a, double b, double c, double d) {
|
||||
buffer.writeClipPlane(plane, a, b, c, d);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilFunc(int func, int ref, int mask) {
|
||||
buffer.writeStencilFunc(func, ref, mask);
|
||||
commandCount++;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
/**
|
||||
* A class that stores multiple vertex formats & their corresponding buffers.
|
||||
|
||||
@@ -14,7 +14,11 @@ import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
|
||||
|
||||
/**
|
||||
* Compiles multiple ByteBuffers of the same Vertex format into a VBO. Any buffer added to the builder will be freed afterward.
|
||||
@@ -69,7 +73,7 @@ public final class DisplayListVBOBuilder {
|
||||
vertexCount = format.getVertexCount(size);
|
||||
}
|
||||
if (data.drawMode == GL11.GL_QUADS) {
|
||||
IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
|
||||
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
|
||||
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6);
|
||||
} else {
|
||||
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
|
||||
|
||||
@@ -51,6 +51,7 @@ public final class GLCommand {
|
||||
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
|
||||
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
|
||||
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
|
||||
public static final int SCISSOR = 45; // [cmd:4][x:4][y:4][width:4][height:4]
|
||||
|
||||
// === Float commands ===
|
||||
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
|
||||
@@ -88,6 +89,7 @@ public final class GLCommand {
|
||||
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
|
||||
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
|
||||
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
|
||||
public static final int CLIP_PLANE = 94; // [cmd:4][plane:4][a:8d][b:8d][c:8d][d:8d] = 40 bytes
|
||||
|
||||
// === Draw commands ===
|
||||
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4] = 8 bytes
|
||||
@@ -147,6 +149,7 @@ public final class GLCommand {
|
||||
case STENCIL_OP -> "STENCIL_OP";
|
||||
case TEX_PARAMETERI -> "TEX_PARAMETERI";
|
||||
case VIEWPORT -> "VIEWPORT";
|
||||
case SCISSOR -> "SCISSOR";
|
||||
case BLEND_FUNC -> "BLEND_FUNC";
|
||||
case COLOR_MASK -> "COLOR_MASK";
|
||||
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
|
||||
@@ -177,6 +180,7 @@ public final class GLCommand {
|
||||
case LIGHT -> "LIGHT";
|
||||
case LIGHT_MODEL -> "LIGHT_MODEL";
|
||||
case MATERIAL -> "MATERIAL";
|
||||
case CLIP_PLANE -> "CLIP_PLANE";
|
||||
case DRAW_RANGE -> "DRAW_RANGE";
|
||||
case CALL_LIST -> "CALL_LIST";
|
||||
case DRAW_BUFFER -> "DRAW_BUFFER";
|
||||
@@ -220,6 +224,7 @@ public final class GLCommand {
|
||||
// Four int commands (20 bytes)
|
||||
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
|
||||
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
|
||||
GLCommand.SCISSOR,
|
||||
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
|
||||
|
||||
case GLCommand.DRAW_ELEMENTS -> 24;
|
||||
@@ -251,6 +256,7 @@ public final class GLCommand {
|
||||
final int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
|
||||
yield 16 + count * 4;
|
||||
}
|
||||
case GLCommand.CLIP_PLANE -> 40; // cmd + plane + 4 doubles
|
||||
|
||||
default -> throw new IllegalStateException("Unknown command: " + cmd);
|
||||
};
|
||||
|
||||
+128
-49
@@ -4,8 +4,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClientArrayState;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.Buffer;
|
||||
@@ -16,28 +14,49 @@ import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) during display list compilation.
|
||||
* Converts immediate mode geometry to ByteBuffer directly via {@link DirectTessellator}
|
||||
* <p>
|
||||
* Currently only used for display list compilation, should be perfectly usable outside of display lists too though.
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
|
||||
* Used both during display list compilation and for live immediate mode emulation in core profile.
|
||||
*/
|
||||
public final class ImmediateModeRecorder {
|
||||
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/** Separate tessellator for the splash thread. Nulled after splash completes. */
|
||||
private static DirectTessellator splashTessellator;
|
||||
|
||||
private ImmediateModeRecorder() {
|
||||
|
||||
}
|
||||
|
||||
public static DirectTessellator getInternalTessellator() {
|
||||
public static void initSplashTessellator() {
|
||||
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public static void destroySplashTessellator() {
|
||||
splashTessellator = null;
|
||||
}
|
||||
|
||||
private static DirectTessellator tessellator() {
|
||||
final DirectTessellator splash = splashTessellator;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
return splash;
|
||||
}
|
||||
return tessellator;
|
||||
}
|
||||
|
||||
public static DirectTessellator getInternalTessellator() {
|
||||
return tessellator();
|
||||
}
|
||||
|
||||
public static boolean isDrawing() {
|
||||
return tessellator().isDrawing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current texture coordinate.
|
||||
* Called by GLStateManager.glTexCoord*.
|
||||
*/
|
||||
public static void setTexCoord(float s, float t) {
|
||||
tessellator.setTextureUV(s, t);
|
||||
tessellator().setTextureUV(s, t);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,14 +64,39 @@ public final class ImmediateModeRecorder {
|
||||
* Called by GLStateManager.glNormal*.
|
||||
*/
|
||||
public static void setNormal(float x, float y, float z) {
|
||||
tessellator.setNormal(x, y, z);
|
||||
tessellator().setNormal(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current color on the internal tessellator.
|
||||
* Called by GLStateManager.glColor* when inside a glBegin/glEnd block.
|
||||
*/
|
||||
public static void setColor(float r, float g, float b, float a) {
|
||||
tessellator().setColorRGBA((int) (r * 255.0f), (int) (g * 255.0f), (int) (b * 255.0f), (int) (a * 255.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording a new primitive.
|
||||
* Resets the tessellator to clear any data from the previous draw.
|
||||
*
|
||||
* @param primitiveType GL primitive type (GL_QUADS, GL_TRIANGLES, etc.)
|
||||
*/
|
||||
public static void begin(int primitiveType) {
|
||||
tessellator.startDrawing(primitiveType);
|
||||
final DirectTessellator t = tessellator();
|
||||
t.reset();
|
||||
t.startDrawing(primitiveType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording a new primitive with initial color sync from GLSM cache.
|
||||
* Used for live immediate mode emulation only (not display list recording).
|
||||
*/
|
||||
public static void beginLive(int primitiveType) {
|
||||
final DirectTessellator t = tessellator();
|
||||
t.reset();
|
||||
t.startDrawing(primitiveType);
|
||||
final var c = GLStateManager.getColor();
|
||||
t.setColorRGBA((int) (c.getRed() * 255.0f), (int) (c.getGreen() * 255.0f), (int) (c.getBlue() * 255.0f), (int) (c.getAlpha() * 255.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,15 +106,16 @@ public final class ImmediateModeRecorder {
|
||||
* @return Result containing quads, lines, and flags, or null if no geometry was produced
|
||||
*/
|
||||
public static DirectTessellator end() {
|
||||
if (!tessellator.isDrawing) {
|
||||
final DirectTessellator t = tessellator();
|
||||
if (!t.isDrawing) {
|
||||
throw new IllegalStateException("glEnd called without glBegin");
|
||||
}
|
||||
|
||||
tessellator.isDrawing = false;
|
||||
t.isDrawing = false;
|
||||
|
||||
if (tessellator.isEmpty()) return null;
|
||||
if (t.isEmpty()) return null;
|
||||
|
||||
return tessellator;
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,10 +123,11 @@ public final class ImmediateModeRecorder {
|
||||
* Color is read from GLStateManager at call time.
|
||||
*/
|
||||
public static void vertex(float x, float y, float z) {
|
||||
if (!tessellator.isDrawing) {
|
||||
final DirectTessellator t = tessellator();
|
||||
if (!t.isDrawing) {
|
||||
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
|
||||
}
|
||||
tessellator.addVertex(x, y, z);
|
||||
t.addVertex(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +138,7 @@ public final class ImmediateModeRecorder {
|
||||
* where primitives might fail to convert (e.g., incomplete quads).
|
||||
*/
|
||||
public static boolean hasGeometry() {
|
||||
return tessellator.vertexCount != 0;
|
||||
return tessellator().vertexCount != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +146,7 @@ public final class ImmediateModeRecorder {
|
||||
* Called when starting a new display list.
|
||||
*/
|
||||
public static void reset() {
|
||||
tessellator.reset();
|
||||
tessellator().reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,13 +162,19 @@ public final class ImmediateModeRecorder {
|
||||
final ClientArrayState cas = GLStateManager.getClientArrayState();
|
||||
final Buffer vertexPointer = cas.isVertexArrayEnabled() ? cas.getVertexPointer() : null;
|
||||
final Buffer colorPointer = cas.isColorArrayEnabled() ? cas.getColorPointer() : null;
|
||||
final Buffer normalPointer = cas.isNormalArrayEnabled() ? cas.getNormalPointer() : null;
|
||||
final Buffer texCoordPointer = cas.isTexCoordArrayEnabled() ? cas.getTexCoordPointer() : null;
|
||||
|
||||
return convertClientArrays(
|
||||
mode, first, count,
|
||||
vertexPointer,
|
||||
cas.getVertexPointerType(), cas.getVertexPointerSize(), cas.getVertexPointerStride(),
|
||||
colorPointer,
|
||||
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride()
|
||||
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride(),
|
||||
normalPointer,
|
||||
cas.getNormalPointerType(), cas.getNormalPointerStride(),
|
||||
texCoordPointer,
|
||||
cas.getTexCoordPointerType(), cas.getTexCoordPointerSize(), cas.getTexCoordPointerStride()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,33 +182,23 @@ public final class ImmediateModeRecorder {
|
||||
* Convert client array data to immediate mode.
|
||||
* Called by glDrawArrays during display list recording.
|
||||
* Like Mesa's save_DrawArrays, reads vertex data from arrays and emits as immediate mode.
|
||||
*
|
||||
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
|
||||
* @param first Starting index in arrays
|
||||
* @param count Number of vertices
|
||||
* @param vertexPointer Vertex position buffer (or null if not enabled)
|
||||
* @param vertexType GL type (GL_FLOAT, GL_DOUBLE, etc.)
|
||||
* @param vertexSize Size of vertex (2, 3, or 4)
|
||||
* @param vertexStride Stride in bytes (0 for tightly packed)
|
||||
* @param colorPointer Color buffer (or null if not enabled)
|
||||
* @param colorType GL type (GL_FLOAT, GL_UNSIGNED_BYTE, etc.)
|
||||
* @param colorSize Size of color (3 or 4)
|
||||
* @param colorStride Stride in bytes (0 for tightly packed)
|
||||
* @return Result containing converted geometry, or null if no geometry produced
|
||||
*/
|
||||
public static DirectTessellator convertClientArrays(
|
||||
int mode, int first, int count,
|
||||
java.nio.Buffer vertexPointer, int vertexType, int vertexSize, int vertexStride,
|
||||
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride
|
||||
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride,
|
||||
java.nio.Buffer normalPointer, int normalType, int normalStride,
|
||||
java.nio.Buffer texCoordPointer, int texCoordType, int texCoordSize, int texCoordStride
|
||||
) {
|
||||
begin(mode);
|
||||
final DirectTessellator t = tessellator();
|
||||
|
||||
// Hoist type checks and stride calculations outside the loop for performance.
|
||||
// Mesa's algorithm: offset_bytes = index * effective_stride
|
||||
// effective_stride = (stride == 0) ? (numComponents * sizeof(element)) : stride
|
||||
|
||||
// Determine color reader (hoisted out of loop)
|
||||
final ColorReader colorReader;
|
||||
// Determine color reader
|
||||
final ArrayReader colorReader;
|
||||
if (colorPointer == null) {
|
||||
colorReader = null;
|
||||
} else if (colorType == GL11.GL_FLOAT && colorPointer instanceof FloatBuffer fb) {
|
||||
@@ -185,8 +227,46 @@ public final class ImmediateModeRecorder {
|
||||
colorReader = (idx) -> GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Determine vertex reader (hoisted out of loop)
|
||||
final VertexReader vertexReader;
|
||||
// Determine normal reader
|
||||
final ArrayReader normalReader;
|
||||
if (normalPointer == null) {
|
||||
normalReader = null;
|
||||
} else if (normalType == GL11.GL_FLOAT && normalPointer instanceof FloatBuffer fb) {
|
||||
final int effectiveStride = (normalStride == 0) ? 3 * 4 : normalStride;
|
||||
final int strideInFloats = effectiveStride / 4;
|
||||
normalReader = (idx) -> {
|
||||
final int offset = idx * strideInFloats;
|
||||
setNormal(fb.get(offset), fb.get(offset + 1), fb.get(offset + 2));
|
||||
};
|
||||
} else if (normalType == GL11.GL_BYTE && normalPointer instanceof ByteBuffer bb) {
|
||||
final int effectiveStride = (normalStride == 0) ? 3 : normalStride;
|
||||
normalReader = (idx) -> {
|
||||
final int offset = idx * effectiveStride;
|
||||
setNormal(bb.get(offset) / 127.0f, bb.get(offset + 1) / 127.0f, bb.get(offset + 2) / 127.0f);
|
||||
};
|
||||
} else {
|
||||
normalReader = null;
|
||||
}
|
||||
|
||||
// Determine texcoord reader
|
||||
final ArrayReader texCoordReader;
|
||||
if (texCoordPointer == null) {
|
||||
texCoordReader = null;
|
||||
} else if (texCoordType == GL11.GL_FLOAT && texCoordPointer instanceof FloatBuffer fb) {
|
||||
final int effectiveStride = (texCoordStride == 0) ? texCoordSize * 4 : texCoordStride;
|
||||
final int strideInFloats = effectiveStride / 4;
|
||||
texCoordReader = (idx) -> {
|
||||
final int offset = idx * strideInFloats;
|
||||
final float s = fb.get(offset);
|
||||
final float tt = (texCoordSize >= 2) ? fb.get(offset + 1) : 0.0f;
|
||||
setTexCoord(s, tt);
|
||||
};
|
||||
} else {
|
||||
texCoordReader = null;
|
||||
}
|
||||
|
||||
// Determine vertex reader
|
||||
final ArrayReader vertexReader;
|
||||
if (vertexPointer == null) {
|
||||
vertexReader = null;
|
||||
} else if (vertexType == GL11.GL_FLOAT && vertexPointer instanceof FloatBuffer fb) {
|
||||
@@ -197,7 +277,7 @@ public final class ImmediateModeRecorder {
|
||||
final float x = fb.get(offset);
|
||||
final float y = fb.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? fb.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
t.addVertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_DOUBLE && vertexPointer instanceof DoubleBuffer db) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 8 : vertexStride;
|
||||
@@ -207,7 +287,7 @@ public final class ImmediateModeRecorder {
|
||||
final float x = (float) db.get(offset);
|
||||
final float y = (float) db.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? (float) db.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
t.addVertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_INT && vertexPointer instanceof IntBuffer ib) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
|
||||
@@ -217,7 +297,7 @@ public final class ImmediateModeRecorder {
|
||||
final float x = ib.get(offset);
|
||||
final float y = ib.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? ib.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
t.addVertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_SHORT && vertexPointer instanceof ShortBuffer sb) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 2 : vertexStride;
|
||||
@@ -227,7 +307,7 @@ public final class ImmediateModeRecorder {
|
||||
final float x = sb.get(offset);
|
||||
final float y = sb.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? sb.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
t.addVertex(x, y, z);
|
||||
};
|
||||
} else {
|
||||
// Unsupported vertex type
|
||||
@@ -236,6 +316,12 @@ public final class ImmediateModeRecorder {
|
||||
|
||||
// Main loop - no type checks, just function calls
|
||||
for (int i = first; i < first + count; i++) {
|
||||
if (normalReader != null) {
|
||||
normalReader.read(i);
|
||||
}
|
||||
if (texCoordReader != null) {
|
||||
texCoordReader.read(i);
|
||||
}
|
||||
if (colorReader != null) {
|
||||
colorReader.read(i);
|
||||
}
|
||||
@@ -247,15 +333,8 @@ public final class ImmediateModeRecorder {
|
||||
return end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ColorReader {
|
||||
void read(int index);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface VertexReader {
|
||||
private interface ArrayReader {
|
||||
void read(int index);
|
||||
}
|
||||
}
|
||||
+32
-14
@@ -11,6 +11,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
|
||||
@@ -26,18 +27,35 @@ public record TexImage2DCmd(
|
||||
int border,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
byte @Nullable [] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from ShortBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromShortBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ShortBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 2; // 2 bytes per short
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asShortBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -52,9 +70,9 @@ public record TexImage2DCmd(
|
||||
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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -69,9 +87,9 @@ public record TexImage2DCmd(
|
||||
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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -86,9 +104,9 @@ public record TexImage2DCmd(
|
||||
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;
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
@@ -109,7 +127,7 @@ public record TexImage2DCmd(
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
final 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)",
|
||||
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
|
||||
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
|
||||
+32
-14
@@ -11,6 +11,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
|
||||
@@ -26,18 +27,35 @@ public record TexSubImage2DCmd(
|
||||
int height,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
byte @Nullable [] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from ShortBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromShortBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ShortBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 2; // 2 bytes per short
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asShortBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -52,9 +70,9 @@ public record TexSubImage2DCmd(
|
||||
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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -69,9 +87,9 @@ public record TexSubImage2DCmd(
|
||||
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
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final 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);
|
||||
@@ -86,9 +104,9 @@ public record TexSubImage2DCmd(
|
||||
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;
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
@@ -109,7 +127,7 @@ public record TexSubImage2DCmd(
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
final 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)",
|
||||
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
|
||||
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
|
||||
@@ -22,8 +22,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
protected int savedDepth;
|
||||
|
||||
public BooleanStateStack(int glCap) {
|
||||
// Most booleans default to false
|
||||
this(glCap, false);
|
||||
this(glCap, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +33,18 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
* @param initialState initial enabled state
|
||||
*/
|
||||
public BooleanStateStack(int glCap, boolean initialState) {
|
||||
super(glCap);
|
||||
this(glCap, initialState, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BooleanStateStack with custom initial state and FFP flag.
|
||||
*
|
||||
* @param glCap GL capability constant
|
||||
* @param initialState initial enabled state
|
||||
* @param ffpStateOnly when true, GL calls are skipped when FFP emulation is active
|
||||
*/
|
||||
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
|
||||
super(glCap, ffpStateOnly);
|
||||
this.enabled = initialState;
|
||||
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
|
||||
+9
-7
@@ -12,12 +12,12 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
private final int unitIndex;
|
||||
|
||||
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
|
||||
super(glCap);
|
||||
super(glCap, false, true); // All texture enable/disable states are FFP-only
|
||||
this.unitIndex = unitIndex;
|
||||
}
|
||||
|
||||
public TextureUnitBooleanStateStack(int glCap, int unitIndex, boolean initialState) {
|
||||
super(glCap, initialState);
|
||||
super(glCap, initialState, true);
|
||||
this.unitIndex = unitIndex;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,14 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
if (bypass || enabled != this.enabled) {
|
||||
if (!bypass) this.enabled = enabled;
|
||||
|
||||
int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
boolean needsSwitch = currentUnit != unitIndex;
|
||||
if (!ffpStateOnly) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
final boolean needsSwitch = currentUnit != unitIndex;
|
||||
|
||||
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + unitIndex);
|
||||
if (enabled) GL11.glEnable(glCap); else GL11.glDisable(glCap);
|
||||
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + unitIndex);
|
||||
if (enabled) GL11.glEnable(glCap); else GL11.glDisable(glCap);
|
||||
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector3f value (e.g. current normal).
|
||||
*/
|
||||
public class Vec3fStack implements IStateStack<Vec3fStack> {
|
||||
|
||||
private final Vector3f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
|
||||
public Vec3fStack(Vector3f value) {
|
||||
this.value = value;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
pointer++;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector4f value (e.g. current texture coordinates).
|
||||
*/
|
||||
public class Vec4fStack implements IStateStack<Vec4fStack> {
|
||||
|
||||
private final Vector4f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
|
||||
public Vec4fStack(Vector4f value) {
|
||||
this.value = value;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
stack[pointer][3] = value.w;
|
||||
pointer++;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL11;
|
||||
@Getter
|
||||
public class AlphaState implements ISettableState<AlphaState> {
|
||||
@Setter protected int function = GL11.GL_ALWAYS;
|
||||
@Setter protected float reference = -1.0F;
|
||||
@Setter protected float reference = 0.0F;
|
||||
|
||||
@Override
|
||||
public AlphaState set(AlphaState state) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.states;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
|
||||
@Getter @Setter
|
||||
@@ -11,6 +12,12 @@ public class BlendState implements ISettableState<BlendState> {
|
||||
protected int dstRgb = GL11.GL_ZERO;
|
||||
protected int srcAlpha = GL11.GL_ONE;
|
||||
protected int dstAlpha = GL11.GL_ZERO;
|
||||
protected int equationRgb = GL14.GL_FUNC_ADD;
|
||||
protected int equationAlpha = GL14.GL_FUNC_ADD;
|
||||
protected float blendColorR = 0.0f;
|
||||
protected float blendColorG = 0.0f;
|
||||
protected float blendColorB = 0.0f;
|
||||
protected float blendColorA = 0.0f;
|
||||
|
||||
public BlendState() {}
|
||||
|
||||
@@ -39,6 +46,12 @@ public class BlendState implements ISettableState<BlendState> {
|
||||
this.dstRgb = state.dstRgb;
|
||||
this.srcAlpha = state.srcAlpha;
|
||||
this.dstAlpha = state.dstAlpha;
|
||||
this.equationRgb = state.equationRgb;
|
||||
this.equationAlpha = state.equationAlpha;
|
||||
this.blendColorR = state.blendColorR;
|
||||
this.blendColorG = state.blendColorG;
|
||||
this.blendColorB = state.blendColorB;
|
||||
this.blendColorA = state.blendColorA;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -46,7 +59,13 @@ public class BlendState implements ISettableState<BlendState> {
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof BlendState blendState)) return false;
|
||||
return srcRgb == blendState.srcRgb && dstRgb == blendState.dstRgb && srcAlpha == blendState.srcAlpha && dstAlpha == blendState.dstAlpha;
|
||||
return srcRgb == blendState.srcRgb && dstRgb == blendState.dstRgb
|
||||
&& srcAlpha == blendState.srcAlpha && dstAlpha == blendState.dstAlpha
|
||||
&& equationRgb == blendState.equationRgb && equationAlpha == blendState.equationAlpha
|
||||
&& Float.compare(blendColorR, blendState.blendColorR) == 0
|
||||
&& Float.compare(blendColorG, blendState.blendColorG) == 0
|
||||
&& Float.compare(blendColorB, blendState.blendColorB) == 0
|
||||
&& Float.compare(blendColorA, blendState.blendColorA) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,18 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class BooleanState implements ISettableState<BooleanState> {
|
||||
protected final int glCap;
|
||||
protected final boolean ffpStateOnly;
|
||||
|
||||
@Getter protected boolean enabled;
|
||||
protected boolean stateUnknown;
|
||||
|
||||
public BooleanState(int glCap) {
|
||||
this(glCap, false);
|
||||
}
|
||||
|
||||
public BooleanState(int glCap, boolean ffpStateOnly) {
|
||||
this.glCap = glCap;
|
||||
this.ffpStateOnly = ffpStateOnly;
|
||||
}
|
||||
|
||||
public void setUnknownState() {
|
||||
@@ -33,11 +39,12 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
if (!bypass) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
// Always call GL - the calling method controls whether we reach here based on recording mode
|
||||
if (enabled) {
|
||||
GL11.glEnable(this.glCap);
|
||||
} else {
|
||||
GL11.glDisable(this.glCap);
|
||||
if (!ffpStateOnly) {
|
||||
if (enabled) {
|
||||
GL11.glEnable(this.glCap);
|
||||
} else {
|
||||
GL11.glDisable(this.glCap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,19 @@ public class ClientArrayState {
|
||||
return vertexPointerStride;
|
||||
}
|
||||
|
||||
// Normal array state
|
||||
private boolean normalArrayEnabled = false;
|
||||
private Buffer normalPointer = null;
|
||||
private int normalPointerType = GL11.GL_FLOAT;
|
||||
private int normalPointerStride = 0;
|
||||
|
||||
// TexCoord array state
|
||||
private boolean texCoordArrayEnabled = false;
|
||||
private Buffer texCoordPointer = null;
|
||||
private int texCoordPointerSize = 4;
|
||||
private int texCoordPointerType = GL11.GL_FLOAT;
|
||||
private int texCoordPointerStride = 0;
|
||||
|
||||
// Color array methods
|
||||
public void setColorPointer(int size, int type, int stride, Buffer pointer) {
|
||||
this.colorPointerSize = size;
|
||||
@@ -91,4 +104,63 @@ public class ClientArrayState {
|
||||
public int getColorPointerStride() {
|
||||
return colorPointerStride;
|
||||
}
|
||||
|
||||
// Normal array methods
|
||||
public void setNormalPointer(int type, int stride, Buffer pointer) {
|
||||
this.normalPointerType = type;
|
||||
this.normalPointerStride = stride;
|
||||
this.normalPointer = pointer;
|
||||
}
|
||||
|
||||
public boolean isNormalArrayEnabled() {
|
||||
return normalArrayEnabled;
|
||||
}
|
||||
|
||||
public void setNormalArrayEnabled(boolean enabled) {
|
||||
this.normalArrayEnabled = enabled;
|
||||
}
|
||||
|
||||
public Buffer getNormalPointer() {
|
||||
return normalPointer;
|
||||
}
|
||||
|
||||
public int getNormalPointerType() {
|
||||
return normalPointerType;
|
||||
}
|
||||
|
||||
public int getNormalPointerStride() {
|
||||
return normalPointerStride;
|
||||
}
|
||||
|
||||
// TexCoord array methods
|
||||
public void setTexCoordPointer(int size, int type, int stride, Buffer pointer) {
|
||||
this.texCoordPointerSize = size;
|
||||
this.texCoordPointerType = type;
|
||||
this.texCoordPointerStride = stride;
|
||||
this.texCoordPointer = pointer;
|
||||
}
|
||||
|
||||
public boolean isTexCoordArrayEnabled() {
|
||||
return texCoordArrayEnabled;
|
||||
}
|
||||
|
||||
public void setTexCoordArrayEnabled(boolean enabled) {
|
||||
this.texCoordArrayEnabled = enabled;
|
||||
}
|
||||
|
||||
public Buffer getTexCoordPointer() {
|
||||
return texCoordPointer;
|
||||
}
|
||||
|
||||
public int getTexCoordPointerSize() {
|
||||
return texCoordPointerSize;
|
||||
}
|
||||
|
||||
public int getTexCoordPointerType() {
|
||||
return texCoordPointerType;
|
||||
}
|
||||
|
||||
public int getTexCoordPointerStride() {
|
||||
return texCoordPointerStride;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* Stores eye-space clip plane equations for core profile emulation.
|
||||
*/
|
||||
public class ClipPlaneState {
|
||||
private final float[][] eyePlanes = new float[GLStateManager.MAX_CLIP_PLANES][4];
|
||||
|
||||
private static final Vector4f tempPlane = new Vector4f();
|
||||
private static final Matrix4f tempInverse = new Matrix4f();
|
||||
|
||||
public void setPlane(int index, double a, double b, double c, double d, Matrix4f modelView) {
|
||||
final float[] dest = eyePlanes[index];
|
||||
modelView.invert(tempInverse);
|
||||
tempPlane.set((float) a, (float) b, (float) c, (float) d);
|
||||
tempInverse.transformTranspose(tempPlane);
|
||||
dest[0] = tempPlane.x;
|
||||
dest[1] = tempPlane.y;
|
||||
dest[2] = tempPlane.z;
|
||||
dest[3] = tempPlane.w;
|
||||
}
|
||||
|
||||
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
|
||||
public void putEyePlane(int index, FloatBuffer buf) {
|
||||
final float[] p = eyePlanes[index];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,13 @@ import org.lwjgl.opengl.GL11;
|
||||
public class DepthState implements ISettableState<DepthState> {
|
||||
protected boolean enabled = true;
|
||||
protected int func = GL11.GL_LESS;
|
||||
protected double clearValue = 1.0;
|
||||
|
||||
@Override
|
||||
public DepthState set(DepthState state) {
|
||||
this.enabled = state.enabled;
|
||||
this.func = state.func;
|
||||
this.clearValue = state.clearValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -20,7 +22,7 @@ public class DepthState implements ISettableState<DepthState> {
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof DepthState depthState)) return false;
|
||||
return enabled == depthState.enabled && func == depthState.func;
|
||||
return enabled == depthState.enabled && func == depthState.func && Double.compare(clearValue, depthState.clearValue) == 0;
|
||||
}
|
||||
@Override
|
||||
public DepthState copy() {
|
||||
|
||||
@@ -16,6 +16,8 @@ public class FogState implements ISettableState<FogState> {
|
||||
protected float density = 1.0F;
|
||||
protected float start;
|
||||
protected float end = 1.0F;
|
||||
/** Fog distance computation mode: 0=EYE_RADIAL, 1=EYE_PLANE, 2=EYE_PLANE_ABS */
|
||||
protected int fogDistanceMode = 2;
|
||||
|
||||
@Override
|
||||
public FogState set(FogState state) {
|
||||
@@ -23,16 +25,20 @@ public class FogState implements ISettableState<FogState> {
|
||||
this.fogColor.set(state.fogColor);
|
||||
this.fogAlpha = state.fogAlpha;
|
||||
this.fogColorBuffer.put(0, state.fogColorBuffer.get(0));
|
||||
this.fogColorBuffer.put(1, state.fogColorBuffer.get(1));
|
||||
this.fogColorBuffer.put(2, state.fogColorBuffer.get(2));
|
||||
this.fogColorBuffer.put(3, state.fogColorBuffer.get(3));
|
||||
this.density = state.density;
|
||||
this.start = state.start;
|
||||
this.end = state.end;
|
||||
this.fogDistanceMode = state.fogDistanceMode;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof FogState fogState)) return false;
|
||||
return fogMode == fogState.fogMode && Float.compare(fogState.fogAlpha, fogAlpha) == 0 && Float.compare(fogState.density, density) == 0 && Float.compare(fogState.start, start) == 0 && Float.compare(fogState.end, end) == 0 && fogColor.equals(fogState.fogColor);
|
||||
return fogMode == fogState.fogMode && fogDistanceMode == fogState.fogDistanceMode && Float.compare(fogState.fogAlpha, fogAlpha) == 0 && Float.compare(fogState.density, density) == 0 && Float.compare(fogState.start, start) == 0 && Float.compare(fogState.end, end) == 0 && fogColor.equals(fogState.fogColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.gtnewhorizons.angelica.glsm.states;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector4i;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -32,7 +31,6 @@ public class LightModelState implements ISettableState<LightModelState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +39,12 @@ public class LightModelState implements ISettableState<LightModelState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorControl(int val) {
|
||||
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
|
||||
this.colorControl = val;
|
||||
GL11.glLightModeli(GL12.GL_LIGHT_MODEL_COLOR_CONTROL, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +55,6 @@ public class LightModelState implements ISettableState<LightModelState> {
|
||||
public void setLocalViewer(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
|
||||
this.localViewer = val;
|
||||
GL11.glLightModelf(GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +73,6 @@ public class LightModelState implements ISettableState<LightModelState> {
|
||||
public void setTwoSide(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
|
||||
this.twoSide = val;
|
||||
GL11.glLightModelf(GL11.GL_LIGHT_MODEL_TWO_SIDE, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +84,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +91,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_DIFFUSE, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +99,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_DIFFUSE, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +106,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_SPECULAR, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +114,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
GL11.glLight(this.light, GL11.GL_SPECULAR, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,14 +123,12 @@ public class LightState implements ISettableState<LightState> {
|
||||
// with and the untransformed coordinates in addition to the final transformation.
|
||||
this.position.set(newBuffer);
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
GL11.glLight(this.light, GL11.GL_POSITION, newBuffer);
|
||||
}
|
||||
|
||||
public void setPosition(IntBuffer newBuffer) {
|
||||
vector4i.set(newBuffer);
|
||||
this.position.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
|
||||
this.position.mul(GLStateManager.getModelViewMatrix());
|
||||
GL11.glLight(this.light, GL11.GL_POSITION, newBuffer);
|
||||
}
|
||||
|
||||
public void setSpotDirection(FloatBuffer newBuffer) {
|
||||
@@ -146,7 +138,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.set(newBuffer);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
GL11.glLight(this.light, GL11.GL_SPOT_DIRECTION, newBuffer);
|
||||
}
|
||||
|
||||
public void setSpotDirection(IntBuffer newBuffer) {
|
||||
@@ -154,7 +145,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
|
||||
this.spotDirection.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
|
||||
this.spotDirection.mul(matrix3f);
|
||||
GL11.glLight(this.light, GL11.GL_SPOT_DIRECTION, newBuffer);
|
||||
}
|
||||
|
||||
public void setSpotExponent(FloatBuffer newBuffer) {
|
||||
@@ -172,7 +162,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setSpotExponent(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
|
||||
this.spotExponent = f;
|
||||
GL11.glLightf(this.light, GL11.GL_SPOT_EXPONENT, f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +180,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setSpotCutoff(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
|
||||
this.spotCutoff = f;
|
||||
GL11.glLightf(this.light, GL11.GL_SPOT_CUTOFF, f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +198,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setConstantAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
|
||||
this.constantAttenuation = f;
|
||||
GL11.glLightf(this.light, GL11.GL_CONSTANT_ATTENUATION, f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +216,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setLinearAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
|
||||
this.linearAttenuation = f;
|
||||
GL11.glLightf(this.light, GL11.GL_LINEAR_ATTENUATION, f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +234,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setQuadraticAttenuation(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
|
||||
this.quadraticAttenuation = f;
|
||||
GL11.glLightf(this.light, GL11.GL_QUADRATIC_ATTENUATION, f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector4i;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
@@ -42,7 +41,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +49,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
|
||||
this.ambient.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_AMBIENT, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +56,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_DIFFUSE, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +64,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
|
||||
this.diffuse.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_DIFFUSE, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +71,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_SPECULAR, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +79,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
|
||||
this.specular.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_SPECULAR, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +86,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(newBuffer);
|
||||
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
|
||||
this.emission.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_EMISSION, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +94,12 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
|
||||
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
|
||||
this.emission.set(vector4f);
|
||||
GL11.glMaterial(face, GL11.GL_EMISSION, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShininess(float val) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
|
||||
this.shininess = val;
|
||||
GL11.glMaterialf(face, GL11.GL_SHININESS, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +122,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
|
||||
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
|
||||
this.colorIndexes.set(vector3f);
|
||||
GL11.glMaterial(face, GL11.GL_COLOR_INDEXES, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +130,6 @@ public class MaterialState implements ISettableState<MaterialState> {
|
||||
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
|
||||
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
|
||||
this.colorIndexes.set(vector3f);
|
||||
GL11.glMaterial(face, GL11.GL_COLOR_INDEXES, newBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ public class MatrixMode implements ISettableState<MatrixMode> {
|
||||
|
||||
public void setMode(int mode) {
|
||||
if(mode != GL11.GL_MODELVIEW && mode != GL11.GL_PROJECTION && mode != GL11.GL_TEXTURE && mode != GL11.GL_COLOR) {
|
||||
// Invalid mode, do nothing on the cache, but pass it along to OGL
|
||||
GL11.glMatrixMode(mode);
|
||||
// Invalid mode, do nothing on the cache
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@ public class MatrixMode implements ISettableState<MatrixMode> {
|
||||
if (!bypass) {
|
||||
this.mode = mode;
|
||||
}
|
||||
GL11.glMatrixMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Per-texture-unit storage for texgen mode and plane coefficients. Based on Mesa's texgen.c storage model.
|
||||
*/
|
||||
public class TexGenState {
|
||||
private int modeS, modeT, modeR, modeQ;
|
||||
|
||||
private final float[] objectPlaneS = {1, 0, 0, 0};
|
||||
private final float[] objectPlaneT = {0, 1, 0, 0};
|
||||
private final float[] objectPlaneR = {0, 0, 0, 0};
|
||||
private final float[] objectPlaneQ = {0, 0, 0, 0};
|
||||
|
||||
private final float[] eyePlaneS = {1, 0, 0, 0};
|
||||
private final float[] eyePlaneT = {0, 1, 0, 0};
|
||||
private final float[] eyePlaneR = {0, 0, 0, 0};
|
||||
private final float[] eyePlaneQ = {0, 0, 0, 0};
|
||||
|
||||
private static final Vector4f tempPlane = new Vector4f();
|
||||
private static final Matrix4f tempInverse = new Matrix4f();
|
||||
|
||||
public int getMode(int coord) {
|
||||
return switch (coord) {
|
||||
case GL11.GL_S -> modeS;
|
||||
case GL11.GL_T -> modeT;
|
||||
case GL11.GL_R -> modeR;
|
||||
case GL11.GL_Q -> modeQ;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public void setMode(int coord, int mode) {
|
||||
switch (coord) {
|
||||
case GL11.GL_S -> modeS = mode;
|
||||
case GL11.GL_T -> modeT = mode;
|
||||
case GL11.GL_R -> modeR = mode;
|
||||
case GL11.GL_Q -> modeQ = mode;
|
||||
}
|
||||
}
|
||||
|
||||
public float[] getObjectPlane(int coord) {
|
||||
return switch (coord) {
|
||||
case GL11.GL_S -> objectPlaneS;
|
||||
case GL11.GL_T -> objectPlaneT;
|
||||
case GL11.GL_R -> objectPlaneR;
|
||||
case GL11.GL_Q -> objectPlaneQ;
|
||||
default -> objectPlaneS;
|
||||
};
|
||||
}
|
||||
|
||||
public float[] getEyePlane(int coord) {
|
||||
return switch (coord) {
|
||||
case GL11.GL_S -> eyePlaneS;
|
||||
case GL11.GL_T -> eyePlaneT;
|
||||
case GL11.GL_R -> eyePlaneR;
|
||||
case GL11.GL_Q -> eyePlaneQ;
|
||||
default -> eyePlaneS;
|
||||
};
|
||||
}
|
||||
|
||||
public void setObjectPlane(int coord, float[] plane) {
|
||||
final float[] dest = getObjectPlane(coord);
|
||||
System.arraycopy(plane, 0, dest, 0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set eye plane coefficients. Per GL spec (Mesa texgen.c:154), the plane is transformed by the inverse of the current modelview matrix at the time of the call.
|
||||
*/
|
||||
public void setEyePlane(int coord, float[] plane, Matrix4f modelView) {
|
||||
final float[] dest = getEyePlane(coord);
|
||||
modelView.invert(tempInverse);
|
||||
tempPlane.set(plane[0], plane[1], plane[2], plane[3]);
|
||||
tempInverse.transformTranspose(tempPlane);
|
||||
dest[0] = tempPlane.x;
|
||||
dest[1] = tempPlane.y;
|
||||
dest[2] = tempPlane.z;
|
||||
dest[3] = tempPlane.w;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public class TextureUnitArray {
|
||||
private final TextureUnitBooleanStateStack[] texGenTStates;
|
||||
private final TextureUnitBooleanStateStack[] texGenRStates;
|
||||
private final TextureUnitBooleanStateStack[] texGenQStates;
|
||||
private final TexGenState[] texGenStates;
|
||||
@Getter
|
||||
public final Matrix4fStack[] textureMatricies;
|
||||
|
||||
@@ -29,6 +30,7 @@ public class TextureUnitArray {
|
||||
texGenTStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texGenRStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texGenQStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texGenStates = new TexGenState[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
textureMatricies = new Matrix4fStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
|
||||
for (int i = 0; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
@@ -40,6 +42,7 @@ public class TextureUnitArray {
|
||||
texGenTStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i);
|
||||
texGenRStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i);
|
||||
texGenQStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i);
|
||||
texGenStates[i] = new TexGenState();
|
||||
textureMatricies[i] = new Matrix4fStack(GLStateManager.MAX_TEXTURE_STACK_DEPTH);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +79,10 @@ public class TextureUnitArray {
|
||||
return texGenQStates[index];
|
||||
}
|
||||
|
||||
public TexGenState getTexGenState(int index) {
|
||||
return texGenStates[index];
|
||||
}
|
||||
|
||||
public Matrix4fStack getTextureUnitMatrix(int index) {
|
||||
return textureMatricies[index];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public class ViewportState implements ISettableState<ViewportState> {
|
||||
public int y;
|
||||
public int width;
|
||||
public int height;
|
||||
public double depthRangeNear = 0.0;
|
||||
public double depthRangeFar = 1.0;
|
||||
|
||||
public void setViewPort(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
@@ -15,6 +17,11 @@ public class ViewportState implements ISettableState<ViewportState> {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setDepthRange(double near, double far) {
|
||||
this.depthRangeNear = near;
|
||||
this.depthRangeFar = far;
|
||||
}
|
||||
|
||||
public void get(IntBuffer params) {
|
||||
params.put(0, x);
|
||||
params.put(1, y);
|
||||
@@ -28,6 +35,8 @@ public class ViewportState implements ISettableState<ViewportState> {
|
||||
this.y = state.y;
|
||||
this.width = state.width;
|
||||
this.height = state.height;
|
||||
this.depthRangeNear = state.depthRangeNear;
|
||||
this.depthRangeFar = state.depthRangeFar;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -36,7 +45,9 @@ public class ViewportState implements ISettableState<ViewportState> {
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof ViewportState viewportState)) return false;
|
||||
return x == viewportState.x && y == viewportState.y && width == viewportState.width && height == viewportState.height;
|
||||
return x == viewportState.x && y == viewportState.y && width == viewportState.width && height == viewportState.height
|
||||
&& Double.compare(depthRangeNear, viewportState.depthRangeNear) == 0
|
||||
&& Double.compare(depthRangeFar, viewportState.depthRangeFar) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Streaming buffer using the classic orphan pattern
|
||||
*/
|
||||
public class OrphanStreamingBuffer implements StreamingBuffer {
|
||||
|
||||
private int bufferId;
|
||||
private int capacity;
|
||||
|
||||
public OrphanStreamingBuffer() {
|
||||
this.bufferId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int upload(ByteBuffer data, int vertexStride) {
|
||||
final int requiredBytes = data.remaining();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
if (requiredBytes > capacity) {
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
|
||||
capacity = requiredBytes;
|
||||
} else {
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, capacity, GL15.GL_STREAM_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
|
||||
}
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferId() {
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDraw() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (bufferId != 0) {
|
||||
GL15.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
|
||||
import org.embeddedt.embeddium.impl.gl.sync.GlFence;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
* Persistent-mapped ring buffer for streaming vertex data on GL4.4+
|
||||
*/
|
||||
public class PersistentStreamingBuffer implements StreamingBuffer {
|
||||
|
||||
public static final int DEFAULT_CAPACITY = 16 * 1024 * 1024; // 16 MB
|
||||
|
||||
private int bufferId;
|
||||
private final int capacity;
|
||||
private final ByteBuffer mappedBuffer;
|
||||
private final long mappedAddress;
|
||||
private final ObjectArrayFIFOQueue<FencedRegion> fenceQueue = new ObjectArrayFIFOQueue<>();
|
||||
|
||||
private int writePos;
|
||||
private int remaining;
|
||||
private int pendingBytes;
|
||||
|
||||
public PersistentStreamingBuffer() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
public PersistentStreamingBuffer(int capacity) {
|
||||
this.capacity = capacity;
|
||||
this.remaining = capacity;
|
||||
|
||||
this.bufferId = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
|
||||
final int storageFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
|
||||
RenderSystem.bufferStorage(GL15.GL_ARRAY_BUFFER, capacity, storageFlags);
|
||||
|
||||
final int mapFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
|
||||
this.mappedBuffer = LWJGL.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, capacity, mapFlags);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
if (this.mappedBuffer == null) {
|
||||
GL15.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
throw new RuntimeException("Failed to persistently map streaming buffer");
|
||||
}
|
||||
this.mappedAddress = memAddress0(mappedBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int upload(ByteBuffer data, int vertexStride) {
|
||||
final int dataSize = data.remaining();
|
||||
if (dataSize > capacity) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
reclaim();
|
||||
|
||||
final int alignUnit = vertexStride * 4;
|
||||
final int alignedPos = ((writePos + alignUnit - 1) / alignUnit) * alignUnit;
|
||||
final int alignmentWaste = alignedPos - writePos;
|
||||
final int tailSpace = capacity - alignedPos;
|
||||
|
||||
if (dataSize <= tailSpace) {
|
||||
// Fits at tail — just need enough remaining budget
|
||||
if (ensureRemaining(alignmentWaste + dataSize)) {
|
||||
return writeAt(alignedPos, data, dataSize, alignmentWaste, vertexStride);
|
||||
}
|
||||
} else {
|
||||
// Must wrap to start — burn remaining tail space
|
||||
final int wrapWaste = capacity - writePos;
|
||||
if (ensureRemaining(wrapWaste + dataSize)) {
|
||||
return writeAtStart(data, dataSize, wrapWaste);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Sync fences until {@code needed} bytes are available, or no fences remain. */
|
||||
private boolean ensureRemaining(int needed) {
|
||||
while (remaining < needed) {
|
||||
if (!syncOldest()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int writeAt(int offset, ByteBuffer data, int dataSize, int waste, int vertexStride) {
|
||||
memCopy(memAddress0(data) + data.position(), mappedAddress + offset, dataSize);
|
||||
|
||||
final int totalCost = waste + dataSize;
|
||||
writePos = offset + dataSize;
|
||||
remaining -= totalCost;
|
||||
pendingBytes += totalCost;
|
||||
return offset / vertexStride;
|
||||
}
|
||||
|
||||
private int writeAtStart(ByteBuffer data, int dataSize, int wrapWaste) {
|
||||
memCopy(memAddress0(data) + data.position(), mappedAddress, dataSize);
|
||||
|
||||
final int totalCost = wrapWaste + dataSize;
|
||||
writePos = dataSize;
|
||||
remaining -= totalCost;
|
||||
pendingBytes += totalCost;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int getRemaining() { return remaining; }
|
||||
|
||||
@Override
|
||||
public int getBufferId() {
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDraw() {
|
||||
if (pendingBytes > 0) {
|
||||
final long fenceId = LWJGL.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
fenceQueue.enqueue(new FencedRegion(new GlFence(fenceId), pendingBytes));
|
||||
pendingBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
while (!fenceQueue.isEmpty()) {
|
||||
fenceQueue.dequeue().fence.delete();
|
||||
}
|
||||
if (bufferId != 0) {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GL15.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void reclaim() {
|
||||
while (!fenceQueue.isEmpty()) {
|
||||
final FencedRegion region = fenceQueue.first();
|
||||
if (!region.fence.isCompleted()) break;
|
||||
region.fence.delete();
|
||||
fenceQueue.dequeue();
|
||||
remaining += region.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sync on oldest fence. Returns true if bytes were reclaimed. */
|
||||
private boolean syncOldest() {
|
||||
if (fenceQueue.isEmpty()) return false;
|
||||
final FencedRegion region = fenceQueue.first();
|
||||
region.fence.sync();
|
||||
region.fence.delete();
|
||||
fenceQueue.dequeue();
|
||||
remaining += region.bytes;
|
||||
reclaim(); // opportunistically reclaim any additional completed fences
|
||||
return true;
|
||||
}
|
||||
|
||||
// Package-private accessors for testing
|
||||
ByteBuffer getMappedBuffer() { return mappedBuffer; }
|
||||
|
||||
private record FencedRegion(GlFence fence, int bytes) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface StreamingBuffer {
|
||||
|
||||
int upload(ByteBuffer data, int vertexStride);
|
||||
int getBufferId();
|
||||
void postDraw();
|
||||
int getCapacity();
|
||||
void destroy();
|
||||
}
|
||||
@@ -3,10 +3,8 @@ package com.gtnewhorizons.angelica.glsm.texture;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class TextureInfoCache {
|
||||
@@ -40,19 +38,9 @@ public class TextureInfoCache {
|
||||
}
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
|
||||
public void onTexImage2D(int texture, int target, int level, int internalformat, int width, int height) {
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
final TextureInfo info = getInfo(texture);
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.width = width;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.glsm.uniform;
|
||||
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniform;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
public class GlUniformFloat2v extends GlUniform<float[]> {
|
||||
|
||||
public GlUniformFloat2v(int index) {
|
||||
super(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(float[] value) {
|
||||
if (value.length != 2) {
|
||||
throw new IllegalArgumentException("value.length != 2");
|
||||
}
|
||||
GL20.glUniform2f(this.index, value[0], value[1]);
|
||||
}
|
||||
|
||||
public void set(float x, float y) {
|
||||
GL20.glUniform2f(this.index, x, y);
|
||||
}
|
||||
}
|
||||
+106
-1
@@ -43,9 +43,11 @@ public final class AngelicaRedirector {
|
||||
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
|
||||
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject";
|
||||
private static final String Project = "org/lwjgl/util/glu/Project";
|
||||
private static final String GLU = "org/lwjgl/util/glu/GLU";
|
||||
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
|
||||
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
|
||||
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
|
||||
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
|
||||
|
||||
// Redirect VAO related calls from NHLib and LWJGLService
|
||||
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
|
||||
@@ -103,6 +105,8 @@ public final class AngelicaRedirector {
|
||||
.add("glColorMask")
|
||||
.add("glColorMaterial")
|
||||
.add("glDeleteLists")
|
||||
.add("glGenLists")
|
||||
.add("glIsList")
|
||||
.add("glDeleteTextures")
|
||||
.add("glDepthFunc")
|
||||
.add("glDepthMask")
|
||||
@@ -175,6 +179,13 @@ public final class AngelicaRedirector {
|
||||
.add("glTexCoord3f")
|
||||
.add("glTexCoord4d")
|
||||
.add("glTexCoord4f")
|
||||
.add("glTexEnvi")
|
||||
.add("glTexEnvf")
|
||||
.add("glTexEnv")
|
||||
.add("glTexGeni")
|
||||
.add("glTexGenf")
|
||||
.add("glTexGend")
|
||||
.add("glTexGen")
|
||||
.add("glTexImage1D")
|
||||
.add("glTexImage2D")
|
||||
.add("glTexImage3D")
|
||||
@@ -186,6 +197,8 @@ public final class AngelicaRedirector {
|
||||
.add("glCopyTexSubImage2D")
|
||||
.add("glPixelStoref")
|
||||
.add("glPixelStorei")
|
||||
.add("glPixelTransferf")
|
||||
.add("glPixelTransferi")
|
||||
.add("glTexParameter")
|
||||
.add("glTexParameterf")
|
||||
.add("glTexParameteri")
|
||||
@@ -198,6 +211,8 @@ public final class AngelicaRedirector {
|
||||
.add("glPointSize")
|
||||
.add("glPolygonMode")
|
||||
.add("glPolygonOffset")
|
||||
.add("glPolygonStipple")
|
||||
.add("glAccum")
|
||||
.add("glReadBuffer")
|
||||
.add("glSampleCoverage")
|
||||
.add("glScissor")
|
||||
@@ -215,8 +230,13 @@ public final class AngelicaRedirector {
|
||||
.add("glVertex3f")
|
||||
.add("glVertexPointer")
|
||||
.add("glColorPointer")
|
||||
.add("glNormalPointer")
|
||||
.add("glTexCoordPointer")
|
||||
.add("glEnableClientState")
|
||||
.add("glDisableClientState")
|
||||
.add("glInterleavedArrays")
|
||||
.add("glPushClientAttrib")
|
||||
.add("glPopClientAttrib")
|
||||
.add("glViewport")
|
||||
);
|
||||
methodRedirects.put(GL12, RedirectMap.newMap()
|
||||
@@ -227,6 +247,9 @@ public final class AngelicaRedirector {
|
||||
methodRedirects.put(GL13, RedirectMap.newMap()
|
||||
.add("glActiveTexture")
|
||||
.add("glSampleCoverage")
|
||||
.add("glClientActiveTexture")
|
||||
.add("glMultiTexCoord2f")
|
||||
.add("glMultiTexCoord2s")
|
||||
);
|
||||
methodRedirects.put(GL14, RedirectMap.newMap()
|
||||
.add("glBlendFuncSeparate", "tryBlendFuncSeparate")
|
||||
@@ -243,9 +266,40 @@ public final class AngelicaRedirector {
|
||||
.add("glStencilMaskSeparate")
|
||||
.add("glStencilOpSeparate")
|
||||
.add("glUseProgram")
|
||||
.add("glShaderSource")
|
||||
.add("glLinkProgram")
|
||||
.add("glDeleteProgram")
|
||||
.add("glCreateShader")
|
||||
.add("glCompileShader")
|
||||
.add("glCreateProgram")
|
||||
.add("glAttachShader")
|
||||
.add("glDetachShader")
|
||||
.add("glValidateProgram")
|
||||
.add("glGetUniformLocation")
|
||||
.add("glGetAttribLocation")
|
||||
.add("glUniform1f")
|
||||
.add("glUniform2f")
|
||||
.add("glUniform3f")
|
||||
.add("glUniform4f")
|
||||
.add("glUniform1i")
|
||||
.add("glUniform2i")
|
||||
.add("glUniform3i")
|
||||
.add("glUniform4i")
|
||||
.add("glUniform1")
|
||||
.add("glUniform2")
|
||||
.add("glUniform3")
|
||||
.add("glUniform4")
|
||||
.add("glUniformMatrix2")
|
||||
.add("glUniformMatrix3")
|
||||
.add("glUniformMatrix4")
|
||||
.add("glGetActiveUniform")
|
||||
.add("glGetAttachedShaders")
|
||||
.add("glGetShaderSource")
|
||||
.add("glGetUniform")
|
||||
);
|
||||
methodRedirects.put(GL30, RedirectMap.newMap()
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
.add("glBindFramebuffer")
|
||||
);
|
||||
|
||||
@@ -268,8 +322,46 @@ public final class AngelicaRedirector {
|
||||
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
|
||||
.add("glActiveTextureARB")
|
||||
);
|
||||
methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
|
||||
.add("glUseProgramObjectARB", "glUseProgram")
|
||||
.add("glShaderSourceARB", "glShaderSource")
|
||||
.add("glLinkProgramARB", "glLinkProgram")
|
||||
.add("glCreateShaderObjectARB", "glCreateShader")
|
||||
.add("glCompileShaderARB", "glCompileShader")
|
||||
.add("glCreateProgramObjectARB", "glCreateProgram")
|
||||
.add("glAttachObjectARB", "glAttachShader")
|
||||
.add("glDetachObjectARB", "glDetachShader")
|
||||
.add("glValidateProgramARB", "glValidateProgram")
|
||||
.add("glGetUniformLocationARB", "glGetUniformLocation")
|
||||
.add("glGetAttribLocationARB", "glGetAttribLocation")
|
||||
.add("glDeleteObjectARB")
|
||||
.add("glGetObjectParameterARB")
|
||||
.add("glGetObjectParameteriARB")
|
||||
.add("glGetInfoLogARB")
|
||||
.add("glGetHandleARB")
|
||||
.add("glUniform1fARB", "glUniform1f")
|
||||
.add("glUniform2fARB", "glUniform2f")
|
||||
.add("glUniform3fARB", "glUniform3f")
|
||||
.add("glUniform4fARB", "glUniform4f")
|
||||
.add("glUniform1iARB", "glUniform1i")
|
||||
.add("glUniform2iARB", "glUniform2i")
|
||||
.add("glUniform3iARB", "glUniform3i")
|
||||
.add("glUniform4iARB", "glUniform4i")
|
||||
.add("glUniform1ARB", "glUniform1")
|
||||
.add("glUniform2ARB", "glUniform2")
|
||||
.add("glUniform3ARB", "glUniform3")
|
||||
.add("glUniform4ARB", "glUniform4")
|
||||
.add("glUniformMatrix2ARB", "glUniformMatrix2")
|
||||
.add("glUniformMatrix3ARB", "glUniformMatrix3")
|
||||
.add("glUniformMatrix4ARB", "glUniformMatrix4")
|
||||
.add("glGetActiveUniformARB", "glGetActiveUniform")
|
||||
.add("glGetAttachedObjectsARB", "glGetAttachedShaders")
|
||||
.add("glGetShaderSourceARB", "glGetShaderSource")
|
||||
.add("glGetUniformARB", "glGetUniform")
|
||||
);
|
||||
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
);
|
||||
|
||||
|
||||
@@ -281,14 +373,26 @@ public final class AngelicaRedirector {
|
||||
// GTNHLib VAO
|
||||
methodRedirects.put(UniversalVAO, RedirectMap.newMap()
|
||||
.add("bindVertexArray", "glBindVertexArray")
|
||||
.add("deleteVertexArrays", "glDeleteVertexArrays")
|
||||
);
|
||||
|
||||
// OTHER
|
||||
methodRedirects.put(Project, RedirectMap.newMap().add("gluPerspective"));
|
||||
methodRedirects.put(Project, RedirectMap.newMap()
|
||||
.add("gluPerspective")
|
||||
.add("gluLookAt")
|
||||
.add("gluPickMatrix")
|
||||
);
|
||||
methodRedirects.put(GLU, RedirectMap.newMap()
|
||||
.add("gluPerspective")
|
||||
.add("gluLookAt")
|
||||
.add("gluOrtho2D")
|
||||
.add("gluPickMatrix")
|
||||
);
|
||||
|
||||
// Interface/virtual redirects — callers invoke these on a receiver object
|
||||
interfaceRedirects.put(VaoFunctions, RedirectMap.newMap()
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
);
|
||||
interfaceRedirects.put(LWJGLService, RedirectMap.newMap()
|
||||
.add("glBindVertexArray")
|
||||
@@ -303,6 +407,7 @@ public final class AngelicaRedirector {
|
||||
stringsToSearch.add(OpenGlHelper);
|
||||
stringsToSearch.add(EXTBlendFunc);
|
||||
stringsToSearch.add(ARBMultiTexture);
|
||||
stringsToSearch.add(ARBShaderObjects);
|
||||
stringsToSearch.add(Project);
|
||||
stringsToSearch.add(UniversalVAO);
|
||||
stringsToSearch.add(VaoFunctions);
|
||||
|
||||
@@ -29,6 +29,7 @@ public enum Mixins implements IMixins {
|
||||
.addClientMixins(
|
||||
"angelica.MixinActiveRenderInfo"
|
||||
, "angelica.MixinEntityRenderer"
|
||||
, "angelica.MixinForgeHooksClient_CoreProfile"
|
||||
, "angelica.MixinGameSettings"
|
||||
, "angelica.MixinMinecraft"
|
||||
, "angelica.MixinMinecraftServer"
|
||||
@@ -37,6 +38,7 @@ public enum Mixins implements IMixins {
|
||||
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
|
||||
, "angelica.glsm.MixinSplashProgressCaching"
|
||||
, "angelica.debug.MixinMinecraft_FPSCap"
|
||||
, "angelica.ffp.MixinTessellator_CoreProfile"
|
||||
)
|
||||
),
|
||||
|
||||
@@ -275,6 +277,7 @@ public enum Mixins implements IMixins {
|
||||
OPTIMIZE_WORLD_UPDATE_LIGHT(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.addExcludedMod(TargetedMod.ARCHAICFIX)
|
||||
.addExcludedMod(TargetedMod.SUPERNOVA)
|
||||
.setApplyIf(() -> AngelicaConfig.optimizeWorldUpdateLight)
|
||||
.addCommonMixins("angelica.lighting.MixinWorld_FixLightUpdateLag")),
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ public enum TargetedMod implements ITargetMod {
|
||||
IC2("ic2.core.coremod.IC2core", "IC2"),
|
||||
NATURA(null, "Natura"),
|
||||
SECURITYCRAFT(null, "securitycraft"),
|
||||
SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"),
|
||||
THAUMCRAFT(null, "Thaumcraft"),
|
||||
TINKERS_CONSTRUCT(null, "TConstruct"),
|
||||
TWILIGHT_FOREST(null, "TwilightForest"),
|
||||
|
||||
@@ -25,38 +25,44 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.uniform.GlUniformFloat2v;
|
||||
import jss.notfine.core.Settings;
|
||||
import jss.notfine.gui.options.named.GraphicsQualityOff;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.Vec3;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformInt;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
// Shared constants.
|
||||
private static final float PX_SIZE = 1 / 256F;
|
||||
|
||||
// Building constants.
|
||||
private static final int TOP_SECTIONS = 12; // Number of slices a top face will span.
|
||||
private static final int TOP_SECTIONS = 12;
|
||||
private static final int HEIGHT = 4;
|
||||
private static final float INSET = 0.001F;
|
||||
private static final float ALPHA = 0.8F;
|
||||
|
||||
// Debug
|
||||
private static final boolean WIREFRAME = false;
|
||||
|
||||
// Instance fields
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final ResourceLocation texture = new ResourceLocation("textures/environment/clouds.png");
|
||||
|
||||
@@ -66,14 +72,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
private int cloudElevation = -1;
|
||||
private int scaleMult = -1;
|
||||
|
||||
private DynamicTexture COLOR_TEX = null;
|
||||
private int texColor;
|
||||
|
||||
private int texW;
|
||||
private int texH;
|
||||
|
||||
private GlProgram<CloudUniforms> program;
|
||||
private final Matrix4f mvpScratch = new Matrix4f();
|
||||
|
||||
public CloudRenderer() {
|
||||
// Resource manager should always be reloadable.
|
||||
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this);
|
||||
}
|
||||
|
||||
@@ -89,24 +94,28 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
}
|
||||
|
||||
private float ceilToScale(float value) {
|
||||
float scale = getScale();
|
||||
final float scale = getScale();
|
||||
return (float)Math.ceil(value / scale) * scale;
|
||||
}
|
||||
|
||||
private float meshExtent() {
|
||||
return ceilToScale(renderDistance * 4 * 16);
|
||||
}
|
||||
|
||||
private void vertices(Tessellator tessellator) {
|
||||
GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore();
|
||||
boolean fancy = cloudGraphicsQuality == GraphicsQualityOff.FANCY || cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics;
|
||||
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore();
|
||||
final boolean fancy = cloudGraphicsQuality == GraphicsQualityOff.FANCY || cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics;
|
||||
|
||||
float scale = getScale();
|
||||
float CULL_DIST = 2 * scale;
|
||||
final float scale = getScale();
|
||||
final float CULL_DIST = 2 * scale;
|
||||
|
||||
float bCol = fancy ? 0.7F : 1F;
|
||||
final float bCol = fancy ? 0.7F : 1F;
|
||||
|
||||
float sectEnd = ceilToScale((renderDistance * 2) * 16);
|
||||
float sectStart = -sectEnd;
|
||||
final float sectEnd = meshExtent();
|
||||
final float sectStart = -sectEnd;
|
||||
|
||||
float sectStep = ceilToScale(sectEnd * 2 / TOP_SECTIONS);
|
||||
float sectPx = PX_SIZE / scale;
|
||||
final float sectStep = ceilToScale(sectEnd * 2 / TOP_SECTIONS);
|
||||
final float sectPx = PX_SIZE / scale;
|
||||
|
||||
tessellator.startDrawingQuads();
|
||||
|
||||
@@ -128,10 +137,10 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
if (sectZ1 > sectEnd)
|
||||
sectZ1 = sectEnd;
|
||||
|
||||
float u0 = sectX0 * sectPx;
|
||||
float u1 = sectX1 * sectPx;
|
||||
float v0 = sectZ0 * sectPx;
|
||||
float v1 = sectZ1 * sectPx;
|
||||
final float u0 = sectX0 * sectPx;
|
||||
final float u1 = sectX1 * sectPx;
|
||||
final float v0 = sectZ0 * sectPx;
|
||||
final float v1 = sectZ1 * sectPx;
|
||||
|
||||
// Bottom
|
||||
tessellator.setColorRGBA_F(bCol, bCol, bCol, ALPHA);
|
||||
@@ -222,6 +231,10 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
vao.delete();
|
||||
vao = null;
|
||||
}
|
||||
if (program != null) {
|
||||
program.delete();
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void build() {
|
||||
@@ -239,7 +252,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
}
|
||||
|
||||
public void checkSettings() {
|
||||
GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore();
|
||||
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore();
|
||||
final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY
|
||||
|| cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1;
|
||||
final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF
|
||||
@@ -273,91 +286,170 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void initProgram() {
|
||||
if (program != null) return;
|
||||
|
||||
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, "angelica:cloud.vert", ShaderConstants.EMPTY);
|
||||
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, "angelica:cloud.frag", ShaderConstants.EMPTY);
|
||||
try {
|
||||
program = GlProgram.builder("angelica:cloud")
|
||||
.attachShader(vert)
|
||||
.attachShader(frag)
|
||||
.link(CloudUniforms::new);
|
||||
} finally {
|
||||
vert.delete();
|
||||
frag.delete();
|
||||
}
|
||||
|
||||
final CloudUniforms u = program.getInterface();
|
||||
final float start = renderDistance * 16.0f;
|
||||
final float end = meshExtent();
|
||||
final float range = end - start;
|
||||
|
||||
program.bind();
|
||||
u.cloudTex.setInt(0);
|
||||
u.fogParams.set(new float[] {
|
||||
range != 0.0f ? -1.0f / range : 0.0f,
|
||||
range != 0.0f ? end / range : 1.0f,
|
||||
0.0f, 0.0f
|
||||
});
|
||||
program.unbind();
|
||||
}
|
||||
|
||||
private void uploadFogUniforms(float partialTicks) {
|
||||
final CloudUniforms u = program.getInterface();
|
||||
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
|
||||
u.setFogEnabled(fogEnabled);
|
||||
|
||||
if (fogEnabled) {
|
||||
final Vec3 sky = mc.theWorld.getSkyColor(mc.renderViewEntity, partialTicks);
|
||||
u.setFogColor((float) sky.xCoord, (float) sky.yCoord, (float) sky.zCoord);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CloudUniforms {
|
||||
final GlUniformMatrix4f mvp;
|
||||
final GlUniformFloat3v colorMult;
|
||||
final GlUniformFloat2v texOffset;
|
||||
final GlUniformFloat2v fogOrigin;
|
||||
final GlUniformFloat4v fogParams;
|
||||
final GlUniformFloat4v fogColor;
|
||||
final GlUniformInt fogEnabled;
|
||||
final GlUniformInt cloudTex;
|
||||
|
||||
private int lastFogEnabled = -1;
|
||||
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
|
||||
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
|
||||
private final float[] fogColorBuf = new float[4];
|
||||
|
||||
CloudUniforms(ShaderBindingContext ctx) {
|
||||
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
|
||||
colorMult = ctx.bindUniform("u_ColorMultiplier", GlUniformFloat3v::new);
|
||||
texOffset = ctx.bindUniform("u_TexOffset", GlUniformFloat2v::new);
|
||||
fogOrigin = ctx.bindUniform("u_FogOrigin", GlUniformFloat2v::new);
|
||||
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
|
||||
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
|
||||
fogEnabled = ctx.bindUniform("u_FogEnabled", GlUniformInt::new);
|
||||
cloudTex = ctx.bindUniform("u_CloudTexture", GlUniformInt::new);
|
||||
}
|
||||
|
||||
void setFogEnabled(boolean enabled) {
|
||||
final int val = enabled ? 1 : 0;
|
||||
if (val != lastFogEnabled) {
|
||||
lastFogEnabled = val;
|
||||
fogEnabled.setInt(val);
|
||||
}
|
||||
}
|
||||
|
||||
void setFogColor(float r, float g, float b) {
|
||||
if (r != lastFogR || g != lastFogG || b != lastFogB) {
|
||||
lastFogR = r; lastFogG = g; lastFogB = b;
|
||||
fogColorBuf[0] = r; fogColorBuf[1] = g; fogColorBuf[2] = b; fogColorBuf[3] = 1.0f;
|
||||
fogColor.set(fogColorBuf);
|
||||
}
|
||||
}
|
||||
|
||||
void setColorMult(float r, float g, float b) {
|
||||
if (r != lastColorR || g != lastColorG || b != lastColorB) {
|
||||
lastColorR = r; lastColorG = g; lastColorB = b;
|
||||
colorMult.set(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean render(int cloudTicks, float partialTicks) {
|
||||
if (!isBuilt())
|
||||
return false;
|
||||
|
||||
Entity entity = mc.renderViewEntity;
|
||||
if (program == null) {
|
||||
initProgram();
|
||||
if (program == null) return false;
|
||||
}
|
||||
|
||||
double totalOffset = cloudTicks + partialTicks;
|
||||
final Entity entity = mc.renderViewEntity;
|
||||
|
||||
double x = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks + totalOffset * 0.03;
|
||||
double y = cloudElevation - (entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks) + 0.33;
|
||||
final double totalOffset = cloudTicks + partialTicks;
|
||||
|
||||
final double x = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks + totalOffset * 0.03;
|
||||
final double y = cloudElevation - (entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks) + 0.33;
|
||||
double z = entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks;
|
||||
|
||||
int scale = getScale();
|
||||
final int scale = getScale();
|
||||
|
||||
if (cloudMode == 2)
|
||||
z += 0.33 * scale;
|
||||
|
||||
// Integer UVs to translate the texture matrix by.
|
||||
int offU = fullCoord(x, scale);
|
||||
int offV = fullCoord(z, scale);
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
// Translate by the remainder after the UV offset.
|
||||
GLStateManager.glTranslated((offU * scale) - x, y, (offV * scale) - z);
|
||||
|
||||
// Modulo to prevent texture samples becoming inaccurate at extreme offsets.
|
||||
final float fogOriginX = (float)(x - offU * scale);
|
||||
final float fogOriginZ = (float)(z - offV * scale);
|
||||
|
||||
offU = offU % texW;
|
||||
offV = offV % texH;
|
||||
|
||||
// Translate the texture.
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glTranslatef(offU * PX_SIZE, offV * PX_SIZE, 0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
|
||||
GLStateManager.disableCull();
|
||||
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
// Color multiplier.
|
||||
Vec3 color = mc.theWorld.getCloudColour(partialTicks);
|
||||
final Vec3 color = mc.theWorld.getCloudColour(partialTicks);
|
||||
float r = (float) color.xCoord;
|
||||
float g = (float) color.yCoord;
|
||||
float b = (float) color.zCoord;
|
||||
|
||||
if (mc.gameSettings.anaglyph) {
|
||||
float tempR = r * 0.3F + g * 0.59F + b * 0.11F;
|
||||
float tempG = r * 0.3F + g * 0.7F;
|
||||
float tempB = r * 0.3F + b * 0.7F;
|
||||
final float tempR = r * 0.3F + g * 0.59F + b * 0.11F;
|
||||
final float tempG = r * 0.3F + g * 0.7F;
|
||||
final float tempB = r * 0.3F + b * 0.7F;
|
||||
r = tempR;
|
||||
g = tempG;
|
||||
b = tempB;
|
||||
}
|
||||
|
||||
// Apply a color multiplier through a texture upload if shaders aren't supported.
|
||||
final int newColor = 0xFF000000
|
||||
| ((int) (r * 255)) << 16
|
||||
| ((int) (g * 255)) << 8
|
||||
| (int) (b * 255);
|
||||
if (texColor != newColor) {
|
||||
if (COLOR_TEX == null) {
|
||||
COLOR_TEX = new DynamicTexture(1, 1);
|
||||
}
|
||||
COLOR_TEX.getTextureData()[0] = newColor;
|
||||
COLOR_TEX.updateDynamicTexture();
|
||||
texColor = newColor;
|
||||
}
|
||||
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, COLOR_TEX.getGlTextureId());
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
// Bind the clouds texture last so the shader's sampler2D is correct.
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
mc.renderEngine.bindTexture(texture);
|
||||
program.bind();
|
||||
|
||||
final CloudUniforms u = program.getInterface();
|
||||
final Matrix4fStack mv = GLStateManager.getModelViewMatrix();
|
||||
GLStateManager.getProjectionMatrix().mul(mv, mvpScratch);
|
||||
u.mvp.set(mvpScratch);
|
||||
u.texOffset.set(offU * PX_SIZE, offV * PX_SIZE);
|
||||
u.setColorMult(r, g, b);
|
||||
u.fogOrigin.set(fogOriginX, fogOriginZ);
|
||||
|
||||
uploadFogUniforms(partialTicks);
|
||||
|
||||
vao.bind();
|
||||
|
||||
// Depth pass to prevent insides rendering from the outside.
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
|
||||
vao.draw();
|
||||
|
||||
// Full render.
|
||||
if (!mc.gameSettings.anaglyph) {
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
} else {
|
||||
@@ -371,32 +463,19 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
}
|
||||
}
|
||||
|
||||
// Wireframe for debug.
|
||||
if (WIREFRAME) {
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
|
||||
GL11.glLineWidth(2.0F);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.disableFog();
|
||||
vao.draw();
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableFog();
|
||||
}
|
||||
|
||||
vao.draw();
|
||||
vao.unbind(); // Unbind buffer.
|
||||
|
||||
// Disable our coloring.
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
// Reset texture matrix.
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
program.unbind();
|
||||
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.enableCull();
|
||||
@@ -417,5 +496,9 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
@Override
|
||||
public void onResourceManagerReload(IResourceManager resourceManager) {
|
||||
reloadTextures();
|
||||
if (program != null) {
|
||||
program.delete();
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,10 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@@ -8,5 +8,12 @@ public interface StateAwareTessellator {
|
||||
|
||||
void angelica$setAppliedAo(boolean flag);
|
||||
|
||||
/**
|
||||
* Sets whether we're doing terrain meshing as part of celeritas -- collects additional information.
|
||||
*
|
||||
* Enables per-vertex AO state collection into {@link #angelica$getVertexStates()}.
|
||||
*/
|
||||
void angelica$setCeleritasMeshing(boolean active);
|
||||
|
||||
int[] angelica$getVertexStates();
|
||||
}
|
||||
+12
-6
@@ -104,6 +104,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
onEnterExecute();
|
||||
|
||||
final Tessellator tessellator = getTessellator();
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(true);
|
||||
|
||||
try {
|
||||
tessellator.setTranslation(-minX, -minY, -minZ);
|
||||
@@ -207,6 +208,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
} catch (Throwable ex) {
|
||||
throw fillCrashInfo(CrashReport.makeCrashReport(ex, "Encountered exception while building chunk meshes"), region, blockPos);
|
||||
} finally {
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(false);
|
||||
SmoothBiomeColorCache.clearActiveCache();
|
||||
onExitExecute();
|
||||
}
|
||||
@@ -270,15 +272,19 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
final RenderBlocks mainThreadRenderBlocks = new RenderBlocks(region);
|
||||
final Tessellator mainTessellator = Tessellator.instance;
|
||||
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(true);
|
||||
mainTessellator.setTranslation(-minX, -minY, -minZ);
|
||||
|
||||
for (DeferredBlock deferred : deferredBlocks) {
|
||||
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
|
||||
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
|
||||
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride());
|
||||
try {
|
||||
for (DeferredBlock deferred : deferredBlocks) {
|
||||
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
|
||||
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
|
||||
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride());
|
||||
}
|
||||
} finally {
|
||||
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
|
||||
mainTessellator.setTranslation(0, 0, 0);
|
||||
}
|
||||
|
||||
mainTessellator.setTranslation(0, 0, 0);
|
||||
}, AngelicaRenderQueue.executor());
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
|
||||
+1
-1
@@ -4,9 +4,9 @@ import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import org.embeddedt.embeddium.impl.gl.device.RenderDevice;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.DefaultChunkRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DirectMultiDrawEmitter;
|
||||
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.QuadPrimitiveType;
|
||||
@@ -21,17 +22,17 @@ public class AngelicaRenderPassConfiguration {
|
||||
@Override
|
||||
public void setup() {
|
||||
if (pass == 0) {
|
||||
GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
}
|
||||
if (disableAlphaTest) {
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (disableAlphaTest) {
|
||||
GL11.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.enableAlphaTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +41,7 @@ public class AngelicaRenderPassConfiguration {
|
||||
return TerrainRenderPass.builder()
|
||||
.pipelineState(new AngelicaPipelineState(pass, disableAlphaTest))
|
||||
.vertexType(vertexType)
|
||||
.primitiveType(QuadPrimitiveType.DIRECT);
|
||||
.primitiveType(QuadPrimitiveType.TRIANGULATED);
|
||||
}
|
||||
|
||||
public static RenderPassConfiguration<BlockRenderLayer> build(ChunkVertexType vertexType) {
|
||||
|
||||
+7
@@ -4,6 +4,8 @@ import com.gtnewhorizons.angelica.Tags;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import cpw.mods.fml.common.eventhandler.EventPriority;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
@@ -42,6 +44,11 @@ public class CeleritasDebugScreenHandler {
|
||||
event.right.add("");
|
||||
event.right.add(EnumChatFormatting.GREEN + "Angelica " + Tags.VERSION + " [Celeritas Renderer]");
|
||||
event.right.addAll(renderer.getDebugStrings());
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm != null) {
|
||||
event.right.add(sm.getDebugInfo());
|
||||
}
|
||||
event.right.add(TessellatorStreamingDrawer.getDebugInfo());
|
||||
event.right.add("");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,15 @@ import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.blending.BlendModeOverride;
|
||||
import net.coderbot.iris.gl.blending.BufferBlendOverride;
|
||||
import net.coderbot.iris.gl.framebuffer.GlFramebuffer;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import net.coderbot.iris.gl.program.ProgramImages;
|
||||
import net.coderbot.iris.gl.program.ProgramSamplers;
|
||||
import net.coderbot.iris.gl.program.ProgramUniforms;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
import net.coderbot.iris.shadows.ShadowRenderingState;
|
||||
import net.coderbot.iris.uniforms.custom.CustomUniforms;
|
||||
import java.util.List;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix3f;
|
||||
@@ -30,6 +27,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class IrisCeleritasChunkShaderInterface implements ChunkShaderInterface {
|
||||
@Nullable
|
||||
@@ -129,7 +130,7 @@ public class IrisCeleritasChunkShaderInterface implements ChunkShaderInterface {
|
||||
|
||||
@Override
|
||||
public GlPrimitiveType getPrimitiveType() {
|
||||
return GlPrimitiveType.QUADS;
|
||||
return GlPrimitiveType.TRIANGLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,18 +34,18 @@ public class GlImage extends GlObject {
|
||||
|
||||
GLDebug.nameObject(GL11.GL_TEXTURE, handle(), name);
|
||||
|
||||
GL11.glBindTexture(target.getGlType(), handle());
|
||||
GLStateManager.glBindTexture(target.getGlType(), handle());
|
||||
target.apply(handle(), width, height, depth, internalFormat.getGlFormat(), format.getGlFormat(), pixelType.getGlFormat(), null);
|
||||
|
||||
int texture = handle();
|
||||
final int texture = handle();
|
||||
|
||||
setup(texture, width, height, depth);
|
||||
|
||||
GL11.glBindTexture(target.getGlType(), 0);
|
||||
GLStateManager.glBindTexture(target.getGlType(), 0);
|
||||
}
|
||||
|
||||
protected void setup(int texture, int width, int height, int depth) {
|
||||
boolean isInteger = internalTextureFormat.getPixelFormat().isInteger();
|
||||
final boolean isInteger = internalTextureFormat.getPixelFormat().isInteger();
|
||||
RenderSystem.texParameteri(texture, target.getGlType(), GL11.GL_TEXTURE_MIN_FILTER, isInteger ? GL11.GL_NEAREST : GL11.GL_LINEAR);
|
||||
RenderSystem.texParameteri(texture, target.getGlType(), GL11.GL_TEXTURE_MAG_FILTER, isInteger ? GL11.GL_NEAREST : GL11.GL_LINEAR);
|
||||
RenderSystem.texParameteri(texture, target.getGlType(), GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
@@ -139,14 +139,14 @@ public class GlImage extends GlObject {
|
||||
|
||||
@Override
|
||||
public void updateNewSize(int width, int height) {
|
||||
GL11.glBindTexture(target.getGlType(), handle());
|
||||
GLStateManager.glBindTexture(target.getGlType(), handle());
|
||||
target.apply(handle(), (int) (width * relativeWidth), (int) (height * relativeHeight), 0, internalTextureFormat.getGlFormat(), format.getGlFormat(), pixelType.getGlFormat(), null);
|
||||
|
||||
int texture = handle();
|
||||
final int texture = handle();
|
||||
|
||||
setup(texture, width, height, 0);
|
||||
|
||||
GL11.glBindTexture(target.getGlType(), 0);
|
||||
GLStateManager.glBindTexture(target.getGlType(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import net.minecraft.world.biome.BiomeGenBase;
|
||||
import org.lwjgl.LWJGLUtil;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -262,7 +263,11 @@ public class StandardMacros {
|
||||
* @see <a href="https://github.com/sp614x/optifine/blob/9c6a5b5326558ccc57c6490b66b3be3b2dc8cbef/OptiFineDoc/doc/shaders.txt#L735-L738">Optifine Doc</a>
|
||||
*/
|
||||
public static Set<String> getGlExtensions() {
|
||||
String[] extensions = Objects.requireNonNull(GL11.glGetString(GL11.GL_EXTENSIONS)).split("\\s+");
|
||||
int numExtensions = GL11.glGetInteger(GL30.GL_NUM_EXTENSIONS);
|
||||
String[] extensions = new String[numExtensions];
|
||||
for (int i = 0; i < numExtensions; i++) {
|
||||
extensions[i] = GL30.glGetStringi(GL11.GL_EXTENSIONS, i);
|
||||
}
|
||||
|
||||
// TODO note that we do not add extensions based on if the shader uses them and if they are supported
|
||||
// see https://github.com/sp614x/optifine/blob/master/OptiFineDoc/doc/shaders.txt#L738
|
||||
|
||||
@@ -54,7 +54,7 @@ public class GlTexture extends GlObject implements TextureAccess {
|
||||
}
|
||||
|
||||
public static void bindTextureForSetup(int glType, int glId) {
|
||||
GL11.glBindTexture(glType, glId);
|
||||
GLStateManager.glBindTexture(glType, glId);
|
||||
}
|
||||
|
||||
public TextureType getTarget() {
|
||||
|
||||
@@ -8,7 +8,6 @@ import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.Gui;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
/**
|
||||
* Class serving as abstraction and
|
||||
@@ -59,7 +58,7 @@ public final class GuiUtil {
|
||||
final int vOffset = disabled ? 46 : hovered ? 86 : 66;
|
||||
|
||||
// Sets RenderSystem to use solid white as the tint color for blend mode, and enables blend mode
|
||||
GL14.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.enableBlend();
|
||||
|
||||
// Sets RenderSystem to be able to use textures when drawing
|
||||
@@ -194,7 +193,7 @@ public final class GuiUtil {
|
||||
*/
|
||||
public void draw(int x, int y) {
|
||||
// Sets RenderSystem to use solid white as the tint color for blend mode, and enables blend mode
|
||||
GL14.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.enableBlend();
|
||||
|
||||
// Sets RenderSystem to be able to use textures when drawing
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.coderbot.iris.gui.element;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -129,8 +130,8 @@ public abstract class IrisGuiSlot extends GuiSlot {
|
||||
|
||||
|
||||
this.bindAmountScrolled();
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
GL11.glDisable(GL11.GL_FOG);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glDisable(GL11.GL_FOG);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
drawContainerBackground(tessellator);
|
||||
final int elementRight = this.left + this.width / 2 - this.getListWidth() / 2 + 2;
|
||||
@@ -141,14 +142,14 @@ public abstract class IrisGuiSlot extends GuiSlot {
|
||||
}
|
||||
|
||||
this.drawSelectionBox(elementRight, relativeY, mouseX, mouseY);
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
this.overlayBackground(0, this.top, 255, 255);
|
||||
this.overlayBackground(this.bottom, this.height, 255, 255);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
OpenGlHelper.glBlendFunc(770, 771, 0, 1);
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glShadeModel(GL11.GL_SMOOTH);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorRGBA_I(0, 0);
|
||||
tessellator.addVertexWithUV(this.left, (this.top + offset), 0.0D, 0.0D, 1.0D);
|
||||
@@ -209,9 +210,9 @@ public abstract class IrisGuiSlot extends GuiSlot {
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glShadeModel(GL11.GL_FLAT);
|
||||
GL11.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import cpw.mods.fml.client.config.GuiUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -37,7 +36,7 @@ public class IrisImageButton extends IrisButton {
|
||||
}
|
||||
|
||||
// Sets RenderSystem to use solid white as the tint color for blend mode, and enables blend mode
|
||||
GL14.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.enableBlend();
|
||||
|
||||
// Sets RenderSystem to be able to use textures when drawing
|
||||
|
||||
@@ -34,8 +34,8 @@ public class ClearPass {
|
||||
color = this.color;
|
||||
}
|
||||
|
||||
GL11.glClearColor(color.x, color.y, color.z, color.w);
|
||||
GL11.glClear(clearFlags);
|
||||
GLStateManager.glClearColor(color.x, color.y, color.z, color.w);
|
||||
GLStateManager.glClear(clearFlags);
|
||||
if (Minecraft.isRunningOnMac) {
|
||||
GL11.glGetError();
|
||||
}
|
||||
|
||||
@@ -288,12 +288,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
// Initialize custom images
|
||||
this.customImages = new HashSet<>();
|
||||
final var customImageInfos = programs.getPack().getCustomImages();
|
||||
Iris.logger.debug("[CustomImages] Found {} custom image definitions from shader pack", customImageInfos.size());
|
||||
final List<GlImage> clearList = new ArrayList<>();
|
||||
for (var entry : customImageInfos.object2ObjectEntrySet()) {
|
||||
ImageInformation info = entry.getValue();
|
||||
Iris.logger.debug("[CustomImages] Creating GlImage: {} with info: {}", entry.getKey(), info);
|
||||
GlImage image;
|
||||
final ImageInformation info = entry.getValue();
|
||||
final GlImage image;
|
||||
if (info.isRelative()) {
|
||||
image = new GlImage.Relative(info.name(), info.samplerName(), info.format(), info.internalTextureFormat(),
|
||||
info.type(), info.clear(), info.relativeWidth(), info.relativeHeight(),
|
||||
@@ -302,14 +300,12 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
image = new GlImage(info.name(), info.samplerName(), info.target(), info.format(), info.internalTextureFormat(),
|
||||
info.type(), info.clear(), info.width(), info.height(), info.depth());
|
||||
}
|
||||
Iris.logger.debug("[CustomImages] Created GlImage: {} with texture ID: {}", entry.getKey(), image.getId());
|
||||
customImages.add(image);
|
||||
if (image.shouldClear()) {
|
||||
clearList.add(image);
|
||||
}
|
||||
}
|
||||
this.imagesToClear = clearList.toArray(new GlImage[0]);
|
||||
Iris.logger.debug("[CustomImages] Total GlImages created: {}, images to clear: {}", customImages.size(), imagesToClear.length);
|
||||
|
||||
// Initialize SSBOs
|
||||
final var bufferObjectInfos = programs.getPack().getBufferObjects();
|
||||
@@ -747,7 +743,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return;
|
||||
}
|
||||
|
||||
beginPass(table.match(getCondition(getPhase()), inputs));
|
||||
final RenderCondition condition = getCondition(getPhase());
|
||||
final Pass matched = table.match(condition, inputs);
|
||||
|
||||
beginPass(matched);
|
||||
}
|
||||
|
||||
public void beginPass(Pass pass) {
|
||||
@@ -1117,7 +1116,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
} else {
|
||||
// Clear depth first, regardless of any color clearing.
|
||||
shadowRenderTargets.getDepthSourceFb().bind();
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
if (Minecraft.isRunningOnMac) {
|
||||
GL11.glGetError();
|
||||
}
|
||||
@@ -1467,16 +1466,16 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
// DimensionSpecialEffects.SkyType skyType = Minecraft.getMinecraft().theWorld.effects().skyType();
|
||||
|
||||
if (true/*skyType == DimensionSpecialEffects.SkyType.NORMAL*/) {
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDepthMask(false);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glDepthMask(false);
|
||||
|
||||
final Vector3d fogColor = GLStateManager.getFogColor();
|
||||
GL11.glColor4f((float) fogColor.x, (float) fogColor.y, (float) fogColor.z, 1.0F);
|
||||
GLStateManager.glColor4f((float) fogColor.x, (float) fogColor.y, (float) fogColor.z, 1.0F);
|
||||
|
||||
horizonRenderer.renderHorizon(RenderingState.INSTANCE.getModelViewBuffer());
|
||||
|
||||
GL11.glDepthMask(true);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.coderbot.iris.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.mojang.Camera;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
@@ -17,7 +18,6 @@ import net.coderbot.iris.shaderpack.texture.TextureStage;
|
||||
import net.coderbot.iris.uniforms.FrameUpdateNotifier;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
@@ -35,7 +35,7 @@ public class FixedFunctionWorldRenderingPipeline implements WorldRenderingPipeli
|
||||
public void beginLevelRendering() {
|
||||
// Use the default Minecraft framebuffer and ensure that no programs are in use
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
|
||||
GL20.glUseProgram(0);
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,8 @@ import org.lwjgl.util.glu.Project;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
|
||||
public class HandRenderer {
|
||||
public static final HandRenderer INSTANCE = new HandRenderer();
|
||||
|
||||
@@ -30,30 +32,30 @@ public class HandRenderer {
|
||||
private void setupGlState(RenderGlobal gameRenderer, Camera camera, float tickDelta) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
// We need to scale the matrix by 0.125 so the hand doesn't clip through blocks.
|
||||
GL11.glScalef(1.0F, 1.0F, DEPTH);
|
||||
GLStateManager.glScalef(1.0F, 1.0F, DEPTH);
|
||||
|
||||
// TODO: Anaglyph
|
||||
/*if (this.mc.gameSettings.anaglyph) {
|
||||
GL11.glTranslatef((float) (-(anaglyphChannel * 2 - 1)) * 0.07F, 0.0F, 0.0F);
|
||||
GLStateManager.glTranslatef((float) (-(anaglyphChannel * 2 - 1)) * 0.07F, 0.0F, 0.0F);
|
||||
}*/
|
||||
|
||||
if (mc.entityRenderer.cameraZoom != 1.0D) {
|
||||
GL11.glTranslatef((float) mc.entityRenderer.cameraYaw, (float) (-mc.entityRenderer.cameraPitch), 0.0F);
|
||||
GL11.glScaled(mc.entityRenderer.cameraZoom, mc.entityRenderer.cameraZoom, 1.0D);
|
||||
GLStateManager.glTranslatef((float) mc.entityRenderer.cameraYaw, (float) (-mc.entityRenderer.cameraPitch), 0.0F);
|
||||
GLStateManager.glScaled(mc.entityRenderer.cameraZoom, mc.entityRenderer.cameraZoom, 1.0D);
|
||||
}
|
||||
|
||||
Project.gluPerspective(mc.entityRenderer.getFOVModifier(tickDelta, false), (float) mc.displayWidth / (float) mc.displayHeight, 0.05F, mc.entityRenderer.farPlaneDistance * 2.0F);
|
||||
|
||||
|
||||
if (mc.playerController.enableEverythingIsScrewedUpMode()) {
|
||||
GL11.glScalef(1.0F, 2 / 3f, 1.0F);
|
||||
GLStateManager.glScalef(1.0F, 2 / 3f, 1.0F);
|
||||
}
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GL11.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
// TODO: Anaglyph
|
||||
/*if (mc.gameSettings.anaglyph) {
|
||||
@@ -106,8 +108,8 @@ public class HandRenderer {
|
||||
|
||||
pipeline.setPhase(WorldRenderingPhase.HAND_SOLID);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glDepthMask(true); // actually write to the depth buffer, it's normally disabled at this point
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glDepthMask(true); // actually write to the depth buffer, it's normally disabled at this point
|
||||
|
||||
mc.mcProfiler.startSection("iris_hand");
|
||||
|
||||
@@ -116,13 +118,14 @@ public class HandRenderer {
|
||||
GbufferPrograms.setBlockEntityDefaults();
|
||||
|
||||
renderingSolid = true;
|
||||
|
||||
mc.entityRenderer.enableLightmap(tickDelta);
|
||||
mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta);
|
||||
mc.entityRenderer.disableLightmap(tickDelta);
|
||||
|
||||
GLStateManager.defaultBlendFunc();
|
||||
GL11.glDepthMask(false);
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
mc.mcProfiler.endSection();
|
||||
|
||||
@@ -146,7 +149,7 @@ public class HandRenderer {
|
||||
|
||||
pipeline.setPhase(WorldRenderingPhase.HAND_TRANSLUCENT);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
mc.mcProfiler.startSection("iris_hand_translucent");
|
||||
|
||||
@@ -158,7 +161,7 @@ public class HandRenderer {
|
||||
mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta);
|
||||
mc.entityRenderer.disableLightmap(tickDelta);
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
resetProjectionMatrix();
|
||||
|
||||
@@ -170,10 +173,10 @@ public class HandRenderer {
|
||||
}
|
||||
|
||||
private void resetProjectionMatrix() {
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glMultMatrix(RenderingState.INSTANCE.getProjectionBuffer());
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMultMatrix(RenderingState.INSTANCE.getProjectionBuffer());
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
|
||||
@@ -4,12 +4,10 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
@@ -157,15 +155,15 @@ public class HorizonRenderer {
|
||||
rebuildBuffer();
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glMultMatrix(matrix);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMultMatrix(matrix);
|
||||
|
||||
vao.bind();
|
||||
vao.draw();
|
||||
vao.unbind();
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
|
||||
@@ -2,25 +2,20 @@ package net.coderbot.iris.pipeline;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.Camera;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil;
|
||||
import com.gtnewhorizons.angelica.compat.toremove.MatrixStack;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.gui.option.IrisVideoSettings;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.PackDirectives;
|
||||
import net.coderbot.iris.shaderpack.ShadowCullState;
|
||||
import net.coderbot.iris.shaderpack.PackShadowDirectives;
|
||||
import net.coderbot.iris.shaderpack.ProgramSource;
|
||||
import net.coderbot.iris.shaderpack.ShadowCullState;
|
||||
import net.coderbot.iris.shadow.ShadowMatrices;
|
||||
import net.coderbot.iris.shadows.CullingDataCache;
|
||||
import net.coderbot.iris.shadows.ShadowCompositeRenderer;
|
||||
@@ -32,7 +27,6 @@ import net.coderbot.iris.shadows.frustum.advanced.AdvancedShadowCullingFrustum;
|
||||
import net.coderbot.iris.shadows.frustum.advanced.SafeZoneCullingFrustum;
|
||||
import net.coderbot.iris.shadows.frustum.fallback.BoxCullingFrustum;
|
||||
import net.coderbot.iris.shadows.frustum.fallback.NonCullingFrustum;
|
||||
import net.coderbot.iris.uniforms.CameraUniforms;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.coderbot.iris.uniforms.CelestialUniforms;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -47,7 +41,6 @@ import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.profiler.Profiler;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.ViewportProvider;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3d;
|
||||
@@ -60,6 +53,13 @@ import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ShadowRenderer {
|
||||
public static final Matrix4f MODELVIEW = new Matrix4f();
|
||||
public static final FloatBuffer MODELVIEW_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
@@ -485,14 +485,14 @@ public class ShadowRenderer {
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(1.0f, 1.0f);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glPushMatrix();
|
||||
MODELVIEW_BUFFER.clear().rewind();
|
||||
modelView.peek().getModel().get(MODELVIEW_BUFFER);
|
||||
GL11.glLoadMatrix(MODELVIEW_BUFFER);
|
||||
GLStateManager.glLoadMatrix(MODELVIEW_BUFFER);
|
||||
}
|
||||
|
||||
private void teardownEntityShadowState() {
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
@@ -553,7 +553,7 @@ public class ShadowRenderer {
|
||||
}
|
||||
int brightness = tile.getWorldObj().getLightBrightnessForSkyBlocks(tile.xCoord, tile.yCoord, tile.zCoord, 0);
|
||||
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, (float) brightness % 65536, (float) brightness / 65536);
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
TileEntityRendererDispatcher.instance.renderTileEntityAt(tile,
|
||||
(double)tile.xCoord - cameraX,
|
||||
(double)tile.yCoord - cameraY,
|
||||
@@ -573,10 +573,10 @@ public class ShadowRenderer {
|
||||
culler.setPosition(cameraX, cameraY, cameraZ);
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glPushMatrix();
|
||||
MODELVIEW_BUFFER.clear().rewind();
|
||||
modelView.peek().getModel().get(MODELVIEW_BUFFER);
|
||||
GL11.glLoadMatrix(MODELVIEW_BUFFER);
|
||||
GLStateManager.glLoadMatrix(MODELVIEW_BUFFER);
|
||||
|
||||
GbufferPrograms.beginBlockEntities();
|
||||
GbufferPrograms.setBlockEntityDefaults();
|
||||
|
||||
@@ -12,73 +12,59 @@ import java.util.Map;
|
||||
* transformation methods.
|
||||
*/
|
||||
class AttributeTransformer {
|
||||
public static void transform(Transformer transformer, AttributeParameters parameters, String profile, int version) {
|
||||
boolean isCore = profile.equals("core") || (version > 140 && !profile.equals("compatibility"));
|
||||
|
||||
// Common patches (compatibility profile)
|
||||
CommonTransformer.transform(transformer, parameters, isCore, version);
|
||||
public static void transform(Transformer transformer, AttributeParameters parameters, int version) {
|
||||
// Always core profile — minimum GLSL version is 330 (see ShaderTransformer.getStageMinimumVersion)
|
||||
CommonTransformer.transform(transformer, parameters, true, version);
|
||||
|
||||
// Entity ID and overlay color patching (uniform path)
|
||||
EntityPatcher.patchEntityId(transformer, parameters);
|
||||
EntityPatcher.patchOverlayColor(transformer, parameters);
|
||||
|
||||
if (isCore) {
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
throw new IllegalStateException("Vertex shaders must be in the compatibility profile to run properly!");
|
||||
transformCore(transformer, parameters);
|
||||
}
|
||||
|
||||
private static void transformCore(Transformer transformer, AttributeParameters parameters) {
|
||||
CoreTransformHelper.injectMatrixUniforms(transformer);
|
||||
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 iris_Vertex;");
|
||||
transformer.injectVariable("layout(location = 1) in vec4 iris_Color;");
|
||||
transformer.injectVariable("layout(location = 2) in vec4 iris_MultiTexCoord0;");
|
||||
transformer.injectVariable("layout(location = 3) in vec4 iris_MultiTexCoord1;");
|
||||
transformer.injectVariable("layout(location = 4) in vec3 iris_Normal;");
|
||||
|
||||
transformer.rename("gl_Vertex", "iris_Vertex");
|
||||
transformer.rename("gl_Color", "iris_Color");
|
||||
transformer.rename("gl_Normal", "iris_Normal");
|
||||
|
||||
// ftransform() = gl_ModelViewProjectionMatrix * gl_Vertex
|
||||
transformer.renameFunctionCall("ftransform", "iris_ftransform");
|
||||
transformer.injectFunction("vec4 iris_ftransform() { return (iris_ProjectionMatrix * iris_ModelViewMatrix) * iris_Vertex; }");
|
||||
|
||||
// gl_MultiTexCoord1 and gl_MultiTexCoord2 are both lightmap
|
||||
if (parameters.inputs.lightmap) {
|
||||
transformer.rename("gl_MultiTexCoord2", "gl_MultiTexCoord1");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// gl_MultiTexCoord1 and gl_MultiTexCoord2 are both ways to refer to the
|
||||
// lightmap texture coordinate.
|
||||
// See https://github.com/IrisShaders/Iris/issues/1149
|
||||
if (parameters.inputs.lightmap) {
|
||||
transformer.rename("gl_MultiTexCoord2", "gl_MultiTexCoord1");
|
||||
}
|
||||
Map<String, String> texCoordReplacements = new HashMap<>();
|
||||
if (!parameters.inputs.lightmap) {
|
||||
texCoordReplacements.put("gl_MultiTexCoord1", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
texCoordReplacements.put("gl_MultiTexCoord2", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
}
|
||||
if (!parameters.inputs.texture) {
|
||||
texCoordReplacements.put("gl_MultiTexCoord0", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
}
|
||||
texCoordReplacements.forEach(transformer::replaceExpression);
|
||||
|
||||
Map<String, String> texCoordReplacements = new HashMap<>();
|
||||
if (!parameters.inputs.lightmap) {
|
||||
texCoordReplacements.put("gl_MultiTexCoord1", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
texCoordReplacements.put("gl_MultiTexCoord2", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
}
|
||||
if (!parameters.inputs.texture) {
|
||||
texCoordReplacements.put("gl_MultiTexCoord0", "vec4(240.0, 240.0, 0.0, 1.0)");
|
||||
}
|
||||
texCoordReplacements.forEach(transformer::replaceExpression);
|
||||
// Rename remaining gl_MultiTexCoord references to iris_ versions
|
||||
transformer.rename("gl_MultiTexCoord0", "iris_MultiTexCoord0");
|
||||
transformer.rename("gl_MultiTexCoord1", "iris_MultiTexCoord1");
|
||||
|
||||
patchTextureMatrices(transformer, parameters.inputs.lightmap);
|
||||
|
||||
if (parameters.type == ShaderType.VERTEX && transformer.hasVariable("gl_MultiTexCoord3") && !transformer.hasVariable("mc_midTexCoord")) {
|
||||
transformer.rename("gl_MultiTexCoord3", "mc_midTexCoord");
|
||||
transformer.injectVariable("attribute vec4 mc_midTexCoord;");
|
||||
if (transformer.hasVariable("gl_MultiTexCoord3") && !transformer.hasVariable("mc_midTexCoord")) {
|
||||
transformer.rename("gl_MultiTexCoord3", "mc_midTexCoord");
|
||||
transformer.injectVariable("in vec4 mc_midTexCoord;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void patchTextureMatrices(Transformer transformer, boolean hasLightmap) {
|
||||
transformer.rename("gl_TextureMatrix", "iris_TextureMatrix");
|
||||
|
||||
transformer.injectVariable("float iris_ONE_OVER_256 = 0.00390625;");
|
||||
transformer.injectVariable("float iris_ONE_OVER_32 = iris_ONE_OVER_256 * 8;");
|
||||
|
||||
if (hasLightmap) {
|
||||
transformer.injectVariable("mat4 iris_LightmapTextureMatrix = gl_TextureMatrix[1];");
|
||||
} else {
|
||||
transformer.injectVariable("mat4 iris_LightmapTextureMatrix =" +
|
||||
"mat4(iris_ONE_OVER_256, 0.0, 0.0, 0.0," +
|
||||
" 0.0, iris_ONE_OVER_256, 0.0, 0.0," +
|
||||
" 0.0, 0.0, iris_ONE_OVER_256, 0.0," +
|
||||
" iris_ONE_OVER_32, iris_ONE_OVER_32, iris_ONE_OVER_32, iris_ONE_OVER_256);");
|
||||
}
|
||||
|
||||
transformer.injectVariable("mat4 iris_TextureMatrix[8] = mat4[8](" +
|
||||
"gl_TextureMatrix[0]," +
|
||||
"iris_LightmapTextureMatrix," +
|
||||
"mat4(1.0)," +
|
||||
"mat4(1.0)," +
|
||||
"mat4(1.0)," +
|
||||
"mat4(1.0)," +
|
||||
"mat4(1.0)," +
|
||||
"mat4(1.0)" +
|
||||
");");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import net.coderbot.iris.gl.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
@@ -9,7 +8,8 @@ import java.util.Map;
|
||||
|
||||
class CeleritasTransformer {
|
||||
public static void transform(Transformer transformer, Parameters parameters, int glslVersion) {
|
||||
CommonTransformer.transform(transformer, parameters, false, glslVersion);
|
||||
// Always core profile — minimum GLSL version is 330 (see ShaderTransformer.getStageMinimumVersion)
|
||||
CommonTransformer.transform(transformer, parameters, true, glslVersion);
|
||||
|
||||
switch (parameters.type) {
|
||||
case FRAGMENT:
|
||||
@@ -60,25 +60,6 @@ class CeleritasTransformer {
|
||||
}
|
||||
|
||||
private static void transformShared(Transformer transformer, Parameters parameters) {
|
||||
transformer.injectVariable("uniform mat4 iris_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 iris_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 iris_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_LightmapTextureMatrix;");
|
||||
|
||||
final Map<String, String> renames = new HashMap<>();
|
||||
renames.put("gl_ModelViewMatrix", "iris_ModelViewMatrix");
|
||||
renames.put("gl_ModelViewMatrixInverse", "iris_ModelViewMatrixInverse");
|
||||
renames.put("gl_ProjectionMatrix", "iris_ProjectionMatrix");
|
||||
renames.put("gl_ProjectionMatrixInverse", "iris_ProjectionMatrixInverse");
|
||||
renames.put("gl_NormalMatrix", "iris_NormalMatrix");
|
||||
transformer.rename(renames);
|
||||
|
||||
final Map<String, String> sharedReplacements = new HashMap<>();
|
||||
sharedReplacements.put("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
sharedReplacements.put("gl_TextureMatrix[1]", "iris_LightmapTextureMatrix");
|
||||
sharedReplacements.put("gl_ModelViewProjectionMatrix", "(iris_ProjectionMatrix * iris_ModelViewMatrix)");
|
||||
sharedReplacements.forEach(transformer::replaceExpression);
|
||||
CoreTransformHelper.injectMatrixUniforms(transformer);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,15 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import net.coderbot.iris.gl.blending.AlphaTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import net.coderbot.iris.gl.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class CommonTransformer {
|
||||
|
||||
private static final Map<String, String> COMMON_TEXTURE_RENAMES = Map.ofEntries(
|
||||
Map.entry("texture2D", "texture"),
|
||||
Map.entry("texture3D", "texture"),
|
||||
Map.entry("texture2DLod", "textureLod"),
|
||||
Map.entry("texture3DLod", "textureLod"),
|
||||
Map.entry("texture2DProj", "textureProj"),
|
||||
Map.entry("texture3DProj", "textureProj"),
|
||||
Map.entry("texture2DGrad", "textureGrad"),
|
||||
Map.entry("texture2DGradARB", "textureGrad"),
|
||||
Map.entry("texture3DGrad", "textureGrad"),
|
||||
Map.entry("texelFetch2D", "texelFetch"),
|
||||
Map.entry("texelFetch3D", "texelFetch"),
|
||||
Map.entry("textureSize2D", "textureSize")
|
||||
);
|
||||
|
||||
public static void transform(Transformer root, Parameters parameters, boolean core, int glslVersion) {
|
||||
root.rename("gl_FogFragCoord", "iris_FogFragCoord");
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
@@ -34,8 +20,12 @@ public class CommonTransformer {
|
||||
}
|
||||
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("vec4 iris_FrontColor;");
|
||||
root.injectVariable("out vec4 iris_FrontColor;");
|
||||
root.rename("gl_FrontColor", "iris_FrontColor");
|
||||
root.prependMain("iris_FrontColor = vec4(1.0);");
|
||||
} else if (parameters.type == ShaderType.FRAGMENT) {
|
||||
root.injectVariable("in vec4 iris_FrontColor;");
|
||||
root.rename("gl_Color", "iris_FrontColor");
|
||||
}
|
||||
|
||||
if (parameters.type == ShaderType.FRAGMENT) {
|
||||
@@ -43,10 +33,21 @@ public class CommonTransformer {
|
||||
root.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
// Note: In compatibility profile, gl_FragData is a built-in array.
|
||||
if (!core && parameters.getAlphaTest() != AlphaTest.ALWAYS) {
|
||||
root.injectVariable("uniform float iris_currentAlphaTest;");
|
||||
root.appendMain(parameters.getAlphaTest().toExpression("gl_FragData[0].a", "iris_currentAlphaTest", ""));
|
||||
if (core) {
|
||||
// Core profile: gl_FragData doesn't exist. Flatten gl_FragData[N] → iris_FragDataN with layout-qualified out declarations.
|
||||
Set<Integer> found = new HashSet<>();
|
||||
root.renameArray("gl_FragData", "iris_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
root.injectVariable("layout (location = " + i + ") out vec4 iris_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed. 1.7.10 engine relies on alpha test to discard transparent fragments. Inject
|
||||
// runtime discard using the GLSM-tracked alpha reference value.
|
||||
if (found.contains(0) && parameters.patch != Patch.COMPOSITE && parameters.patch != Patch.COMPUTE) {
|
||||
root.injectVariable("uniform float iris_currentAlphaTest;");
|
||||
root.appendMain("if (iris_FragData0.a <= iris_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +55,8 @@ public class CommonTransformer {
|
||||
root.rename("texture", "gtexture");
|
||||
}
|
||||
|
||||
if (root.hasVariable("iris_renamed_texture")) {
|
||||
root.rename("iris_renamed_texture", "gtexture");
|
||||
if (root.hasVariable("angelica_renamed_texture")) {
|
||||
root.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
if (root.containsCall("gcolor") && root.hasVariable("gcolor")) {
|
||||
@@ -70,11 +71,24 @@ public class CommonTransformer {
|
||||
root.injectFunction("struct iris_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
root.injectFunction("iris_FogParameters iris_Fog = iris_FogParameters(iris_FogColor, iris_FogDensity, iris_FogStart, iris_FogEnd, 1.0f / (iris_FogEnd - iris_FogStart));");
|
||||
|
||||
// GLSL 120 natively uses texture2D/shadow2D etc. Ttexture/textureLod don't exist in 120, so only rename for versions that support them.
|
||||
if (glslVersion > 120) {
|
||||
root.renameFunctionCall(COMMON_TEXTURE_RENAMES);
|
||||
root.renameAndWrapShadow("shadow2D", "texture");
|
||||
root.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
root.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
root.renameAndWrapShadow("shadow2D", "texture");
|
||||
root.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
|
||||
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
|
||||
root.appendMain(
|
||||
"{ if (angelica_ClipPlanesEnabled) { vec4 _cp_ep = iris_ModelViewMatrix * iris_Vertex; "
|
||||
+ "gl_ClipDistance[0] = dot(angelica_ClipPlane[0], _cp_ep); "
|
||||
+ "gl_ClipDistance[1] = dot(angelica_ClipPlane[1], _cp_ep); "
|
||||
+ "gl_ClipDistance[2] = dot(angelica_ClipPlane[2], _cp_ep); "
|
||||
+ "gl_ClipDistance[3] = dot(angelica_ClipPlane[3], _cp_ep); "
|
||||
+ "gl_ClipDistance[4] = dot(angelica_ClipPlane[4], _cp_ep); "
|
||||
+ "gl_ClipDistance[5] = dot(angelica_ClipPlane[5], _cp_ep); "
|
||||
+ "gl_ClipDistance[6] = dot(angelica_ClipPlane[6], _cp_ep); "
|
||||
+ "gl_ClipDistance[7] = dot(angelica_ClipPlane[7], _cp_ep); } }"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import net.coderbot.iris.gl.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
class CompositeDepthTransformer {
|
||||
|
||||
public static void transform(Transformer transformer, Parameters parameters, int glslVersion) {
|
||||
CommonTransformer.transform(transformer, parameters, false, glslVersion);
|
||||
CommonTransformer.transform(transformer, parameters, true, glslVersion);
|
||||
|
||||
int type = transformer.findType("centerDepthSmooth");
|
||||
CoreTransformHelper.injectMatrixUniforms(transformer);
|
||||
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
CoreTransformHelper.injectCompositeVertexAttributes(transformer);
|
||||
}
|
||||
|
||||
final int type = transformer.findType("centerDepthSmooth");
|
||||
if (type != 0) {
|
||||
transformer.injectVariable("uniform sampler2D iris_centerDepthSmooth;");
|
||||
transformer.replaceExpression("centerDepthSmooth", "texture2D(iris_centerDepthSmooth, vec2(0.5)).r");
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shared core-profile transformation logic for matrix uniforms, texture matrices, and vertex attribute replacement.
|
||||
*/
|
||||
class CoreTransformHelper {
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename FFP matrix builtins to iris_* equivalents.
|
||||
* Handles: ModelView, ModelViewInverse, Projection, ProjectionInverse, NormalMatrix, ModelViewProjectionMatrix, TextureMatrix[0..1], and LightmapTextureMatrix.
|
||||
*/
|
||||
static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 iris_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 iris_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 iris_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 iris_LightmapTextureMatrix;");
|
||||
|
||||
final Map<String, String> renames = new HashMap<>();
|
||||
renames.put("gl_ModelViewMatrix", "iris_ModelViewMatrix");
|
||||
renames.put("gl_ModelViewMatrixInverse", "iris_ModelViewMatrixInverse");
|
||||
renames.put("gl_ProjectionMatrix", "iris_ProjectionMatrix");
|
||||
renames.put("gl_ProjectionMatrixInverse", "iris_ProjectionMatrixInverse");
|
||||
renames.put("gl_NormalMatrix", "iris_NormalMatrix");
|
||||
transformer.rename(renames);
|
||||
|
||||
final Map<String, String> replacements = new HashMap<>();
|
||||
replacements.put("gl_ModelViewProjectionMatrix", "(iris_ProjectionMatrix * iris_ModelViewMatrix)");
|
||||
replacements.put("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
replacements.put("gl_TextureMatrix[1]", "iris_LightmapTextureMatrix");
|
||||
replacements.forEach(transformer::replaceExpression);
|
||||
|
||||
// Catch any remaining gl_TextureMatrix references (e.g. [2]-[7])
|
||||
transformer.replaceExpression("gl_TextureMatrix", "mat4[8](mat4(1.0), iris_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject vertex attributes and ftransform() replacement for composite/depth shaders. Uses locations matching FullScreenQuadRenderer's POSITION_TEXTURE VAO layout.
|
||||
*/
|
||||
static void injectCompositeVertexAttributes(Transformer transformer) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 iris_Vertex;");
|
||||
transformer.injectVariable("layout(location = 2) in vec4 iris_MultiTexCoord0;");
|
||||
|
||||
transformer.rename("gl_Vertex", "iris_Vertex");
|
||||
transformer.rename("gl_MultiTexCoord0", "iris_MultiTexCoord0");
|
||||
|
||||
transformer.renameFunctionCall("ftransform", "iris_ftransform");
|
||||
transformer.injectFunction("vec4 iris_ftransform() { return (iris_ProjectionMatrix * iris_ModelViewMatrix) * iris_Vertex; }");
|
||||
|
||||
ShaderTransformer.applyIntelHd4000Workaround(transformer);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,25 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.gtnewhorizons.angelica.glsm.CompatShaderTransformer;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.IrisExtendedChunkVertexType;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
|
||||
import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.StorageCollector;
|
||||
import org.taumc.glsl.Transformer;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -37,11 +29,6 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class ShaderTransformer {
|
||||
private static final Pattern versionPattern = Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
|
||||
private static final Pattern inOutVaryingPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(in|out)(\\s+)");
|
||||
private static final Pattern inPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(in)(\\s+)");
|
||||
private static final Pattern outPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(out)(\\s+)");
|
||||
private static final Pattern texturePattern = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
||||
private static final Pattern unsignedSuffixPattern = Pattern.compile("(\\b(?:\\d+|0[xX][0-9a-fA-F]+))[uU]\\b");
|
||||
|
||||
private static final int CACHE_SIZE = 100;
|
||||
private static final Object2ObjectLinkedOpenHashMap<TransformKey<?>, Map<PatchShaderType, String>> shaderTransformationCache = new Object2ObjectLinkedOpenHashMap<>();
|
||||
@@ -57,26 +44,6 @@ public class ShaderTransformer {
|
||||
loggedNegotiations.clear();
|
||||
}
|
||||
|
||||
private static final Map<Integer, List<String>> versionedReservedWords = new HashMap<>();
|
||||
|
||||
private static String replaceTexture(String input) {
|
||||
final var matcher = texturePattern.matcher(input);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
matcher.appendReplacement(builder, "iris_renamed_texture");
|
||||
} else {
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static {
|
||||
// sample was added as a keyword in GLSL 400, many shaders use it
|
||||
versionedReservedWords.put(400, List.of("sample"));
|
||||
}
|
||||
|
||||
private record VersionRequirement(String keyword, int minVersion, BooleanSupplier supported) {}
|
||||
|
||||
@@ -97,68 +64,13 @@ public class ShaderTransformer {
|
||||
};
|
||||
|
||||
|
||||
private record DowngradeRule(
|
||||
int fromVersion,
|
||||
int toVersion,
|
||||
BooleanSupplier supported,
|
||||
List<String> extensions,
|
||||
Map<String, String> defines,
|
||||
List<String> preprocessorDefines,
|
||||
boolean convertStorageQualifiers
|
||||
) {}
|
||||
|
||||
private static final DowngradeRule[] DOWNGRADE_RULES = {
|
||||
new DowngradeRule(130, 120,
|
||||
RenderSystem::supportsGpuShader4,
|
||||
List.of("GL_EXT_gpu_shader4"),
|
||||
Map.of("texture", "texture2D"),
|
||||
List.of(
|
||||
// Preprocessor defines
|
||||
"#define uint int",
|
||||
"#define uvec2 ivec2",
|
||||
"#define uvec3 ivec3",
|
||||
"#define uvec4 ivec4",
|
||||
"#define isnan(x) ((x) != (x))",
|
||||
"#define isinf(x) ((x) == (1.0/0.0) || (x) == (-1.0/0.0))",
|
||||
"#define trunc(x) (sign(x) * floor(abs(x)))",
|
||||
"#define round(x) (floor((x) + 0.5))",
|
||||
"#define texelFetch texelFetch2D",
|
||||
"#define textureSize textureSize2D",
|
||||
"#define modf iris_modf",
|
||||
// Function definitions
|
||||
"GLSL_FUNC:float iris_modf(float x, out float i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
||||
"GLSL_FUNC:vec2 iris_modf(vec2 x, out vec2 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
||||
"GLSL_FUNC:vec3 iris_modf(vec3 x, out vec3 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
||||
"GLSL_FUNC:vec4 iris_modf(vec4 x, out vec4 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
||||
"GLSL_FUNC:vec2 mix(vec2 a, vec2 b, bvec2 sel) { return vec2(sel.x ? b.x : a.x, sel.y ? b.y : a.y); }",
|
||||
"GLSL_FUNC:vec3 mix(vec3 a, vec3 b, bvec3 sel) { return vec3(sel.x ? b.x : a.x, sel.y ? b.y : a.y, sel.z ? b.z : a.z); }",
|
||||
"GLSL_FUNC:vec4 mix(vec4 a, vec4 b, bvec4 sel) { return vec4(sel.x ? b.x : a.x, sel.y ? b.y : a.y, sel.z ? b.z : a.z, sel.w ? b.w : a.w); }",
|
||||
"GLSL_FUNC:bool any(bool b) { return b; }",
|
||||
"GLSL_FUNC:bool all(bool b) { return b; }"
|
||||
),
|
||||
true
|
||||
)
|
||||
};
|
||||
|
||||
record NegotiationResult(
|
||||
int targetVersion,
|
||||
String profile,
|
||||
List<String> extensions,
|
||||
Map<String, String> defines,
|
||||
List<String> preprocessorDefines,
|
||||
boolean convertStorageQualifiers,
|
||||
String error
|
||||
) {
|
||||
static NegotiationResult success(int targetVersion, String profile, List<String> extensions, Map<String, String> defines, List<String> preprocessorDefines, boolean convertStorageQualifiers) {
|
||||
return new NegotiationResult(targetVersion, profile, extensions, defines, preprocessorDefines, convertStorageQualifiers, null);
|
||||
}
|
||||
|
||||
record NegotiationResult(int targetVersion, String profile, String error) {
|
||||
static NegotiationResult error(String message) {
|
||||
return new NegotiationResult(-1, "", List.of(), Map.of(), List.of(), false, message);
|
||||
return new NegotiationResult(-1, "", message);
|
||||
}
|
||||
|
||||
static NegotiationResult noop(int version, String profile) {
|
||||
return new NegotiationResult(version, profile, List.of(), Map.of(), List.of(), false, null);
|
||||
return new NegotiationResult(version, profile, null);
|
||||
}
|
||||
|
||||
boolean isError() { return error != null; }
|
||||
@@ -168,8 +80,8 @@ public class ShaderTransformer {
|
||||
return switch (stage) {
|
||||
case COMPUTE -> 330;
|
||||
case TESS_CONTROL, TESS_EVAL -> 400;
|
||||
case GEOMETRY -> 150;
|
||||
default -> 110;
|
||||
case GEOMETRY -> 330;
|
||||
default -> 330;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,17 +89,7 @@ public class ShaderTransformer {
|
||||
final int maxGlsl = RenderSystem.getMaxGlslVersion();
|
||||
|
||||
if (effectiveVersion <= maxGlsl) {
|
||||
if (effectiveVersion <= 120 && RenderSystem.supportsGpuShader4()) {
|
||||
final String profile = "";
|
||||
for (DowngradeRule rule : DOWNGRADE_RULES) {
|
||||
if (rule.fromVersion == 130 && rule.toVersion == 120) {
|
||||
final List<String> definesOnly = rule.preprocessorDefines.stream().filter(d -> !d.startsWith("GLSL_FUNC:")).toList();
|
||||
return NegotiationResult.success(effectiveVersion, profile, rule.extensions, rule.defines, definesOnly, true);
|
||||
}
|
||||
}
|
||||
return NegotiationResult.success(effectiveVersion, profile, List.of("GL_EXT_gpu_shader4"), Map.of(), List.of(), true);
|
||||
}
|
||||
return NegotiationResult.noop(effectiveVersion, effectiveVersion >= 150 ? "compatibility" : "");
|
||||
return NegotiationResult.noop(effectiveVersion, effectiveVersion >= 150 ? "core" : "");
|
||||
}
|
||||
|
||||
final int stageMin = getStageMinimumVersion(stage);
|
||||
@@ -195,44 +97,7 @@ public class ShaderTransformer {
|
||||
return NegotiationResult.error("Hardware GLSL " + maxGlsl + " below stage minimum " + stageMin + " for " + stage.name());
|
||||
}
|
||||
|
||||
// Walk downgrade rules from effectiveVersion toward maxGlsl
|
||||
final List<String> accExtensions = new ArrayList<>();
|
||||
final Map<String, String> accDefines = new HashMap<>();
|
||||
final List<String> accPreprocessorDefines = new ArrayList<>();
|
||||
boolean accConvertQualifiers = false;
|
||||
int currentVersion = effectiveVersion;
|
||||
|
||||
while (currentVersion > maxGlsl) {
|
||||
DowngradeRule matched = null;
|
||||
for (DowngradeRule rule : DOWNGRADE_RULES) {
|
||||
if (rule.fromVersion == currentVersion) {
|
||||
matched = rule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched == null) {
|
||||
return NegotiationResult.error("No downgrade rule from GLSL " + currentVersion + " (hardware max: " + maxGlsl + ", shader requires: " + effectiveVersion + ")");
|
||||
}
|
||||
|
||||
// Verify rule's required extensions are supported
|
||||
if (!matched.supported.getAsBoolean()) {
|
||||
return NegotiationResult.error("Downgrade from " + matched.fromVersion + " to " + matched.toVersion + " requires extensions " + matched.extensions + " which are not supported");
|
||||
}
|
||||
|
||||
accExtensions.addAll(matched.extensions);
|
||||
accDefines.putAll(matched.defines);
|
||||
accPreprocessorDefines.addAll(matched.preprocessorDefines);
|
||||
accConvertQualifiers |= matched.convertStorageQualifiers;
|
||||
currentVersion = matched.toVersion;
|
||||
}
|
||||
|
||||
if (currentVersion < stageMin) {
|
||||
return NegotiationResult.error("Downgrade reached GLSL " + currentVersion + " which is below stage minimum " + stageMin + " for " + stage.name());
|
||||
}
|
||||
|
||||
final String profile = currentVersion >= 150 ? "compatibility" : "";
|
||||
return NegotiationResult.success(currentVersion, profile, accExtensions, accDefines, accPreprocessorDefines, accConvertQualifiers);
|
||||
return NegotiationResult.error("Shader requires GLSL " + effectiveVersion + " but hardware max is " + maxGlsl);
|
||||
}
|
||||
|
||||
private static Pattern hoistPattern;
|
||||
@@ -433,39 +298,26 @@ public class ShaderTransformer {
|
||||
versionString = String.valueOf(versionInt);
|
||||
}
|
||||
|
||||
String profileString = "#version " + versionString + " core\n";
|
||||
final String profileString = "#version " + versionString + " core\n";
|
||||
|
||||
// Rename reserved words
|
||||
String input = replaceTexture(compute);
|
||||
for (int version : versionedReservedWords.keySet()) {
|
||||
if (versionInt < version) {
|
||||
for (String reservedWord : versionedReservedWords.get(version)) {
|
||||
final String newName = "iris_renamed_" + reservedWord;
|
||||
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pre-parse reserved word renaming
|
||||
String input = GlslTransformUtils.replaceTexture(compute);
|
||||
input = GlslTransformUtils.renameReservedWords(input, versionInt);
|
||||
|
||||
final var parsedShader = ShaderParser.parseShader(input);
|
||||
final var transformer = new Transformer(parsedShader.full());
|
||||
|
||||
doTransform(transformer, patchType, parameters, "core", versionInt);
|
||||
doTransform(transformer, patchType, parameters, versionInt);
|
||||
|
||||
// Extract extensions
|
||||
final var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
final var extensions = versionPattern.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
final String finalHeader = profileString + (extensions.isEmpty() ? "" : "\n" + extensions);
|
||||
final StringBuilder formattedShaderBuilder = new StringBuilder();
|
||||
|
||||
transformer.mutateTree(tree -> {
|
||||
formattedShaderBuilder.append(getFormattedShader(tree, finalHeader));
|
||||
});
|
||||
transformer.mutateTree(tree -> formattedShaderBuilder.append(GlslTransformUtils.getFormattedShader(tree, finalHeader)));
|
||||
|
||||
String formattedShader = formattedShaderBuilder.toString();
|
||||
|
||||
// Restore identifiers that were temporarily renamed
|
||||
formattedShader = formattedShader.replace("iris_renamed_texture", "texture");
|
||||
formattedShader = formattedShader.replace("iris_renamed_sample", "sample");
|
||||
String formattedShader = GlslTransformUtils.restoreReservedWords(formattedShaderBuilder.toString());
|
||||
|
||||
result.put(PatchShaderType.COMPUTE, formattedShader);
|
||||
|
||||
@@ -478,8 +330,6 @@ public class ShaderTransformer {
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
||||
final EnumMap<PatchShaderType, Transformer> types = new EnumMap<>(PatchShaderType.class);
|
||||
final EnumMap<PatchShaderType, String> prepatched = new EnumMap<>(PatchShaderType.class);
|
||||
final EnumMap<PatchShaderType, NegotiationResult> negotiations = new EnumMap<>(PatchShaderType.class);
|
||||
final EnumMap<PatchShaderType, Boolean> needsTextureLodExtension = new EnumMap<>(PatchShaderType.class);
|
||||
|
||||
final Stopwatch watch = Stopwatch.createStarted();
|
||||
|
||||
@@ -501,7 +351,6 @@ public class ShaderTransformer {
|
||||
continue;
|
||||
}
|
||||
|
||||
String profile = "";
|
||||
int versionInt = Integer.parseInt(versionString);
|
||||
|
||||
// Include celeritas header in scan — it's injected post-negotiation but contains uint/uvec3
|
||||
@@ -513,62 +362,36 @@ public class ShaderTransformer {
|
||||
versionString = String.valueOf(versionInt);
|
||||
}
|
||||
|
||||
// Negotiate version downgrade if needed
|
||||
// Ensure minimum version for this stage (330 for most, 400 for tessellation)
|
||||
final int stageMin = getStageMinimumVersion(type);
|
||||
if (versionInt < stageMin) {
|
||||
versionInt = stageMin;
|
||||
versionString = String.valueOf(versionInt);
|
||||
}
|
||||
|
||||
// Negotiate version if needed (error if hardware can't support)
|
||||
final NegotiationResult negotiation = negotiateVersion(versionInt, type);
|
||||
if (negotiation.isError()) {
|
||||
throw new RuntimeException("Shader version negotiation failed for " + type.name() + ": " + negotiation.error());
|
||||
}
|
||||
|
||||
if (negotiation.targetVersion() != versionInt) {
|
||||
String negotiationKey = type.name() + "_" + versionInt + "_" + negotiation.targetVersion();
|
||||
if (loggedNegotiations.add(negotiationKey)) {
|
||||
Iris.logger.info("Negotiated {} shader from GLSL {} to {} (extensions: {}, polyfills: {})", type.name(), versionInt, negotiation.targetVersion(), negotiation.extensions(), negotiation.preprocessorDefines().size());
|
||||
}
|
||||
versionInt = negotiation.targetVersion();
|
||||
versionString = String.valueOf(versionInt);
|
||||
profile = negotiation.profile();
|
||||
} else {
|
||||
if (versionInt >= 150) {
|
||||
profile = matcher.group(2);
|
||||
if (profile == null) {
|
||||
profile = "compatibility";
|
||||
}
|
||||
}
|
||||
}
|
||||
negotiations.put(type, negotiation);
|
||||
// All stages >= 330 use core profile
|
||||
final String profile = "core";
|
||||
final String profileString = "#version " + versionString + " " + profile + "\n";
|
||||
|
||||
final String profileString = "#version " + versionString + (profile.isEmpty() ? "" : " " + profile) + "\n";
|
||||
|
||||
input = replaceTexture(input);
|
||||
// The primary reason we rename words here using regex, is because if the words cause invalid
|
||||
// GLSL, regardless of the version being used, it will cause glsl-transformation-lib to fail
|
||||
// so we need to rename them prior to passing the shader input to glsl-transformation-lib.
|
||||
for (int version : versionedReservedWords.keySet()) {
|
||||
if (versionInt < version) {
|
||||
for (String reservedWord : versionedReservedWords.get(version)) {
|
||||
final String newName = "iris_renamed_" + reservedWord;
|
||||
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pre-parse reserved word renaming — prevents ANTLR parse failures
|
||||
input = GlslTransformUtils.replaceTexture(input);
|
||||
input = GlslTransformUtils.renameReservedWords(input, versionInt);
|
||||
input = CompatShaderTransformer.fixupQualifiers(input, parameters.type == ShaderType.FRAGMENT);
|
||||
|
||||
final var parsedShader = ShaderParser.parseShader(input);
|
||||
final var transformer = new Transformer(parsedShader.full());
|
||||
|
||||
if (parameters.type == ShaderType.VERTEX || parameters.type == ShaderType.FRAGMENT) {
|
||||
upgradeStorageQualifiers(transformer, parameters);
|
||||
}
|
||||
|
||||
doTransform(transformer, patchType, parameters, profile, versionInt);
|
||||
|
||||
// Check if we need to patch in texture LOD extension enabling
|
||||
if (versionInt <= 120 && (transformer.containsCall("texture2DLod") || transformer.containsCall("texture3DLod") || transformer.containsCall("texture2DGradARB"))) {
|
||||
needsTextureLodExtension.put(type, true);
|
||||
}
|
||||
doTransform(transformer, patchType, parameters, versionInt);
|
||||
|
||||
// Extract extensions from the pre-parsed content (version + extensions before main code)
|
||||
// This preserves #extension directives that the shader pack declares
|
||||
final var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
final var extensions = versionPattern.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
types.put(type, transformer);
|
||||
prepatched.put(type, profileString + (extensions.isEmpty() ? "" : "\n" + extensions));
|
||||
@@ -578,7 +401,6 @@ public class ShaderTransformer {
|
||||
final PatchShaderType shaderType = entry.getKey();
|
||||
final Transformer transformer = entry.getValue();
|
||||
String header = prepatched.get(shaderType);
|
||||
final NegotiationResult negotiation = negotiations.get(shaderType);
|
||||
|
||||
// For Celeritas terrain vertex shaders, inject chunk_vertex.glsl header
|
||||
if (patchType == Patch.CELERITAS_TERRAIN && shaderType == PatchShaderType.VERTEX) {
|
||||
@@ -588,46 +410,9 @@ public class ShaderTransformer {
|
||||
final String finalHeader = header;
|
||||
final StringBuilder formattedShaderBuilder = new StringBuilder();
|
||||
|
||||
transformer.mutateTree(tree -> {
|
||||
formattedShaderBuilder.append(getFormattedShader(tree, finalHeader));
|
||||
});
|
||||
transformer.mutateTree(tree -> formattedShaderBuilder.append(GlslTransformUtils.getFormattedShader(tree, finalHeader)));
|
||||
|
||||
String formattedShader = formattedShaderBuilder.toString();
|
||||
|
||||
// Restore identifiers that were temporarily renamed to dodge GLSL reserved keywords.
|
||||
formattedShader = formattedShader.replace("iris_renamed_texture", "texture");
|
||||
formattedShader = formattedShader.replace("iris_renamed_sample", "sample");
|
||||
|
||||
// Inject texture LOD extension if needed
|
||||
if (needsTextureLodExtension.containsKey(shaderType)) {
|
||||
final String[] parts = formattedShader.split("\n", 2);
|
||||
parts[1] = "#extension GL_ARB_shader_texture_lod : require\n" + parts[1];
|
||||
formattedShader = parts[0] + "\n" + parts[1];
|
||||
}
|
||||
|
||||
if (negotiation != null && (!negotiation.extensions().isEmpty() || !negotiation.preprocessorDefines().isEmpty())) {
|
||||
formattedShader = injectGlslPreamble(formattedShader, negotiation.extensions(), negotiation.preprocessorDefines());
|
||||
}
|
||||
|
||||
if (negotiation != null && !negotiation.defines().isEmpty()) {
|
||||
for (var defineEntry : negotiation.defines().entrySet()) {
|
||||
formattedShader = formattedShader.replaceAll("\\b" + Pattern.quote(defineEntry.getKey()) + "\\b", Matcher.quoteReplacement(defineEntry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert storage qualifiers for downgraded shaders or native 120 shaders
|
||||
if (negotiation != null && (negotiation.convertStorageQualifiers() || negotiation.targetVersion() <= 120)) {
|
||||
if (shaderType == PatchShaderType.VERTEX) {
|
||||
final Matcher inMatcher = inPattern.matcher(formattedShader);
|
||||
formattedShader = inMatcher.replaceAll("$1attribute$3");
|
||||
final Matcher outMatcher = outPattern.matcher(formattedShader);
|
||||
formattedShader = outMatcher.replaceAll("$1varying$3");
|
||||
} else {
|
||||
final Matcher inOutVaryingMatcher = inOutVaryingPattern.matcher(formattedShader);
|
||||
formattedShader = inOutVaryingMatcher.replaceAll("$1varying$3");
|
||||
}
|
||||
formattedShader = unsignedSuffixPattern.matcher(formattedShader).replaceAll("$1");
|
||||
}
|
||||
String formattedShader = GlslTransformUtils.restoreReservedWords(formattedShaderBuilder.toString());
|
||||
|
||||
result.put(shaderType, formattedShader);
|
||||
}
|
||||
@@ -636,7 +421,7 @@ public class ShaderTransformer {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void doTransform(Transformer transformer, Patch patchType, Parameters parameters, String profile, int versionInt) {
|
||||
private static void doTransform(Transformer transformer, Patch patchType, Parameters parameters, int versionInt) {
|
||||
switch (patchType) {
|
||||
case CELERITAS_TERRAIN:
|
||||
CeleritasTransformer.transform(transformer, parameters, versionInt);
|
||||
@@ -649,7 +434,7 @@ public class ShaderTransformer {
|
||||
CompositeDepthTransformer.transform(transformer, parameters, versionInt);
|
||||
break;
|
||||
case ATTRIBUTES:
|
||||
AttributeTransformer.transform(transformer, (AttributeParameters) parameters, profile, versionInt);
|
||||
AttributeTransformer.transform(transformer, (AttributeParameters) parameters, versionInt);
|
||||
break;
|
||||
case COMPUTE:
|
||||
ComputeTransformer.transform(transformer, parameters, versionInt);
|
||||
@@ -729,126 +514,5 @@ public class ShaderTransformer {
|
||||
return "\n\n" + chunkVertexHeader + "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts GLSL 120 storage qualifiers (varying/attribute) to modern in/out tokens so downstream AST transforms (e.g. CompatibilityTransformer) can find them.
|
||||
* The hacky120Patches regex pass converts them back for the final output.
|
||||
*/
|
||||
public static void upgradeStorageQualifiers(Transformer root, Parameters parameters) {
|
||||
final List<TerminalNode> tokens = new ArrayList<>();
|
||||
root.mutateTree(tree -> ParseTreeWalker.DEFAULT.walk(new StorageCollector(tokens), tree));
|
||||
|
||||
for (TerminalNode node : tokens) {
|
||||
if (!(node.getSymbol() instanceof CommonToken token)) {
|
||||
return;
|
||||
}
|
||||
if (token.getType() == GLSLParser.ATTRIBUTE) {
|
||||
token.setType(GLSLParser.IN);
|
||||
token.setText(GLSLParser.VOCABULARY.getLiteralName(GLSLParser.IN).replace("'", ""));
|
||||
} else if (token.getType() == GLSLParser.VARYING) {
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
token.setType(GLSLParser.OUT);
|
||||
token.setText(GLSLParser.VOCABULARY.getLiteralName(GLSLParser.OUT).replace("'", ""));
|
||||
} else {
|
||||
token.setType(GLSLParser.IN);
|
||||
token.setText(GLSLParser.VOCABULARY.getLiteralName(GLSLParser.IN).replace("'", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String injectGlslPreamble(String shader, List<String> extensions, List<String> preprocessorDefines) {
|
||||
final StringBuilder extensionBlock = new StringBuilder();
|
||||
final StringBuilder defineBlock = new StringBuilder();
|
||||
final StringBuilder functionBlock = new StringBuilder();
|
||||
|
||||
for (String ext : extensions) {
|
||||
extensionBlock.append("#extension ").append(ext).append(" : require\n");
|
||||
}
|
||||
for (String define : preprocessorDefines) {
|
||||
if (define.startsWith("GLSL_FUNC:")) {
|
||||
functionBlock.append(define.substring(10)).append("\n");
|
||||
} else {
|
||||
defineBlock.append(define).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Find where preprocessor section ends and code begins
|
||||
final String[] lines = shader.split("\n");
|
||||
final StringBuilder shaderResult = new StringBuilder();
|
||||
boolean extensionsInjected = false, definesInjected = false, functionsInjected = false;
|
||||
|
||||
for (String line : lines) {
|
||||
final String trimmed = line.trim();
|
||||
final boolean isPreprocessor = trimmed.startsWith("#");
|
||||
final boolean isExtension = trimmed.startsWith("#extension");
|
||||
final boolean isVersion = trimmed.startsWith("#version");
|
||||
|
||||
// Inject our extensions right after #version
|
||||
if (isVersion) {
|
||||
shaderResult.append(line).append("\n");
|
||||
shaderResult.append(extensionBlock);
|
||||
extensionsInjected = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Inject our defines after the last #extension line (before other preprocessor or code)
|
||||
if (!definesInjected && extensionsInjected && !isExtension && !trimmed.isEmpty()) {
|
||||
shaderResult.append(defineBlock);
|
||||
definesInjected = true;
|
||||
}
|
||||
|
||||
// Inject our functions after all preprocessor lines
|
||||
if (!functionsInjected && definesInjected && !isPreprocessor && !trimmed.isEmpty()) {
|
||||
shaderResult.append(functionBlock);
|
||||
functionsInjected = true;
|
||||
}
|
||||
|
||||
shaderResult.append(line).append("\n");
|
||||
}
|
||||
|
||||
if (!definesInjected) shaderResult.append(defineBlock);
|
||||
if (!functionsInjected) shaderResult.append(functionBlock);
|
||||
|
||||
return shaderResult.toString();
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String string) {
|
||||
final StringBuilder sb = new StringBuilder(string + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
|
||||
if (tree instanceof TerminalNode) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
}
|
||||
|
||||
if (text.equals("}")) {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,13 +49,6 @@ public class CenterDepthSampler {
|
||||
|
||||
try {
|
||||
String fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/centerDepth.fsh"))), StandardCharsets.UTF_8);
|
||||
|
||||
if (GLStateManager.capabilities.OpenGL32) {
|
||||
fsh = fsh.replace("VERSIONPLACEHOLDER", "150 compatibility");
|
||||
} else {
|
||||
fsh = fsh.replace("#define IS_GL3", "");
|
||||
fsh = fsh.replace("VERSIONPLACEHOLDER", "120");
|
||||
}
|
||||
String vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/centerDepth.vsh"))), StandardCharsets.UTF_8);
|
||||
|
||||
builder = ProgramBuilder.begin("centerDepthSmooth", vsh, null, fsh, ImmutableSet.of(0, 1, 2));
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.coderbot.iris.gl.program.ProgramSamplers;
|
||||
import net.coderbot.iris.gl.program.ProgramUniforms;
|
||||
import net.coderbot.iris.gl.sampler.SamplerLimits;
|
||||
import net.coderbot.iris.gl.state.FogMode;
|
||||
import net.coderbot.iris.gl.texture.TextureAccess;
|
||||
import net.coderbot.iris.pipeline.PatchedShaderPrinter;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
@@ -32,7 +33,6 @@ import net.coderbot.iris.shaderpack.ProgramDirectives;
|
||||
import net.coderbot.iris.shaderpack.ProgramSource;
|
||||
import net.coderbot.iris.shaderpack.texture.TextureStage;
|
||||
import net.coderbot.iris.shadows.ShadowRenderTargets;
|
||||
import net.coderbot.iris.gl.state.FogMode;
|
||||
import net.coderbot.iris.uniforms.CommonUniforms;
|
||||
import net.coderbot.iris.uniforms.FrameUpdateNotifier;
|
||||
import net.coderbot.iris.uniforms.custom.CustomUniforms;
|
||||
@@ -42,7 +42,6 @@ import net.minecraft.client.shader.Framebuffer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
|
||||
@@ -291,6 +290,7 @@ public class CompositeRenderer {
|
||||
renderPass.program.use();
|
||||
|
||||
this.customUniforms.push(renderPass.program);
|
||||
FullScreenQuadRenderer.uploadCompositeMatrices();
|
||||
|
||||
FullScreenQuadRenderer.INSTANCE.renderQuad();
|
||||
}
|
||||
@@ -302,7 +302,7 @@ public class CompositeRenderer {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
|
||||
ProgramUniforms.clearActiveUniforms();
|
||||
ProgramSamplers.clearActiveSamplers();
|
||||
GL20.glUseProgram(0);
|
||||
GLStateManager.glUseProgram(0);
|
||||
|
||||
// NB: Unbinding all of these textures is necessary for proper shaderpack reloading.
|
||||
for (int i = 0; i < SamplerLimits.get().getMaxTextureUnits(); i++) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.coderbot.iris.gl.program.ProgramSamplers;
|
||||
import net.coderbot.iris.gl.program.ProgramUniforms;
|
||||
import net.coderbot.iris.gl.sampler.SamplerLimits;
|
||||
import net.coderbot.iris.gl.state.FogMode;
|
||||
import net.coderbot.iris.gl.texture.TextureAccess;
|
||||
import net.coderbot.iris.pipeline.PatchedShaderPrinter;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
@@ -31,7 +32,6 @@ import net.coderbot.iris.shaderpack.ProgramSet;
|
||||
import net.coderbot.iris.shaderpack.ProgramSource;
|
||||
import net.coderbot.iris.shaderpack.texture.TextureStage;
|
||||
import net.coderbot.iris.shadows.ShadowRenderTargets;
|
||||
import net.coderbot.iris.gl.state.FogMode;
|
||||
import net.coderbot.iris.uniforms.CommonUniforms;
|
||||
import net.coderbot.iris.uniforms.FrameUpdateNotifier;
|
||||
import net.coderbot.iris.uniforms.custom.CustomUniforms;
|
||||
@@ -41,7 +41,6 @@ import net.minecraft.client.shader.Framebuffer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
|
||||
@@ -237,6 +236,7 @@ public class FinalPassRenderer {
|
||||
finalPass.program.use();
|
||||
|
||||
this.customUniforms.push(finalPass.program);
|
||||
FullScreenQuadRenderer.uploadCompositeMatrices();
|
||||
|
||||
FullScreenQuadRenderer.INSTANCE.renderQuad();
|
||||
|
||||
@@ -284,7 +284,7 @@ public class FinalPassRenderer {
|
||||
main.bindFramebuffer(true);
|
||||
ProgramUniforms.clearActiveUniforms();
|
||||
ProgramSamplers.clearActiveSamplers();
|
||||
GL20.glUseProgram(0);
|
||||
GLStateManager.glUseProgram(0);
|
||||
|
||||
for (int i = 0; i < SamplerLimits.get().getMaxTextureUnits(); i++) {
|
||||
// Unbind all textures that we may have used.
|
||||
|
||||
@@ -4,9 +4,11 @@ package net.coderbot.iris.postprocess;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
@@ -15,11 +17,18 @@ import java.nio.FloatBuffer;
|
||||
*/
|
||||
public class FullScreenQuadRenderer {
|
||||
private final int quadBuffer;
|
||||
private final int vao;
|
||||
|
||||
public static final FullScreenQuadRenderer INSTANCE = new FullScreenQuadRenderer();
|
||||
|
||||
private FullScreenQuadRenderer() {
|
||||
this.vao = org.lwjgl.opengl.GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
this.quadBuffer = createQuad();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, quadBuffer);
|
||||
DefaultVertexFormat.POSITION_TEXTURE.setupBufferState(0L);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
@@ -33,21 +42,20 @@ public class FullScreenQuadRenderer {
|
||||
public void begin() {
|
||||
GLStateManager.disableDepthTest();
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
// scale the quad from [0, 1] to [-1, 1]
|
||||
GL11.glTranslatef(-1.0F, -1.0F, 0.0F);
|
||||
GL11.glScalef(2.0F, 2.0F, 0.0F);
|
||||
GLStateManager.glTranslatef(-1.0F, -1.0F, 0.0F);
|
||||
GLStateManager.glScalef(2.0F, 2.0F, 1.0F);
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, quadBuffer);
|
||||
DefaultVertexFormat.POSITION_TEXTURE.setupBufferState(0L);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
public void renderQuad() {
|
||||
@@ -55,15 +63,45 @@ public class FullScreenQuadRenderer {
|
||||
}
|
||||
|
||||
public static void end() {
|
||||
DefaultVertexFormat.POSITION_TEXTURE.clearBufferState();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.enableDepthTest();
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPopMatrix();
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
private static final FloatBuffer matBuf = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
/** Per-program cached uniform locations: [mvLoc, projLoc]. Populated on first miss. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> compositeLocCache = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public static void uploadCompositeMatrices() {
|
||||
final int program = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
if (program == 0) return;
|
||||
|
||||
int[] locs = compositeLocCache.get(program);
|
||||
if (locs == null) {
|
||||
locs = new int[] {
|
||||
GL20.glGetUniformLocation(program, "iris_ModelViewMatrix"),
|
||||
GL20.glGetUniformLocation(program, "iris_ProjectionMatrix")
|
||||
};
|
||||
compositeLocCache.put(program, locs);
|
||||
}
|
||||
|
||||
if (locs[0] != -1) {
|
||||
GLStateManager.getModelViewMatrix().get(matBuf);
|
||||
matBuf.rewind();
|
||||
GL20.glUniformMatrix4(locs[0], false, matBuf);
|
||||
}
|
||||
|
||||
if (locs[1] != -1) {
|
||||
GLStateManager.getProjectionMatrix().get(matBuf);
|
||||
matBuf.rewind();
|
||||
GL20.glUniformMatrix4(locs[1], false, matBuf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user