Compare commits

..
43 Commits
Author SHA1 Message Date
Jason Mitchell bf08cd50d9 Support glPixelStore in display lists (#1854) 2026-06-11 20:29:14 -07:00
Eclipse-Sol 805a266059 Support extended metas for NEID and EID (#1853) 2026-06-11 17:58:22 -07:00
Eclipse-Sol d3c9963280 Fix ctm crash due to null (#1852) 2026-06-11 18:02:08 -04:00
Eclipse-Sol 4630bc4fd8 Fix shaders not loading the correct dim (#1844) 2026-06-09 23:12:13 -07:00
Jason Mitchell ce32a3d3df Emulate glTexParameter(pname=GL_GENERATE_MIPMAP) on core (#1845) 2026-06-09 23:03:42 -07:00
Eclipse-Sol 1991dc58ec Fix Steadfast not loading right (#1838) 2026-06-08 20:06:29 -07:00
Eclipse-Sol ae19236e1a Remove force hand animation (#1839) 2026-06-08 20:05:49 -07:00
Kotmatross 7ad368c937 Be less lazy (#1834) 2026-06-08 17:03:36 -07:00
Eclipse-Sol 5c86c87bcf Allow devs to target the dragon death rays as its own entity id (#1836) 2026-06-08 17:03:05 -07:00
tiffit 99ba7fa775 Remove CTM adding .png to tile paths (#1832) 2026-06-07 16:05:10 -07:00
Eclipse-Sol 5ae4ab8c5c More specific shader fixes pertaining to EP (#1831) 2026-06-07 11:19:30 -07:00
madoha ddd68e023f Add Japanese Localizations (#1830) 2026-06-07 11:14:20 -07:00
Eclipse-Sol f78cfdd5fb Update and fix Iris' shader menus (#1828) 2026-06-06 17:12:38 -07:00
Jason Mitchell 183bcfbf41 Narrow more transformer exclusions (#1827) 2026-06-06 15:38:05 -07:00
tiffit 502f30c0f9 Fix bottom-face being flipped for compact CTM (#1820) 2026-06-05 16:14:14 -07:00
Jason Mitchell ceff04adba Fixes a regression with TFC. (#1822) 2026-06-05 16:13:30 -07:00
Eclipse-Sol b4dfdb8cc5 Fix beacon beams (#1821) 2026-06-05 16:03:46 -07:00
Eclipse-Sol 3064818477 Link up block outline shader to iris shaders (#1814) 2026-06-05 11:25:31 -07:00
Eclipse-Sol 27524e6a89 Add double_plant handling to match modern (#1815) 2026-06-05 08:36:14 -07:00
Eclipse-Sol 5d5e910fc0 Match modern hand swap animations (#1816) 2026-06-05 08:35:07 -07:00
Jason Mitchellandsisyphussy f325fd7fc3 Be more lazy (#1794)
* Be more lazy

---------

Co-authored-by: sisyphussy <[email protected]>
2026-06-05 08:29:27 -07:00
Jason Mitchell ec278d3d7a Upload fewer things (#1811) 2026-06-04 13:11:59 -07:00
Caedis eef3d2f50a feat(hand): add cancellable RenderHandEvent for canRender (#1806) 2026-06-01 20:38:27 +00:00
tiffit 3da597ab18 Improve perf of EmissiveTextureAutoloader.resourceExists (#1805) 2026-06-01 13:34:23 -07:00
Jason Mitchell 0e79380618 More class loading order fun with java 8... (#1804) 2026-05-31 20:56:00 -07:00
tiffit b639d5c646 Add compat for hodgepodge's async icon loading (#1799) 2026-05-31 15:28:27 -07:00
Jason Mitchell 0d209d426d Document why I did this so I remember next time someone asks about this cursed function (#1802) 2026-05-31 15:12:45 -07:00
danyadev bf24084033 switch to new RFB transform signature (#1421) 2026-05-29 09:35:31 -07:00
tiffit 9267ecc735 Remove registerIcons call in MCPF MixinTextureMap (#1795) 2026-05-28 13:12:10 -07:00
dcc8d8deed Make shader based clouds work with iris shaderpacks, and port optimizations from #1685 (#1793)
* Make shader based clouds work with iris shaderpacks, and port optimizations from #1685

Co-Authored-By: Eclipse-Sol <[email protected]>

* Yeet GL_QUADS

* Optimize a little more

---------

Co-authored-by: Eclipse-Sol <[email protected]>
Co-authored-by: Logan Phillips <[email protected]>
2026-05-28 10:31:17 -04:00
Jason Mitchell 5dc4f1fab8 Fix tests (#1791) 2026-05-27 18:41:41 -07:00
CaedisandJason Mitchell d3cea1bfe4 fix: refresh ClonedChunkSection biome data on cache hit (#1769)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-27 16:45:29 -07:00
DanandRanzu 5db2b248d7 add per-segment drop shadow (&u) (#1754)
Co-authored-by: Ranzu <[email protected]>
2026-05-27 16:44:54 -07:00
Eclipse-Sol 21ab526284 Fix shadow map from being clobbered on Windows (#1789) 2026-05-27 16:43:51 -07:00
MassAnarchy 4c8ea679ef Fix GL_FEEDBACK readback using correct attribute 0 VBO (#1783) 2026-05-27 16:43:03 -07:00
Eclipse-Sol 2317379df2 Add gbuffer_lightning (#1787) 2026-05-27 16:41:51 -07:00
Death Fuel 24dae49c7f Mark ClientFixer as incompatible (#1786) 2026-05-26 16:03:22 -07:00
Eclipse-Sol 865de3cbab Fix issues that directly affects Euphoria Patches + Complementary (#1782) 2026-05-26 14:11:33 -07:00
Jason Mitchell 9c45549e35 Resolve generic compressed image formats (#1781) 2026-05-25 11:50:18 -07:00
Jason Mitchell 2a20198292 Fixes crash with FFP shader linking (#1780) 2026-05-24 17:31:46 -07:00
Eclipse-SolandJason Mitchell f0b68f93af Port modern's damage overlay (#1777)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-24 10:48:42 -07:00
Eclipse-SolandJason Mitchell dd1d3aa518 Add NBT matching to list of valid block/item entries (#1771)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-23 23:08:48 -07:00
Jason Mitchell c9633fd745 Fix osx crash (#1778) 2026-05-23 22:03:59 -07:00
169 changed files with 6228 additions and 1650 deletions

No files matched your search

+1
View File
@@ -50,6 +50,7 @@ Targets GL 3.3+ core profile
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
# Credits
* ShadersMod
+5
View File
@@ -1,9 +1,14 @@
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
+3 -3
View File
@@ -66,9 +66,9 @@ configurations.all {
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
@@ -76,7 +76,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.10.0:dev")
api("com.github.GTNewHorizons:GTNHLib:0.11.4:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
+15
View File
@@ -124,6 +124,7 @@ tasks.withType<DowngradeFiles>().configureEach {
downgradeTo.set(JavaVersion.VERSION_1_8)
multiReleaseOriginal.set(false)
multiReleaseVersions.set(emptySet())
logLevel.set("FATAL")
}
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
@@ -139,6 +140,7 @@ val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath
sourceSets["test"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("testClasses"))
}
@@ -174,6 +176,19 @@ tasks.test {
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
val verifyTestsRan by tasks.registering {
val resultsDir = layout.buildDirectory.dir("test-results/test")
dependsOn(tasks.test)
doLast {
val dir = resultsDir.get().asFile
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
check(xmls.isNotEmpty()) {
":glsm:test produced no TEST-*.xml in $dir - test task likely went NO-SOURCE"
}
}
}
tasks.check { dependsOn(verifyTestsRan) }
publishing {
publications {
create<MavenPublication>("mavenJava") {
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -114,13 +113,17 @@ public class CompatShaderTransformer {
return result;
}
private static boolean needsTransformation(String source) {
// Shaders already at 330+ core don't need compat transformation
public static boolean isCoreShader(String source) {
final Matcher vm = VERSION_PATTERN.matcher(source);
if (vm.find()) {
final int version = Integer.parseInt(vm.group(1));
if (version >= 330 && "core".equals(vm.group(2))) return false;
return version >= 330 && "core".equals(vm.group(2));
}
return false;
}
private static boolean needsTransformation(String source) {
if (isCoreShader(source)) return false;
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
}
@@ -7,15 +7,16 @@ import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Per-program compat uniform location cache and upload manager.
*
@@ -102,8 +103,15 @@ public class CompatUniformManager {
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
private static final class ProgramUniforms {
final int[] locs;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
}
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
@@ -112,6 +120,7 @@ public class CompatUniformManager {
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 Vector3f scratchCol = new Vector3f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
@@ -121,17 +130,18 @@ public class CompatUniformManager {
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
/**
* Normalize each column of a 3x3 matrix in place. Used to strip the residual 1/scale that
* inverse-transpose introduces.
*/
private static void normalizeColumnsInPlace(Matrix3f m) {
m.setColumn(0, m.getColumn(0, scratchCol).normalize());
m.setColumn(1, m.getColumn(1, scratchCol).normalize());
m.setColumn(2, m.getColumn(2, scratchCol).normalize());
}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
@@ -142,45 +152,49 @@ public class CompatUniformManager {
}
if (hasAny) {
programLocations.put(program, locs);
programUniforms.put(program, new ProgramUniforms(locs));
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
/**
* Uploads the emulated uniforms.
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
pu.lastMvGen = mvGen;
pu.lastMvLinearGen = mvLinearGen;
pu.lastProjGen = projGen;
pu.lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
@@ -189,14 +203,16 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
return true;
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -218,15 +234,20 @@ public class CompatUniformManager {
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
}
if (mvLinearChanged) {
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
// Column-normalized to unit magnitude
if (locs[LOC_IRIS_NORMAL] != -1) {
normalizeColumnsInPlace(normalMatrix);
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
@@ -398,14 +419,15 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
programUniforms.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
return programUniforms.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
final ProgramUniforms pu = programUniforms.get(program);
return pu == null ? null : pu.locs;
}
}
@@ -111,6 +111,7 @@ public class DisplayListManager {
int listMode,
CommandRecorder recorder,
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
@@ -131,6 +132,19 @@ public class DisplayListManager {
return currentRecorder != null && Thread.currentThread() == recordingThread;
}
public static UnsupportedOperationException unsupportedInList(String what) {
return new UnsupportedOperationException(what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
}
public static String describeCurrentCompilation() {
if (currentRecorder == null) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
final int depth = compilationStack.size() + 1;
return "while compiling display list " + glListId + " (" + mode + (depth > 1 ? ", nesting depth " + depth : "") + ")";
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
@@ -159,7 +173,9 @@ public class DisplayListManager {
* Only use this if you are 100% that the transforms will not be of any use after the display list draw call.
*/
public static void discardMatrixTransforms() {
resetRelativeTransform();
if (isRecording()) {
resetRelativeTransform();
}
}
/**
@@ -748,7 +764,7 @@ public class DisplayListManager {
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws,
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
@@ -784,9 +800,21 @@ public class DisplayListManager {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
}
try {
finishCurrentList();
} finally {
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
final boolean isNested = !compilationStack.isEmpty();
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
@@ -803,8 +831,9 @@ public class DisplayListManager {
final boolean hasCommands = !commandBuffer.isEmpty();
if (hasCommands) {
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
DisplayListVBO compiledBuffers = null;
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
@@ -824,16 +853,23 @@ public class DisplayListManager {
}
}
compiled = new CompiledDisplayList(commandBuffer.finish(), commandBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
currentRecorder = null;
} catch (Exception e) {
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
e.printStackTrace();
commandBuffer.delete();
currentRecorder = null;
if (compiledBuffers != null) {
compiledBuffers.delete();
}
return;
}
} else {
// Free the recorder even if empty
commandBuffer.delete();
currentRecorder = null;
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
compiled = CompiledDisplayList.EMPTY;
}
@@ -846,6 +882,10 @@ public class DisplayListManager {
if (LOG_DISPLAY_LIST_COMPILATION) {
logCompiledDisplayList(glListId, compiled, compilationStackTrace);
}
}
private static void popCompilationContext() {
final boolean isNested = !compilationStack.isEmpty();
// Handle nested compilation - restore parent context
if (isNested) {
@@ -856,6 +896,7 @@ public class DisplayListManager {
glListMode = parentContext.listMode;
currentRecorder = parentContext.recorder;
accumulatedDraws = parentContext.draws;
transformCallback = parentContext.callback;
compilationStackTrace = parentContext.stackTrace;
pendingTransformOps = parentContext.pendingOps;
multMatrixSources = parentContext.matrixSources;
@@ -869,6 +910,7 @@ public class DisplayListManager {
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
@@ -142,7 +142,7 @@ public class FeedbackManager {
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
@@ -170,7 +170,7 @@ public class FeedbackManager {
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
@@ -251,7 +251,23 @@ public class FeedbackManager {
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VertexAttribState.get(0).vboId;
final int previousVbo = GLStateManager.getBoundVBO();
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
}
try {
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, offset, destination);
} finally {
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousVbo);
}
}
}
private static void emitVertex(int vertexIndex, int stride) {
@@ -38,6 +38,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -144,36 +145,70 @@ public class GLStateManager {
// Generation counters for FFP uniform dirty tracking. Bumped when the corresponding GLSM state changes.
// Per-matrix-mode generation counters — avoids re-uploading all matrices when only one mode changed
public static int mvGeneration; // modelview matrix changes
public static int mvGeneration; // modelview matrix changes (any: translation, rotation, scale)
public static int mvLinearGeneration;
public static int projGeneration; // projection matrix changes
public static int texMatrixGeneration; // texture matrix changes
public static int lightingGeneration;
public static int fragmentGeneration; // fog + alpha ref
public static int fragmentGeneration; // fog + alpha ref + overlay color
public static int colorGeneration; // current vertex color
public static int clipPlaneGeneration; // clip plane equation changes
@Getter private static float overlayR = 0.0f;
@Getter private static float overlayG = 0.0f;
@Getter private static float overlayB = 0.0f;
@Getter private static float overlayA = 0.0f;
public static void setOverlayColor(float r, float g, float b, float a) {
if (r == overlayR && g == overlayG && b == overlayB && a == overlayA) return;
overlayR = r; overlayG = g; overlayB = b; overlayA = a;
fragmentGeneration++;
}
@Getter private static float shaderColorR = 1.0f;
@Getter private static float shaderColorG = 1.0f;
@Getter private static float shaderColorB = 1.0f;
@Getter private static float shaderColorA = 1.0f;
public static void setShaderColor(float r, float g, float b, float a) {
if (r == shaderColorR && g == shaderColorG && b == shaderColorB && a == shaderColorA) return;
shaderColorR = r; shaderColorG = g; shaderColorB = b; shaderColorA = a;
if (GLSMHooks.SHADER_COLOR_CHANGE.hasListeners()) {
GLSMHooks.shaderColorChangeEvent.red = r;
GLSMHooks.shaderColorChangeEvent.green = g;
GLSMHooks.shaderColorChangeEvent.blue = b;
GLSMHooks.shaderColorChangeEvent.alpha = a;
GLSMHooks.SHADER_COLOR_CHANGE.post(GLSMHooks.shaderColorChangeEvent);
}
}
// Deferred vertex attribute upload flags — set when state changes, flushed before draw
private static boolean dirtyColorAttrib;
private static boolean dirtyColorAttrib = true;
private static boolean dirtyNormalAttrib;
private static boolean dirtyTexCoordAttrib;
private static boolean dirtyLightmapAttrib;
private static boolean dirtyLightmapAttrib = true;
/** Flush deferred vertex attribute uploads. Called before draw to ensure default attrib values are current. */
public static void flushDeferredVertexAttribs() {
RENDER_BACKEND.vertexAttrib4f(Usage.COLOR.getAttributeLocation(), color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
dirtyColorAttrib = false;
RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f);
dirtyLightmapAttrib = false;
if (dirtyNormalAttrib) {
// vertexFlags bits mark attribs the VBO already supplies; FFP supplies the rest via u_Current* uniforms.
// Skip the backend vertexAttrib call in either case.
public static void flushDeferredVertexAttribs(int vertexFlags) {
if (dirtyColorAttrib && (vertexFlags & VertexFlags.COLOR_BIT) == 0) {
RENDER_BACKEND.vertexAttrib4f(Usage.COLOR.getAttributeLocation(), color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
dirtyColorAttrib = false;
}
if (dirtyNormalAttrib && (vertexFlags & VertexFlags.NORMAL_BIT) == 0) {
final var n = ShaderManager.getCurrentNormal();
RENDER_BACKEND.vertexAttrib3f(Usage.NORMAL.getAttributeLocation(), n.x, n.y, n.z);
dirtyNormalAttrib = false;
}
if (dirtyTexCoordAttrib) {
if (dirtyTexCoordAttrib && (vertexFlags & VertexFlags.TEXTURE_BIT) == 0) {
final var tc = ShaderManager.getCurrentTexCoord();
RENDER_BACKEND.vertexAttrib4f(Usage.PRIMARY_UV.getAttributeLocation(), tc.x, tc.y, tc.z, tc.w);
dirtyTexCoordAttrib = false;
}
if (dirtyLightmapAttrib) {
RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f);
dirtyLightmapAttrib = false;
}
}
// Highest texture unit index that has ever had a non-zero binding; limits onDeleteTexture scan range
@@ -269,6 +304,7 @@ public class GLStateManager {
// Saved generation counters at push time — used to detect whether state actually changed during push/pop scope
private static final int[] savedMvGen = new int[MAX_ATTRIB_STACK_DEPTH];
private static final int[] savedMvLinearGen = new int[MAX_ATTRIB_STACK_DEPTH];
private static final int[] savedProjGen = new int[MAX_ATTRIB_STACK_DEPTH];
private static final int[] savedTexMatGen = new int[MAX_ATTRIB_STACK_DEPTH];
private static final int[] savedLightingGen = new int[MAX_ATTRIB_STACK_DEPTH];
@@ -428,6 +464,7 @@ public class GLStateManager {
private static final int[] clientArraysVBOOffsets = new int[VertexAttribState.MAX_ATTRIBS];
private static int boundPixelUnpackBuffer;
private static int boundPixelPackBuffer;
private static PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
private static final Int2IntOpenHashMap vaoEboMap = new Int2IntOpenHashMap();
static {
@@ -1281,7 +1318,7 @@ public class GLStateManager {
public static void glBlendEquation(int mode) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glBlendEquation in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glBlendEquation");
}
final boolean caching = isCachingEnabled();
final boolean bypass = BYPASS_CACHE || !caching;
@@ -1296,7 +1333,7 @@ public class GLStateManager {
public static void glBlendEquationSeparate(int modeRGB, int modeAlpha) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glBlendEquationSeparate in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glBlendEquationSeparate");
}
final boolean caching = isCachingEnabled();
final boolean bypass = BYPASS_CACHE || !caching;
@@ -1816,7 +1853,7 @@ public class GLStateManager {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromIntBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromIntBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1825,13 +1862,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromFloatBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromFloatBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1840,13 +1878,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromDoubleBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromDoubleBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1855,13 +1894,14 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels));
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -1870,15 +1910,17 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage2D with buffer offset in display lists not yet supported");
throw DisplayListManager.unsupportedInList("glTexImage2D with buffer offset");
}
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
maybeGenerateMipmap(target, level);
}
public static void glTexCoord1f(float s) {
@@ -1966,7 +2008,14 @@ public class GLStateManager {
}
}
/** Shrink texture to 1x1 to free GPU memory, unbind from all units, but keep the name valid. */
/**
* Defer deleting textures until it has been reclaimed.
* Why you might ask? In compat profile if you bind a texture that doesn't exist, ie a deleted texture,
* mesa will silently create it for you, in core this is an error. Why would you be binding a deleted texture?
* Damned if I know, but vanilla and mods do it... they create one, delete it, and then bind it....
*
* So... we shrink the texture to 1x1 to free GPU memory, unbind from all units, but keep the name valid.
* */
private static void deferDeleteTexture(int id) {
if (id == 0) return;
@@ -2155,7 +2204,7 @@ public class GLStateManager {
public static void glDrawElements(int mode, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
@@ -2169,7 +2218,7 @@ public class GLStateManager {
public static void glDrawElements(int mode, IntBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
@@ -2187,7 +2236,7 @@ public class GLStateManager {
public static void glDrawElements(int mode, ShortBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, indices);
@@ -2205,7 +2254,7 @@ public class GLStateManager {
public static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glDrawElements");
}
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.processDrawElements(mode, count, type, indices);
@@ -2837,6 +2886,22 @@ public class GLStateManager {
}
}
colorMaterial.enable();
bakeCurrentColorIntoTrackedMaterial();
}
public static void disableColorMaterial() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordDisable(GL11.GL_COLOR_MATERIAL);
if (mode == RecordMode.COMPILE) {
return;
}
}
bakeCurrentColorIntoTrackedMaterial();
colorMaterial.disable();
}
private static void bakeCurrentColorIntoTrackedMaterial() {
final float r = getColor().getRed();
final float g = getColor().getGreen();
final float b = getColor().getBlue();
@@ -2867,17 +2932,6 @@ public class GLStateManager {
}
}
public static void disableColorMaterial() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordDisable(GL11.GL_COLOR_MATERIAL);
if (mode == RecordMode.COMPILE) {
return;
}
}
colorMaterial.disable();
}
public static void disableLighting() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3008,16 +3062,19 @@ public class GLStateManager {
}
// Update cached state (FFP shader reads from cache)
if (isCachingEnabled()) {
boolean changed = true;
boolean changed = false;
switch (pname) {
case GL11.GL_FOG_DENSITY -> { fogState.setDensity(param); fragmentGeneration++; }
case GL11.GL_FOG_START -> { fogState.setStart(param); fragmentGeneration++; }
case GL11.GL_FOG_END -> { fogState.setEnd(param); fragmentGeneration++; }
case GL11.GL_FOG_MODE -> { fogState.setFogMode((int) param); fragmentGeneration++; }
default -> changed = false;
case GL11.GL_FOG_DENSITY -> { if (BYPASS_CACHE || fogState.getDensity() != param) { fogState.setDensity(param); changed = true; } }
case GL11.GL_FOG_START -> { if (BYPASS_CACHE || fogState.getStart() != param) { fogState.setStart(param); changed = true; } }
case GL11.GL_FOG_END -> { if (BYPASS_CACHE || fogState.getEnd() != param) { fogState.setEnd(param); changed = true; } }
case GL11.GL_FOG_MODE -> { if (BYPASS_CACHE || fogState.getFogMode() != (int) param) { fogState.setFogMode((int) param); changed = true; } }
default -> {}
}
if (changed && GLSMHooks.FOG_STATE_CHANGE.hasListeners()) {
GLSMHooks.FOG_STATE_CHANGE.post(GLSMHooks.fogStateChangeEvent);
if (changed) {
fragmentGeneration++;
if (GLSMHooks.FOG_STATE_CHANGE.hasListeners()) {
GLSMHooks.FOG_STATE_CHANGE.post(GLSMHooks.fogStateChangeEvent);
}
}
}
}
@@ -3031,7 +3088,7 @@ public class GLStateManager {
}
}
// Update cached state (FFP shader reads from cache)
if (isCachingEnabled() && pname == GL11.GL_FOG_MODE) {
if (isCachingEnabled() && pname == GL11.GL_FOG_MODE && (BYPASS_CACHE || fogState.getFogMode() != param)) {
fogState.setFogMode(param);
fragmentGeneration++;
if (GLSMHooks.FOG_STATE_CHANGE.hasListeners()) {
@@ -3182,6 +3239,7 @@ public class GLStateManager {
// Snapshot generation counters so we can detect actual changes at pop time
savedMvGen[attribDepth] = mvGeneration;
savedMvLinearGen[attribDepth] = mvLinearGeneration;
savedProjGen[attribDepth] = projGeneration;
savedTexMatGen[attribDepth] = texMatrixGeneration;
savedLightingGen[attribDepth] = lightingGeneration;
@@ -3290,13 +3348,23 @@ public class GLStateManager {
if ((mask & GL11.GL_TEXTURE_BIT) != 0 && fragmentGeneration != savedFragmentGen[depth]) fragmentGeneration++; // texenv state
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
if (mvGeneration != savedMvGen[depth]) mvGeneration++;
if (mvLinearGeneration != savedMvLinearGen[depth]) mvLinearGeneration++;
if (projGeneration != savedProjGen[depth]) projGeneration++;
if (texMatrixGeneration != savedTexMatGen[depth]) texMatrixGeneration++;
}
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
if (colorGeneration != savedColorGen[depth]) colorGeneration++;
if (ShaderManager.getNormalGeneration() != savedNormalGen[depth]) ShaderManager.bumpNormalGeneration();
if (ShaderManager.getTexCoordGeneration() != savedTexCoordGen[depth]) ShaderManager.bumpTexCoordGeneration();
if (colorGeneration != savedColorGen[depth]) {
colorGeneration++;
dirtyColorAttrib = true;
}
if (ShaderManager.getNormalGeneration() != savedNormalGen[depth]) {
ShaderManager.bumpNormalGeneration();
dirtyNormalAttrib = true;
}
if (ShaderManager.getTexCoordGeneration() != savedTexCoordGen[depth]) {
ShaderManager.bumpTexCoordGeneration();
dirtyTexCoordAttrib = true;
}
}
}
@@ -3407,8 +3475,15 @@ public class GLStateManager {
/** Bump the generation counter for the currently active matrix mode. */
private static void bumpMatrixGeneration() {
bumpMatrixGeneration(true);
}
private static void bumpMatrixGeneration(boolean linearChanged) {
switch (matrixMode.getMode()) {
case GL11.GL_MODELVIEW -> mvGeneration++;
case GL11.GL_MODELVIEW -> {
mvGeneration++;
if (linearChanged) mvLinearGeneration++;
}
case GL11.GL_PROJECTION -> projGeneration++;
case GL11.GL_TEXTURE -> texMatrixGeneration++;
}
@@ -3435,7 +3510,7 @@ public class GLStateManager {
}
if (isCachingEnabled()) {
getMatrixStack().translate(x, y, z);
bumpMatrixGeneration();
bumpMatrixGeneration(false);
}
}
@@ -3446,7 +3521,7 @@ public class GLStateManager {
}
if (isCachingEnabled()) {
getMatrixStack().translate((float) x, (float) y, (float) z);
bumpMatrixGeneration();
bumpMatrixGeneration(false);
}
}
@@ -3666,6 +3741,25 @@ public class GLStateManager {
return DisplayListManager.getListMode();
}
private static boolean handleRemovedTexParam(int target, int pname, int value) {
if (pname == GL14.GL_GENERATE_MIPMAP) {
if (target == GL11.GL_TEXTURE_2D) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(getBoundTextureForServerState());
if (info != null) info.setGenerateMipmap(value != 0);
}
return true;
}
return false;
}
private static void maybeGenerateMipmap(int target, int level) {
if (target != GL11.GL_TEXTURE_2D) return;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(getBoundTextureForServerState());
if (info != null && info.isGenerateMipmap() && level == info.getBaseLevel() && level < info.getMaxLevel()) {
RENDER_BACKEND.generateMipmap(target);
}
}
public static boolean updateTexParameteriCache(int target, int texture, int pname, int param) {
if (target != GL11.GL_TEXTURE_2D) {
return true;
@@ -3691,6 +3785,10 @@ public class GLStateManager {
if (info.getWrapT() == param && !shouldBypassCache()) return false;
info.setWrapT(param);
}
case GL12.GL_TEXTURE_BASE_LEVEL -> {
if (info.getBaseLevel() == param && !shouldBypassCache()) return false;
info.setBaseLevel(param);
}
case GL12.GL_TEXTURE_MAX_LEVEL -> {
if (info.getMaxLevel() == param && !shouldBypassCache()) return false;
info.setMaxLevel(param);
@@ -3708,6 +3806,7 @@ public class GLStateManager {
}
public static void glTexParameter(int target, int pname, IntBuffer params) {
if (handleRemovedTexParam(target, pname, params.remaining() >= 1 ? params.get(params.position()) : 0)) return;
if (params.remaining() >= 1) {
final int val = params.get(params.position());
final int remapped = remapTexClamp(pname, val);
@@ -3718,6 +3817,7 @@ public class GLStateManager {
}
public static void glTexParameter(int target, int pname, FloatBuffer params) {
if (handleRemovedTexParam(target, pname, params.remaining() >= 1 ? (int) params.get(params.position()) : 0)) return;
if (params.remaining() >= 1) {
final float val = params.get(params.position());
final float remapped = remapTexClamp(pname, val);
@@ -3728,6 +3828,7 @@ public class GLStateManager {
}
public static void glTexParameteri(int target, int pname, int param) {
if (handleRemovedTexParam(target, pname, param)) return;
param = remapTexClamp(pname, param);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3767,6 +3868,7 @@ public class GLStateManager {
}
public static void glTexParameterf(int target, int pname, float param) {
if (handleRemovedTexParam(target, pname, param > 0 ? (int) (param + 0.5f) : (int) (param - 0.5f))) return;
param = remapTexClamp(pname, param);
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3799,9 +3901,11 @@ public class GLStateManager {
case GL11.GL_TEXTURE_MAG_FILTER -> info.getMagFilter();
case GL11.GL_TEXTURE_WRAP_S -> info.getWrapS();
case GL11.GL_TEXTURE_WRAP_T -> info.getWrapT();
case GL12.GL_TEXTURE_BASE_LEVEL -> info.getBaseLevel();
case GL12.GL_TEXTURE_MAX_LEVEL -> info.getMaxLevel();
case GL12.GL_TEXTURE_MIN_LOD -> info.getMinLod();
case GL12.GL_TEXTURE_MAX_LOD -> info.getMaxLod();
case GL14.GL_GENERATE_MIPMAP -> info.isGenerateMipmap() ? GL11.GL_TRUE : GL11.GL_FALSE;
default -> {
if (isRecordingDisplayList()) {
throw new IllegalStateException(String.format(
@@ -3865,7 +3969,12 @@ public class GLStateManager {
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> info.getWidth();
case GL11.GL_TEXTURE_HEIGHT -> info.getHeight();
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> info.getInternalFormat();
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
}
yield info.getResolvedInternalFormat();
}
default -> {
if (isRecordingDisplayList()) {
throw new IllegalStateException(String.format(
@@ -3912,64 +4021,72 @@ public class GLStateManager {
public static void glSamplerParameteri(int sampler, int pname, int param) { RENDER_BACKEND.samplerParameteri(sampler, pname, param); }
public static void glSamplerParameterf(int sampler, int pname, float param) { RENDER_BACKEND.samplerParameterf(sampler, pname, param); }
private static void glMaterialFront(int pname, FloatBuffer params) {
switch (pname) {
private static boolean glMaterialFront(int pname, FloatBuffer params) {
return switch (pname) {
case GL11.GL_AMBIENT -> frontMaterial.setAmbient(params);
case GL11.GL_DIFFUSE -> frontMaterial.setDiffuse(params);
case GL11.GL_SPECULAR -> frontMaterial.setSpecular(params);
case GL11.GL_EMISSION -> frontMaterial.setEmission(params);
case GL11.GL_SHININESS -> frontMaterial.setShininess(params);
case GL11.GL_AMBIENT_AND_DIFFUSE -> {
frontMaterial.setAmbient(params);
frontMaterial.setDiffuse(params);
final boolean a = frontMaterial.setAmbient(params);
final boolean d = frontMaterial.setDiffuse(params);
yield a || d;
}
case GL11.GL_COLOR_INDEXES -> frontMaterial.setColorIndexes(params);
}
default -> false;
};
}
private static void glMaterialBack(int pname, FloatBuffer params) {
switch (pname) {
private static boolean glMaterialBack(int pname, FloatBuffer params) {
return switch (pname) {
case GL11.GL_AMBIENT -> backMaterial.setAmbient(params);
case GL11.GL_DIFFUSE -> backMaterial.setDiffuse(params);
case GL11.GL_SPECULAR -> backMaterial.setSpecular(params);
case GL11.GL_EMISSION -> backMaterial.setEmission(params);
case GL11.GL_SHININESS -> backMaterial.setShininess(params);
case GL11.GL_AMBIENT_AND_DIFFUSE -> {
backMaterial.setAmbient(params);
backMaterial.setDiffuse(params);
final boolean a = backMaterial.setAmbient(params);
final boolean d = backMaterial.setDiffuse(params);
yield a || d;
}
case GL11.GL_COLOR_INDEXES -> backMaterial.setColorIndexes(params);
}
default -> false;
};
}
private static void glMaterialFront(int pname, IntBuffer params) {
switch (pname) {
private static boolean glMaterialFront(int pname, IntBuffer params) {
return switch (pname) {
case GL11.GL_AMBIENT -> frontMaterial.setAmbient(params);
case GL11.GL_DIFFUSE -> frontMaterial.setDiffuse(params);
case GL11.GL_SPECULAR -> frontMaterial.setSpecular(params);
case GL11.GL_EMISSION -> frontMaterial.setEmission(params);
case GL11.GL_SHININESS -> frontMaterial.setShininess(params);
case GL11.GL_AMBIENT_AND_DIFFUSE -> {
frontMaterial.setAmbient(params);
frontMaterial.setDiffuse(params);
final boolean a = frontMaterial.setAmbient(params);
final boolean d = frontMaterial.setDiffuse(params);
yield a || d;
}
case GL11.GL_COLOR_INDEXES -> frontMaterial.setColorIndexes(params);
}
default -> false;
};
}
private static void glMaterialBack(int pname, IntBuffer params) {
switch (pname) {
private static boolean glMaterialBack(int pname, IntBuffer params) {
return switch (pname) {
case GL11.GL_AMBIENT -> backMaterial.setAmbient(params);
case GL11.GL_DIFFUSE -> backMaterial.setDiffuse(params);
case GL11.GL_SPECULAR -> backMaterial.setSpecular(params);
case GL11.GL_EMISSION -> backMaterial.setEmission(params);
case GL11.GL_SHININESS -> backMaterial.setShininess(params);
case GL11.GL_AMBIENT_AND_DIFFUSE -> {
backMaterial.setAmbient(params);
backMaterial.setDiffuse(params);
final boolean a = backMaterial.setAmbient(params);
final boolean d = backMaterial.setDiffuse(params);
yield a || d;
}
case GL11.GL_COLOR_INDEXES -> backMaterial.setColorIndexes(params);
}
default -> false;
};
}
public static void glMaterial(int face, int pname, FloatBuffer params) {
@@ -3980,17 +4097,19 @@ public class GLStateManager {
return;
}
}
boolean changed = false;
if (face == GL11.GL_FRONT) {
glMaterialFront(pname, params);
changed = glMaterialFront(pname, params);
} else if (face == GL11.GL_BACK) {
glMaterialBack(pname, params);
changed = glMaterialBack(pname, params);
} else if (face == GL11.GL_FRONT_AND_BACK) {
glMaterialFront(pname, params);
glMaterialBack(pname, params);
final boolean f = glMaterialFront(pname, params);
final boolean b = glMaterialBack(pname, params);
changed = f || b;
} else {
throw new RuntimeException("Unsupported face value for glMaterial: " + face);
}
lightingGeneration++;
if (changed) lightingGeneration++;
}
public static void glMaterial(int face, int pname, IntBuffer params) {
@@ -4009,17 +4128,19 @@ public class GLStateManager {
return;
}
}
boolean changed = false;
if (face == GL11.GL_FRONT) {
glMaterialFront(pname, params);
changed = glMaterialFront(pname, params);
} else if (face == GL11.GL_BACK) {
glMaterialBack(pname, params);
changed = glMaterialBack(pname, params);
} else if (face == GL11.GL_FRONT_AND_BACK) {
glMaterialFront(pname, params);
glMaterialBack(pname, params);
final boolean f = glMaterialFront(pname, params);
final boolean b = glMaterialBack(pname, params);
changed = f || b;
} else {
throw new RuntimeException("Unsupported face value for glMaterial: " + face);
}
lightingGeneration++;
if (changed) lightingGeneration++;
}
public static void glMaterialf(int face, int pname, float val) {
@@ -4035,17 +4156,19 @@ public class GLStateManager {
return;
}
boolean changed = false;
if (face == GL11.GL_FRONT) {
frontMaterial.setShininess(val);
changed = frontMaterial.setShininess(val);
} else if (face == GL11.GL_BACK) {
backMaterial.setShininess(val);
changed = backMaterial.setShininess(val);
} else if (face == GL11.GL_FRONT_AND_BACK) {
frontMaterial.setShininess(val);
backMaterial.setShininess(val);
final boolean f = frontMaterial.setShininess(val);
final boolean b = backMaterial.setShininess(val);
changed = f || b;
} else {
throw new RuntimeException("Unsupported face value for glMaterial: " + face);
}
lightingGeneration++;
if (changed) lightingGeneration++;
}
public static void glMateriali(int face, int pname, int val) {
@@ -4063,7 +4186,7 @@ public class GLStateManager {
}
}
final LightStateStack lightState = lightDataStates[light - GL11.GL_LIGHT0];
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_AMBIENT -> lightState.setAmbient(params);
case GL11.GL_DIFFUSE -> lightState.setDiffuse(params);
case GL11.GL_SPECULAR -> lightState.setSpecular(params);
@@ -4074,9 +4197,9 @@ public class GLStateManager {
case GL11.GL_CONSTANT_ATTENUATION -> lightState.setConstantAttenuation(params);
case GL11.GL_LINEAR_ATTENUATION -> lightState.setLinearAttenuation(params);
case GL11.GL_QUADRATIC_ATTENUATION -> lightState.setQuadraticAttenuation(params);
default -> {}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLight(int light, int pname, IntBuffer params) {
@@ -4096,7 +4219,7 @@ public class GLStateManager {
}
}
final LightStateStack lightState = lightDataStates[light - GL11.GL_LIGHT0];
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_AMBIENT -> lightState.setAmbient(params);
case GL11.GL_DIFFUSE -> lightState.setDiffuse(params);
case GL11.GL_SPECULAR -> lightState.setSpecular(params);
@@ -4107,9 +4230,9 @@ public class GLStateManager {
case GL11.GL_CONSTANT_ATTENUATION -> lightState.setConstantAttenuation(params);
case GL11.GL_LINEAR_ATTENUATION -> lightState.setLinearAttenuation(params);
case GL11.GL_QUADRATIC_ATTENUATION -> lightState.setQuadraticAttenuation(params);
default -> {}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLightf(int light, int pname, float param) {
@@ -4121,15 +4244,15 @@ public class GLStateManager {
}
}
final LightStateStack lightState = lightDataStates[light - GL11.GL_LIGHT0];
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_SPOT_EXPONENT -> lightState.setSpotExponent(param);
case GL11.GL_SPOT_CUTOFF -> lightState.setSpotCutoff(param);
case GL11.GL_CONSTANT_ATTENUATION -> lightState.setConstantAttenuation(param);
case GL11.GL_LINEAR_ATTENUATION -> lightState.setLinearAttenuation(param);
case GL11.GL_QUADRATIC_ATTENUATION -> lightState.setQuadraticAttenuation(param);
default -> {}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLighti(int light, int pname, int param) {
@@ -4141,16 +4264,15 @@ public class GLStateManager {
}
}
final LightStateStack lightState = lightDataStates[light - GL11.GL_LIGHT0];
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_SPOT_EXPONENT -> lightState.setSpotExponent(param);
case GL11.GL_SPOT_CUTOFF -> lightState.setSpotCutoff(param);
case GL11.GL_CONSTANT_ATTENUATION -> lightState.setConstantAttenuation(param);
case GL11.GL_LINEAR_ATTENUATION -> lightState.setLinearAttenuation(param);
case GL11.GL_QUADRATIC_ATTENUATION -> lightState.setQuadraticAttenuation(param);
default -> {
}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLightModel(int pname, FloatBuffer params) {
@@ -4161,14 +4283,13 @@ public class GLStateManager {
return;
}
}
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> lightModel.setAmbient(params);
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> lightModel.setLocalViewer(params);
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> lightModel.setTwoSide(params);
default -> {
}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLightModel(int pname, IntBuffer params) {
@@ -4187,15 +4308,14 @@ public class GLStateManager {
return;
}
}
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> lightModel.setAmbient(params);
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> lightModel.setColorControl(params);
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> lightModel.setLocalViewer(params);
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> lightModel.setTwoSide(params);
default -> {
}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
public static void glLightModelf(int pname, float param) {
@@ -4208,13 +4328,12 @@ public class GLStateManager {
}
if (isCachingEnabled()) {
switch (pname) {
final boolean changed = switch (pname) {
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> lightModel.setLocalViewer(param);
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> lightModel.setTwoSide(param);
default -> {
}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
}
@@ -4228,14 +4347,13 @@ public class GLStateManager {
}
if (isCachingEnabled()) {
switch (pname) {
final boolean changed = switch (pname) {
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> lightModel.setColorControl(param);
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> lightModel.setLocalViewer(param);
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> lightModel.setTwoSide(param);
default -> {
}
}
lightingGeneration++;
default -> false;
};
if (changed) lightingGeneration++;
}
}
@@ -4310,7 +4428,6 @@ public class GLStateManager {
GLDebug.debugMessage("Activating Program - " + program + ":" + programName);
}
RENDER_BACKEND.useProgram(program);
CompatUniformManager.onUseProgram(program);
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
GLSMHooks.programChangeEvent.previousProgram = prev;
GLSMHooks.programChangeEvent.newProgram = program;
@@ -4324,7 +4441,7 @@ public class GLStateManager {
public static void glTexImage1D(int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage1D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage1D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
@@ -4334,7 +4451,7 @@ public class GLStateManager {
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
@@ -4344,7 +4461,7 @@ public class GLStateManager {
public static void glTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
internalformat = changeFormatIfDeprecated(internalformat);
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexImage3D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
@@ -4353,7 +4470,7 @@ public class GLStateManager {
public static void glTexSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage1D in display lists not yet implemented");
throw DisplayListManager.unsupportedInList("glTexSubImage1D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage1D(target, level, xoffset, width, format, type, pixels);
@@ -4442,7 +4559,7 @@ public class GLStateManager {
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromByteBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromByteBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -4450,12 +4567,13 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromIntBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromIntBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels, pixelUnpackState));
if (mode == RecordMode.COMPILE) {
return;
}
@@ -4463,18 +4581,20 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage2D with buffer offset in display lists not yet supported");
throw DisplayListManager.unsupportedInList("glTexSubImage2D with buffer offset");
}
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
maybeGenerateMipmap(target, level);
}
public static void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glTexSubImage3D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glTexSubImage3D");
}
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
@@ -4483,35 +4603,37 @@ public class GLStateManager {
public static void glCopyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexImage1D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexImage1D");
}
RENDER_BACKEND.copyTexImage1D(target, level, internalFormat, x, y, width, border);
}
public static void glCopyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexImage2D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexImage2D");
}
RENDER_BACKEND.copyTexImage2D(target, level, internalFormat, x, y, width, height, border);
maybeGenerateMipmap(target, level);
}
public static void glCopyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage1D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage1D");
}
RENDER_BACKEND.copyTexSubImage1D(target, level, xoffset, x, y, width);
}
public static void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage2D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage2D");
}
RENDER_BACKEND.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
maybeGenerateMipmap(target, level);
}
public static void glCopyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glCopyTexSubImage3D in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glCopyTexSubImage3D");
}
RENDER_BACKEND.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height);
}
@@ -4552,6 +4674,12 @@ public class GLStateManager {
}
public static void glHint(int target, int hint) {
// These hint targets are removed in core profile, ignore them
switch (target) {
case GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_POINT_SMOOTH_HINT, GL11.GL_FOG_HINT, GL14.GL_GENERATE_MIPMAP_HINT -> {
return;
}
}
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordHint(target, hint);
@@ -4640,7 +4768,7 @@ public class GLStateManager {
public static void glReadBuffer(int mode) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glReadBuffer in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glReadBuffer");
}
RENDER_BACKEND.readBuffer(mode);
}
@@ -4709,18 +4837,16 @@ public class GLStateManager {
}
}
// glPixelStore* isn'tcompiled into display lists, it executes immediately
public static void glPixelStorei(int pname, int param) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glPixelStorei in display lists not yet implemented - if you see this, please report!");
if (isCachingEnabled()) {
pixelUnpackState = pixelUnpackState.with(pname, param);
}
RENDER_BACKEND.pixelStorei(pname, param);
}
public static void glPixelStoref(int pname, float param) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glPixelStoref in display lists not yet implemented - if you see this, please report!");
}
RENDER_BACKEND.pixelStoref(pname, param);
glPixelStorei(pname, param < 0 ? -Math.round(-param) : Math.round(param));
}
// Display List Commands
@@ -4815,7 +4941,7 @@ public class GLStateManager {
// Multisample Commands
public static void glSampleCoverage(float value, boolean invert) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glSampleCoverage in display lists not yet implemented - if you see this, please report!");
throw DisplayListManager.unsupportedInList("glSampleCoverage");
}
RENDER_BACKEND.sampleCoverage(value, invert);
}
@@ -4965,6 +5091,14 @@ public class GLStateManager {
RENDER_BACKEND.bindBuffer(target, buffer);
}
public static void forcePixelUnpackState(PixelUnpackState target) {
PixelUnpackState.applyDiff(pixelUnpackState, target);
}
public static void restorePixelUnpackState(PixelUnpackState applied) {
PixelUnpackState.applyDiff(applied, pixelUnpackState);
}
static void suspendPixelUnpackBuffer() {
if (boundPixelUnpackBuffer != 0) {
RENDER_BACKEND.bindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
@@ -5147,8 +5281,12 @@ public class GLStateManager {
return;
}
setLightmapTextureCoords(target, s, t);
} else {
final int unit = target - GL13.GL_TEXTURE0;
if (unit >= 2 && unit < 4) {
ShaderManager.setCurrentTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
// Units 2+ silently ignored — shader uses v_TexCoord0 for all non-lightmap units
}
public static void glMultiTexCoord2d(int target, double s, double t) {
@@ -507,11 +507,6 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13.glSampleCoverage(value, invert);
@@ -8,6 +8,8 @@ import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.List;
/**
* Abstract rendering backend.
@@ -25,6 +27,14 @@ public abstract class RenderBackend {
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
public boolean supportsFileDrop() { return false; }
public void startFileDrop() {}
public void stopFileDrop() {}
public List<String> pollDroppedFiles() { return Collections.emptyList(); }
public abstract int getMinGLSLVersion();
public abstract void flush();
@@ -115,7 +125,6 @@ public abstract class RenderBackend {
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void pixelStoref(int pname, float param) {}
public void sampleCoverage(float value, boolean invert) {}
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
* long[0]: global bits (11) + unit 0 (47 bits at offset 11)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,12 +63,13 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 10;
private static final int GLOBAL_BITS = 11;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -119,6 +120,11 @@ public final class FragmentKey {
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
@@ -227,6 +233,7 @@ public final class FragmentKey {
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -252,6 +259,28 @@ public final class FragmentKey {
return false;
}
/** Per-unit enable bits as a 4-bit mask (bit i = unit i enabled). */
public int enabledUnitMask() {
int mask = 0;
final int n = Math.min(nrEnabledUnits(), MAX_UNITS);
for (int i = 0; i < n; i++) {
if (unitEnabled(i)) mask |= (1 << i);
}
return mask;
}
/** Per-unit enable bits from a packed scratch as a 4-bit mask (bit i = unit i enabled). */
public static int unitMaskFromPacked(long[] packed, int len) {
int mask = 0;
if (len < 1) return 0;
if (((packed[0] >>> GLOBAL_BITS) & 1L) != 0) mask |= 1;
final int n = Math.min(len, MAX_UNITS);
for (int i = 1; i < n; i++) {
if ((packed[i] & 1L) != 0) mask |= (1 << i);
}
return mask;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
@@ -338,10 +367,10 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), nrEnabledUnits()));
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -37,11 +37,11 @@ public final class FragmentShaderGenerator {
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");
// Per-unit texcoord inputs. Each enabled unit sources from its own varying
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("in vec4 v_TexCoord").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
@@ -65,6 +65,9 @@ public final class FragmentShaderGenerator {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.overlayEnabled()) {
sb.append("uniform vec4 u_OverlayColor;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
@@ -75,11 +78,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
// Unit 1 is the lightmap — uses v_TexCoord1.
// Units 2-3 intentionally share unit 0's texture coordinates since
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", v_TexCoord").append(i).append(".st);\n");
}
}
@@ -87,14 +86,21 @@ public final class FragmentShaderGenerator {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
emitOverlay(sb, key);
return;
}
// Process units in order; "previous" starts as vertex color
// Process units in order; "previous" starts as vertex color.
boolean firstUnit = true;
boolean overlayInjected = false;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
if (!firstUnit && !overlayInjected) {
emitOverlay(sb, key);
overlayInjected = true;
}
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
@@ -107,6 +113,16 @@ public final class FragmentShaderGenerator {
}
firstUnit = false;
}
if (!overlayInjected) emitOverlay(sb, key);
}
/**
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -22,12 +22,10 @@ public final class GeometryShaderGenerator {
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("in vec4 v_TexCoord0_gs[];\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1_gs[];\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
@@ -37,12 +35,10 @@ public final class GeometryShaderGenerator {
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.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -82,12 +78,10 @@ public final class GeometryShaderGenerator {
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
}
if (key.lightmapEnabled()) {
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
@@ -26,18 +26,28 @@ public class Program {
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// Per-program uniform dirty-tracking
static final class UploadState {
int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
int texGenGen = -1, clipPlaneGen = -1;
float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
int viewportWidth = -1, viewportHeight = -1;
}
final UploadState uploadState = new UploadState();
// Uniform locations (-1 = not present in this variant)
// Matrices
public int locModelViewMatrix = -1;
public int locProjectionMatrix = -1;
public int locMVPMatrix = -1;
public int locNormalMatrix = -1;
public int locTextureMatrix0 = -1;
public final int[] locTextureMatrix = { -1, -1, -1, -1 };
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public int locCurrentTexCoord = -1;
public final int[] locCurrentTexCoord = { -1, -1, -1, -1 };
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
@@ -92,6 +102,7 @@ public class Program {
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locOverlayColor = -1;
public int locFogParams = -1;
public int locFogColor = -1;
@@ -107,11 +118,13 @@ public class Program {
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
locTextureMatrix0 = loc("u_TextureMatrix0");
for (int i = 0; i < 4; i++) {
locTextureMatrix[i] = loc("u_TextureMatrix" + i);
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + i);
}
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentTexCoord = loc("u_CurrentTexCoord");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
@@ -169,6 +182,7 @@ public class Program {
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locOverlayColor = loc("u_OverlayColor");
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
@@ -22,7 +22,7 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
/**
* FFP shader manager
*/
public class ShaderManager {
public final class ShaderManager {
private static final class Holder {
@@ -36,17 +36,26 @@ public class ShaderManager {
@Getter
private boolean active = false;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@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);
private static final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
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 final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
@@ -79,37 +88,37 @@ public class ShaderManager {
public void activate() {
active = true;
updateVariant(true, true, true, true);
uploadUniforms();
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public void deactivate() {
active = false;
currentProgram = null;
lastBoundProgramId = -1;
currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
GLStateManager.flushDeferredVertexAttribs();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
if (!active) {
if (enabled) {
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
CompatUniformManager.onUseProgram(currentProgramId);
return;
}
active = true;
} else {
return;
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
GLStateManager.flushDeferredVertexAttribs(currentVertexFlags);
if (!active) return;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
@@ -132,18 +141,16 @@ public class ShaderManager {
preDraw(currentVertexFlags);
}
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
commitVariant(vkPacked, fkLen);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
}
private void uploadUniforms() {
@@ -158,7 +165,12 @@ public class ShaderManager {
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
currentTexCoord.set(s, t, r, q);
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
@@ -46,116 +46,99 @@ public class Uniforms {
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
// Dirty tracking: last-uploaded generation per category + program ID.
// Program change forces full re-upload since uniform locations differ.
private int lastProgramId = -1;
private int lastMvGen = -1;
private int lastProjGen = -1;
private int lastTexMatGen = -1;
private int lastLightingGen = -1;
private int lastFragmentGen = -1;
private int lastColorGen = -1;
private int lastNormalGen = -1;
private int lastTexCoordGen = -1;
private float lastLightmapX = Float.NaN;
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
private float lastLineWidth = Float.NaN;
private int lastViewportWidth = -1;
private int lastViewportHeight = -1;
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
* Uses generation counters to skip categories that haven't changed since last upload.
*/
public void upload(Program program) {
final boolean programChanged = program.getProgramId() != lastProgramId;
lastProgramId = program.getProgramId();
final Program.UploadState st = program.uploadState;
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != st.mvGen;
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen;
final boolean projChanged = projGen != st.projGen;
final boolean texMatChanged = texMatGen != st.texMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged);
st.mvGen = mvGen;
st.mvLinearGen = mvLinearGen;
st.projGen = projGen;
st.texMatGen = texMatGen;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
if (litGen != st.lightingGen) {
uploadLighting(program);
lastLightingGen = litGen;
st.lightingGen = litGen;
}
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (programChanged || colorGen != lastColorGen) {
if (colorGen != st.colorGen) {
uploadCurrentColor(program);
lastColorGen = colorGen;
st.colorGen = colorGen;
}
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (programChanged || normalGen != lastNormalGen) {
if (normalGen != st.normalGen) {
uploadCurrentNormal(program);
lastNormalGen = normalGen;
st.normalGen = normalGen;
}
}
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
uploadCurrentTexCoord(program);
lastTexCoordGen = texGen;
if (texGen != st.texCoordGen) {
uploadCurrentTexCoords(program);
st.texCoordGen = texGen;
}
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
st.lightmapX = GLSMConfig.lastBrightnessX;
st.lightmapY = GLSMConfig.lastBrightnessY;
}
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (programChanged || tgGen != lastTexGenGen) {
if (tgGen != st.texGenGen) {
uploadTexGen(program);
lastTexGenGen = tgGen;
st.texGenGen = tgGen;
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
if (cpGen != st.clipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
st.clipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
if (fragGen != st.fragmentGen) {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
st.fragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
uploadWideLineUniforms(program);
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private void uploadMatrices(Program program, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -165,7 +148,9 @@ public class Uniforms {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
}
if (mvLinearChanged) {
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
@@ -198,16 +183,15 @@ public class Uniforms {
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
// Texture matrices (per-unit).
if (texMatChanged) {
// Texture matrix unit 0
if (program.locTextureMatrix0 != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
for (int i = 0; i < 4; i++) {
if (program.locTextureMatrix[i] != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(i);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix[i], false, mat4Buf);
}
}
// Texture matrix unit 1 (lightmap)
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
@@ -315,19 +299,22 @@ public class Uniforms {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
final var color = GLStateManager.getColor();
tempVec4.set(
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.clear();
vec4Buf.put(Math.clamp(0f, 1f, color.getRed()));
vec4Buf.put(Math.clamp(0f, 1f, color.getGreen()));
vec4Buf.put(Math.clamp(0f, 1f, color.getBlue()));
vec4Buf.put(Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.flip();
tempVec4.get(vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoord(Program program) {
if (program.locCurrentTexCoord == -1) return;
final var tc = ShaderManager.getCurrentTexCoord();
uploadVec4(program.locCurrentTexCoord, tc);
private void uploadCurrentTexCoords(Program program) {
for (int i = 0; i < 4; i++) {
if (program.locCurrentTexCoord[i] != -1) {
uploadVec4(program.locCurrentTexCoord[i], ShaderManager.getCurrentTexCoord(i));
}
}
}
private void uploadCurrentLightmapCoord(Program program) {
@@ -341,10 +328,7 @@ public class Uniforms {
// Upload current normal from FFPShaderManager's tracked normal
final var normal = ShaderManager.getCurrentNormal();
vec3Buf.clear();
vec3Buf.put(normal.x);
vec3Buf.put(normal.y);
vec3Buf.put(normal.z);
vec3Buf.flip();
normal.get(vec3Buf);
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
@@ -380,10 +364,7 @@ public class Uniforms {
private void uploadPlane(int loc, float[] plane) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(plane[0]);
vec4Buf.put(plane[1]);
vec4Buf.put(plane[2]);
vec4Buf.put(plane[3]);
vec4Buf.put(plane, 0, 4);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -421,6 +402,16 @@ public class Uniforms {
}
}
if (fk.overlayEnabled() && program.locOverlayColor != -1) {
vec4Buf.clear();
vec4Buf.put(GLStateManager.getOverlayR());
vec4Buf.put(GLStateManager.getOverlayG());
vec4Buf.put(GLStateManager.getOverlayB());
vec4Buf.put(GLStateManager.getOverlayA());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
}
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
@@ -459,29 +450,26 @@ public class Uniforms {
}
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
private void uploadWideLineUniforms(Program program) {
final Program.UploadState st = program.uploadState;
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
if (lineWidth != st.lineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
st.lineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
if (vw != st.viewportWidth || vh != st.viewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
st.viewportWidth = vw;
st.viewportHeight = vh;
}
}
private void uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(v.x);
vec4Buf.put(v.y);
vec4Buf.put(v.z);
vec4Buf.put(v.w);
vec4Buf.flip();
v.get(vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -11,9 +12,14 @@ 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.
*
* Per-unit fields are 4-bit base-indexed: bit (BASE + i) for unit i in [0..3].
* Unit 1 is the lightmap; texcoord enable for unit 1 also gates the lightmap-specific code paths.
*/
public final class VertexKey {
public static final int MAX_UNITS = 4;
private final long packed;
private static final int BIT_LIGHTING = 0;
@@ -24,26 +30,25 @@ public final class VertexKey {
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_FOG_DIST_MODE = 8; // 2 bits: 8-9
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;
private static final int BIT_WIDE_LINE = 35;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -72,9 +77,10 @@ public final class VertexKey {
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 unitTexCoordEnabled(int unit) { return bit(BIT_UNIT_TEX_BASE + unit); }
public boolean unitTexMatEnabled(int unit) { return bit(BIT_UNIT_TEXMAT_BASE + unit); }
public boolean anyUnitTexCoordEnabled() { return ((packed >> BIT_UNIT_TEX_BASE) & 0xF) != 0; }
public boolean lightmapEnabled() { return unitTexCoordEnabled(1); }
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); }
@@ -82,28 +88,18 @@ public final class VertexKey {
/** 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). */
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). Unit 0 only; see TODO in packFromState. */
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 wide line GS emulation is active for this variant. */
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
/** 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; }
@@ -127,7 +123,7 @@ public final class VertexKey {
};
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
@@ -172,23 +168,18 @@ public final class VertexKey {
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);
final TextureUnitArray texUnit = GLStateManager.getTextures();
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
// Check if texture matrix unit 0 is non-identity
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
if (!MatrixHelper.isIdentity(texMat)) {
bits |= (1L << BIT_TEX_MATRIX);
}
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
final var texUnit = GLStateManager.getTextures();
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
final TexGenState tg = texUnit.getTexGenState(0);
boolean anyTexGen = false;
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
@@ -211,9 +202,9 @@ public final class VertexKey {
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)
// Force unit-0 texture matrix on when texgen is active (end portal pattern always uses it).
if (anyTexGen) {
bits |= (1L << BIT_TEX_MATRIX);
bits |= (1L << BIT_UNIT_TEXMAT_BASE);
}
// Vertex format flags
@@ -222,12 +213,10 @@ public final class VertexKey {
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);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
@@ -235,8 +224,8 @@ public final class VertexKey {
return bits;
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
static VertexKey fromPacked(long packed) {
@@ -257,9 +246,11 @@ public final class VertexKey {
@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 wline=%b]",
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
@@ -42,6 +42,8 @@ public final class VertexShaderGenerator {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_Color", "v_Color_gs");
}
@@ -76,11 +78,14 @@ public final class VertexShaderGenerator {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.textureMatrixEnabled()) {
sb.append("uniform mat4 u_TextureMatrix0;\n");
for (int i = 0; i < VertexKey.MAX_UNITS; i++) {
if (i == 1) continue;
if (key.unitTexMatEnabled(i)) {
sb.append("uniform mat4 u_TextureMatrix").append(i).append(";\n");
}
}
// TexGen plane uniforms
// TexGen plane uniforms (unit 0 only - TODO: per-unit)
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");
@@ -98,9 +103,11 @@ public final class VertexShaderGenerator {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
sb.append("uniform vec4 u_CurrentTexCoord;\n");
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
@@ -178,12 +185,15 @@ public final class VertexShaderGenerator {
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
// Per-unit varying outputs. Each enabled texture unit (and texgen, which targets unit 0) gets its own v_TexCoord<i>.
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -331,22 +341,18 @@ public final class VertexShaderGenerator {
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.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
} else {
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
}
sb.append(" v_TexCoord0 = ").append(src).append(";\n");
}
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
@@ -354,6 +360,16 @@ public final class VertexShaderGenerator {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = "u_CurrentTexCoord" + i;
if (key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
}
}
}
/**
@@ -18,6 +18,7 @@ public final class GLSMHooks {
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
@@ -28,6 +29,7 @@ public final class GLSMHooks {
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
private GLSMHooks() {}
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ShaderColorChangeEvent extends MutableEvent {
public float red;
public float green;
public float blue;
public float alpha;
}
@@ -35,11 +35,30 @@ public final class EcosystemNarrowRules {
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("FiskHeroes", "com.fiskmods.heroes.asm", List.of(
"com.fiskmods.heroes.asm.SHLoadingPlugin",
"com.fiskmods.heroes.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("FoamFix", "pl.asie.foamfix", List.of(
"pl.asie.foamfix.bugfixmod",
"pl.asie.foamfix.coremod",
"pl.asie.foamfix.forkage",
"pl.asie.foamfix.ghostbuster",
"pl.asie.foamfix.FoamFixMod",
"pl.asie.foamfix.HackyMessageFactoryWrapper",
"pl.asie.foamfix.LaunchWrapperRuntimeFix",
"pl.asie.foamfix.ProxyClient",
"pl.asie.foamfix.ProxyCommon",
"pl.asie.foamfix.repack.com.unascribed.ears.asm",
"pl.asie.foamfix.repack.com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
@@ -653,12 +653,19 @@ public final class CommandRecorder {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
memFree(buffer);
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
@@ -666,7 +673,10 @@ public final class CommandRecorder {
}
final int size = size();
buffer.limit(size);
return memRealloc(buffer, size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public DisplayListCommand[] getComplexObjects() {
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
final class PixelDataSnapshot {
private PixelDataSnapshot() {}
static @Nullable ByteBuffer copy(@Nullable ByteBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining()).order(ByteOrder.nativeOrder());
out.put(pixels);
out.flip();
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable IntBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asIntBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable FloatBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asFloatBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable DoubleBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 8).order(ByteOrder.nativeOrder());
out.asDoubleBuffer().put(pixels);
pixels.position(pos);
return out;
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexImage2DCmd(
int border,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexSubImage2DCmd(
int height,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexSubImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -27,65 +27,75 @@ public class LightModelState implements ISettableState<LightModelState> {
twoSide = 0.0F;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setColorControl(int val) {
public boolean setColorControl(int val) {
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
this.colorControl = val;
return true;
}
return false;
}
public void setColorControl(IntBuffer newBuffer) {
setColorControl(newBuffer.get());
public boolean setColorControl(IntBuffer newBuffer) {
return setColorControl(newBuffer.get());
}
public void setLocalViewer(float val) {
public boolean setLocalViewer(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
this.localViewer = val;
return true;
}
return false;
}
public void setLocalViewer(FloatBuffer newBuffer) {
setLocalViewer(newBuffer.get());
public boolean setLocalViewer(FloatBuffer newBuffer) {
return setLocalViewer(newBuffer.get());
}
public void setLocalViewer(IntBuffer newBuffer) {
setLocalViewer((float) newBuffer.get());
public boolean setLocalViewer(IntBuffer newBuffer) {
return setLocalViewer((float) newBuffer.get());
}
public void setLocalViewer(int val) {
setLocalViewer((float) val);
public boolean setLocalViewer(int val) {
return setLocalViewer((float) val);
}
public void setTwoSide(float val) {
public boolean setTwoSide(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
this.twoSide = val;
return true;
}
return false;
}
public void setTwoSide(FloatBuffer newBuffer) {
setTwoSide(newBuffer.get());
public boolean setTwoSide(FloatBuffer newBuffer) {
return setTwoSide(newBuffer.get());
}
public void setTwoSide(IntBuffer newBuffer) {
setTwoSide((float) newBuffer.get());
public boolean setTwoSide(IntBuffer newBuffer) {
return setTwoSide((float) newBuffer.get());
}
public void setTwoSide(int val) {
setTwoSide((float) val);
public boolean setTwoSide(int val) {
return setTwoSide((float) val);
}
@Override
@@ -17,6 +17,7 @@ public class LightState implements ISettableState<LightState> {
private static final Vector4i vector4i = new Vector4i();
private static final Vector4f vector4f = new Vector4f();
private static final Vector3f vector3f = new Vector3f();
private static final Vector3i vector3i = new Vector3i();
private static final Matrix3f matrix3f = new Matrix3f();
@@ -27,6 +28,10 @@ public class LightState implements ISettableState<LightState> {
public final Vector4f specular;
public final Vector4f position;
public final Vector3f spotDirection;
private final Vector4f rawPosition;
private int posMvGeneration = -1;
private final Vector3f rawSpotDirection;
private int spotDirLinearGeneration = -1;
public float spotExponent;
public float spotCutoff;
public float spotCosCutoff;
@@ -66,178 +71,221 @@ public class LightState implements ISettableState<LightState> {
this.linearAttenuation = linearAttenuation;
this.quadraticAttenuation = quadraticAttenuation;
this.rawPosition = new Vector4f(position);
this.position = position;
this.position.mul(GLStateManager.getModelViewMatrix());
this.posMvGeneration = GLStateManager.mvGeneration;
this.rawSpotDirection = new Vector3f(spotDirection);
this.spotDirection = spotDirection;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.mul(matrix3f);
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setPosition(FloatBuffer newBuffer) {
// We are bypassing cache everytime for position, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
this.position.set(newBuffer);
this.position.mul(GLStateManager.getModelViewMatrix());
public boolean setPosition(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
return applyPosition(vector4f);
}
public void setPosition(IntBuffer newBuffer) {
public boolean setPosition(IntBuffer newBuffer) {
vector4i.set(newBuffer);
this.position.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
this.position.mul(GLStateManager.getModelViewMatrix());
vector4f.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
return applyPosition(vector4f);
}
public void setSpotDirection(FloatBuffer newBuffer) {
// We are bypassing cache everytime for spot direction, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(newBuffer);
this.spotDirection.mul(matrix3f);
private boolean applyPosition(Vector4f raw) {
final int mvGen = GLStateManager.mvGeneration;
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
this.rawPosition.set(raw);
this.posMvGeneration = mvGen;
this.position.set(raw);
this.position.mul(GLStateManager.getModelViewMatrix());
return true;
}
return false;
}
public void setSpotDirection(IntBuffer newBuffer) {
public boolean setSpotDirection(FloatBuffer newBuffer) {
vector3f.set(newBuffer);
return applySpotDirection(vector3f);
}
public boolean setSpotDirection(IntBuffer newBuffer) {
vector3i.set(newBuffer);
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
this.spotDirection.mul(matrix3f);
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
return applySpotDirection(vector3f);
}
public void setSpotExponent(FloatBuffer newBuffer) {
setSpotExponent(newBuffer.get());
private boolean applySpotDirection(Vector3f raw) {
final int mvLinearGen = GLStateManager.mvLinearGeneration;
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
this.rawSpotDirection.set(raw);
this.spotDirLinearGeneration = mvLinearGen;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(raw);
this.spotDirection.mul(matrix3f);
return true;
}
return false;
}
public void setSpotExponent(IntBuffer newBuffer) {
setSpotExponent((float) newBuffer.get());
public boolean setSpotExponent(FloatBuffer newBuffer) {
return setSpotExponent(newBuffer.get());
}
public void setSpotExponent(int i) {
setSpotExponent((float) i);
public boolean setSpotExponent(IntBuffer newBuffer) {
return setSpotExponent((float) newBuffer.get());
}
public void setSpotExponent(float f) {
public boolean setSpotExponent(int i) {
return setSpotExponent((float) i);
}
public boolean setSpotExponent(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
this.spotExponent = f;
return true;
}
return false;
}
public void setSpotCutoff(FloatBuffer newBuffer) {
setSpotCutoff(newBuffer.get());
public boolean setSpotCutoff(FloatBuffer newBuffer) {
return setSpotCutoff(newBuffer.get());
}
public void setSpotCutoff(IntBuffer newBuffer) {
setSpotCutoff((float) newBuffer.get());
public boolean setSpotCutoff(IntBuffer newBuffer) {
return setSpotCutoff((float) newBuffer.get());
}
public void setSpotCutoff(int i) {
setSpotCutoff((float) i);
public boolean setSpotCutoff(int i) {
return setSpotCutoff((float) i);
}
public void setSpotCutoff(float f) {
public boolean setSpotCutoff(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
this.spotCutoff = f;
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
return true;
}
return false;
}
public void setConstantAttenuation(FloatBuffer newBuffer) {
setConstantAttenuation(newBuffer.get());
public boolean setConstantAttenuation(FloatBuffer newBuffer) {
return setConstantAttenuation(newBuffer.get());
}
public void setConstantAttenuation(IntBuffer newBuffer) {
setConstantAttenuation((float) newBuffer.get());
public boolean setConstantAttenuation(IntBuffer newBuffer) {
return setConstantAttenuation((float) newBuffer.get());
}
public void setConstantAttenuation(int i) {
setConstantAttenuation((float) i);
public boolean setConstantAttenuation(int i) {
return setConstantAttenuation((float) i);
}
public void setConstantAttenuation(float f) {
public boolean setConstantAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
this.constantAttenuation = f;
return true;
}
return false;
}
public void setLinearAttenuation(FloatBuffer newBuffer) {
setLinearAttenuation(newBuffer.get());
public boolean setLinearAttenuation(FloatBuffer newBuffer) {
return setLinearAttenuation(newBuffer.get());
}
public void setLinearAttenuation(IntBuffer newBuffer) {
setLinearAttenuation((float) newBuffer.get());
public boolean setLinearAttenuation(IntBuffer newBuffer) {
return setLinearAttenuation((float) newBuffer.get());
}
public void setLinearAttenuation(int i) {
setLinearAttenuation((float) i);
public boolean setLinearAttenuation(int i) {
return setLinearAttenuation((float) i);
}
public void setLinearAttenuation(float f) {
public boolean setLinearAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
this.linearAttenuation = f;
return true;
}
return false;
}
public void setQuadraticAttenuation(FloatBuffer newBuffer) {
setQuadraticAttenuation(newBuffer.get());
public boolean setQuadraticAttenuation(FloatBuffer newBuffer) {
return setQuadraticAttenuation(newBuffer.get());
}
public void setQuadraticAttenuation(IntBuffer newBuffer) {
setQuadraticAttenuation((float) newBuffer.get());
public boolean setQuadraticAttenuation(IntBuffer newBuffer) {
return setQuadraticAttenuation((float) newBuffer.get());
}
public void setQuadraticAttenuation(int i) {
setQuadraticAttenuation((float) i);
public boolean setQuadraticAttenuation(int i) {
return setQuadraticAttenuation((float) i);
}
public void setQuadraticAttenuation(float f) {
public boolean setQuadraticAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
this.quadraticAttenuation = f;
return true;
}
return false;
}
@Override
@@ -246,7 +294,11 @@ public class LightState implements ISettableState<LightState> {
this.diffuse.set(state.diffuse);
this.specular.set(state.specular);
this.position.set(state.position);
this.rawPosition.set(state.rawPosition);
this.posMvGeneration = state.posMvGeneration;
this.spotDirection.set(state.spotDirection);
this.rawSpotDirection.set(state.rawSpotDirection);
this.spotDirLinearGeneration = state.spotDirLinearGeneration;
this.spotExponent = state.spotExponent;
this.spotCutoff = state.spotCutoff;
this.spotCosCutoff = state.spotCosCutoff;
@@ -37,82 +37,100 @@ public class MaterialState implements ISettableState<MaterialState> {
colorIndexes = new Vector3f(0.0F, 1.0F, 1.0F);
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setEmission(FloatBuffer newBuffer) {
public boolean setEmission(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setEmission(IntBuffer newBuffer) {
public boolean setEmission(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setShininess(float val) {
public boolean setShininess(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
this.shininess = val;
return true;
}
return false;
}
// On the buffer versions of shininess, we need to call `get(0)` explicitly specifying the index
// otherwise calling `get()` advances the buffer position, and some mods may re-use the same buffer
// in subsequent calls, resulting in a BufferUnderflowException.
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get(0)); }
public boolean setShininess(FloatBuffer newBuffer) { return setShininess(newBuffer.get(0)); }
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get(0)); }
public boolean setShininess(IntBuffer newBuffer) { return setShininess((float) newBuffer.get(0)); }
public void setShininess(int val) { setShininess((float) val);}
public boolean setShininess(int val) { return setShininess((float) val);}
public void setColorIndexes(FloatBuffer newBuffer) {
public boolean setColorIndexes(FloatBuffer newBuffer) {
// You are reading this correctly, nvidia drivers flip the y and z values in
// glMaterial specifically for GL_COLOR_INDEXES. Other drivers do not do this.
// This probably breaks things but who knows, GLSM is setup to track to what the
@@ -125,15 +143,19 @@ public class MaterialState implements ISettableState<MaterialState> {
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
public void setColorIndexes(IntBuffer newBuffer) {
public boolean setColorIndexes(IntBuffer newBuffer) {
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
@Override
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -9,6 +9,7 @@ public class TextureInfo {
@Getter protected final int id;
protected int internalFormat = -1;
protected int resolvedInternalFormat = -1;
protected int width = -1;
protected int height = -1;
@@ -16,6 +17,7 @@ public class TextureInfo {
@Getter @Setter protected int magFilter = GL11.GL_LINEAR;
@Getter @Setter protected int wrapS = GL11.GL_REPEAT;
@Getter @Setter protected int wrapT = GL11.GL_REPEAT;
@Getter @Setter protected int baseLevel = 0;
@Getter @Setter protected int maxLevel = 1000;
@Getter @Setter protected int minLod = -1000;
@Getter @Setter protected int maxLod = 1000;
@@ -23,6 +25,8 @@ public class TextureInfo {
@Getter @Setter protected float maxAnisotropy = 1.0f;
@Getter @Setter protected boolean generateMipmap = false;
protected TextureInfo(int id) {
this.id = id;
}
@@ -34,6 +38,17 @@ public class TextureInfo {
return internalFormat;
}
public int getResolvedInternalFormat() {
if (resolvedInternalFormat == -1) {
resolvedInternalFormat = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
}
return resolvedInternalFormat;
}
public boolean needsInternalFormatResolve() {
return resolvedInternalFormat == -1;
}
public int getWidth() {
if (width == -1) {
width = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_WIDTH);
@@ -5,6 +5,9 @@ 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 org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.Buffer;
import java.util.concurrent.locks.ReentrantLock;
@@ -45,6 +48,7 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
@@ -55,11 +59,24 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
return switch (internalFormat) {
case GL30.GL_COMPRESSED_RED,
GL30.GL_COMPRESSED_RG,
GL13.GL_COMPRESSED_RGB,
GL13.GL_COMPRESSED_RGBA,
GL21.GL_COMPRESSED_SRGB,
GL21.GL_COMPRESSED_SRGB_ALPHA -> true;
default -> false;
};
}
public void onDeleteTexture(int id) {
if (id < 0) return;
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.onUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
// Verify no GL errors
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after uniform upload");
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.onUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after fog uniform upload");
}
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;
/**
* 3.3 core-profile context extension
*/
public class GLSMCoreExtension implements BeforeAllCallback, AfterAllCallback {
private boolean ownsDisplay = false;
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
if (Display.isCreated()) return;
Display.setDisplayModeAndFullscreen(new DisplayMode(800, 600));
Display.setResizable(false);
Display.setFullscreen(false);
Display.create(
new PixelFormat().withDepthBits(24).withStencilBits(8),
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
ownsDisplay = true;
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete();
GLStateManager.BYPASS_CACHE = false;
}
@Override
public void afterAll(ExtensionContext context) {
if (ownsDisplay && Display.isCreated()) Display.destroy();
ownsDisplay = false;
}
}
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
@@ -19,7 +21,19 @@ import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExtensionContext.Store.CloseableResource {
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExecutionCondition, ExtensionContext.Store.CloseableResource {
// Compat profile is not available on OSX
private static final boolean IS_MAC = System.getProperty("os.name", "").toLowerCase().contains("mac");
private static final boolean FORCE_RUN = Boolean.getBoolean("angelica.glsm.test.forceRun");
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (IS_MAC && !FORCE_RUN) {
return ConditionEvaluationResult.disabled("Skipped on macOS: needs GL3+ compat profile");
}
return ConditionEvaluationResult.enabled("OS supports GL3+ compat profile");
}
private static final Unsafe theUnsafe;
static {
@@ -0,0 +1,174 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.Vector4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(GLSMCoreExtension.class)
public class GLSM_DirtyTracking_UnitTest {
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
@BeforeEach
void pushTrackedState() {
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_FOG_BIT | GL11.GL_CURRENT_BIT);
}
@AfterEach
void popTrackedState() {
GLStateManager.glPopAttrib();
}
@Test
void materialBumpsOnlyOnChange() {
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical material must not bump lightingGeneration");
newf4b(0.9F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "changed material must bump lightingGeneration once");
}
@Test
void lightModelBumpsOnlyOnChange() {
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical light model ambient must not bump");
}
@Test
void lightColorBumpsOnlyOnChange() {
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical light diffuse must not bump");
}
@Test
void lightPositionDirtySkipsUnderSameModelview() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "same position under same modelview must not bump");
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw position under changed modelview must bump");
GLStateManager.glPopMatrix();
}
@Test
void translationDoesNotBumpLinearGeneration() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
final int mv = GLStateManager.mvGeneration;
final int lin = GLStateManager.mvLinearGeneration;
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
assertEquals(mv + 1, GLStateManager.mvGeneration, "translate must bump mvGeneration");
assertEquals(lin, GLStateManager.mvLinearGeneration, "translate must NOT bump mvLinearGeneration");
GLStateManager.glRotatef(30.0F, 0.0F, 1.0F, 0.0F);
assertEquals(mv + 2, GLStateManager.mvGeneration, "rotate must bump mvGeneration");
assertEquals(lin + 1, GLStateManager.mvLinearGeneration, "rotate must bump mvLinearGeneration");
GLStateManager.glScalef(2.0F, 2.0F, 2.0F);
assertEquals(lin + 2, GLStateManager.mvLinearGeneration, "scale must bump mvLinearGeneration");
GLStateManager.glPopMatrix();
}
@Test
void fogBumpsOnlyOnChange() {
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
final int gen = GLStateManager.fragmentGeneration;
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
assertEquals(gen, GLStateManager.fragmentGeneration, "identical fog density must not bump fragmentGeneration");
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.7F);
assertEquals(gen + 1, GLStateManager.fragmentGeneration, "changed fog density must bump fragmentGeneration");
}
@Test
void spotDirectionDirtySkipsUnderTranslationOnly() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
final int gen = GLStateManager.lightingGeneration;
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
assertEquals(gen, GLStateManager.lightingGeneration, "spot direction under translation-only must not bump");
GLStateManager.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw spot direction under changed 3x3 must bump");
GLStateManager.glPopMatrix();
}
@Test
void colorMaterialFreezesLastColorOnDisable() {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
GLStateManager.glColor4f(1.0F, 0.0F, 0.0F, 1.0F); // enable-time color
GLStateManager.enableColorMaterial();
GLStateManager.glColor4f(0.0F, 0.0F, 1.0F, 1.0F); // last color while enabled
GLStateManager.disableColorMaterial();
final Vector4f diffuse = GLStateManager.getFrontMaterial().diffuse;
assertEquals(0.0F, diffuse.x, "diffuse must freeze at last color (blue), not enable-time red");
assertEquals(0.0F, diffuse.y, "green channel must be 0");
assertEquals(1.0F, diffuse.z, "blue channel must be 1");
}
static void newf4b(float x, float y, float z, float w) {
f4b.clear();
f4b.put(x).put(y).put(z).put(w);
f4b.flip();
}
static void newf3b(float x, float y, float z) {
f3b.clear();
f3b.put(x).put(y).put(z);
f3b.flip();
}
}
@@ -0,0 +1,215 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(GLSMCoreExtension.class)
public class GLSM_DisplayList_PixelStore_Test {
private static final int SRC = 8;
private static final int SUB = 4;
@AfterEach
void resetUnpackState() {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
setUnpack(0, 0, 0);
}
private static void setUnpack(int rowLength, int skipRows, int skipPixels) {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, rowLength);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, skipRows);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, skipPixels);
}
private static void setStridedUnpack() {
setUnpack(SRC, 1, 2);
}
/** RGBA source; red channel = y*size+x, alpha = 255. */
private static ByteBuffer makeSource(int size) {
final ByteBuffer buf = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < size * size; i++) {
buf.put((byte) i).put((byte) 0).put((byte) 0).put((byte) 255);
}
buf.flip();
return buf;
}
private static int makeTexture(int size, ByteBuffer pixels) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return tex;
}
private static ByteBuffer readback(int size) {
final ByteBuffer out = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
return out;
}
private static int red(ByteBuffer pixels, int size, int x, int y) {
return pixels.get((y * size + x) * 4) & 0xFF;
}
private static void assertStridedSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals((y + 1) * SRC + (x + 2), red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertTightSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals(y * SUB + x, red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertUnpack(int rowLength, int skipRows, int skipPixels) {
assertEquals(rowLength, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH));
assertEquals(skipRows, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(skipPixels, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_PIXELS));
}
@Test
void pixelStoreExecutesImmediatelyAndIsNotRecorded() {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 7);
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must execute immediately during recording");
GLStateManager.glEndList();
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must persist after glEndList");
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GLStateManager.glCallList(list);
assertEquals(0, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must not have been recorded into the list");
GLStateManager.glDeleteLists(list, 1);
}
@Test
void stridedTexSubImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertEquals(0, red(readback(SUB), SUB, 0, 0), "GL_COMPILE must not execute the upload");
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexSubImage2DCompileAndExecute() {
final ByteBuffer zeros = ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder());
final int tex = makeTexture(SUB, zeros);
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertStridedSubImage(readback(SUB));
setUnpack(0, 0, 0);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zeros);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexSubImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
}
@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
@@ -15,6 +19,98 @@ import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(GLSMExtension.class)
public class GLSM_TextureInfoCache_UnitTest {
@Test
void testGenericCompressedUploadResolvesToSpecificFormat() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
int format = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertNotEquals(-1, format);
assertNotEquals(GL30.GL_COMPRESSED_RED, format);
assertNotEquals(GL30.GL_COMPRESSED_RG, format);
assertNotEquals(GL13.GL_COMPRESSED_RGB, format);
assertNotEquals(GL13.GL_COMPRESSED_RGBA, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB_ALPHA, format);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testNonGenericUploadCachesWithoutRoundtrip() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertFalse(info.needsInternalFormatResolve());
assertEquals(GL11.GL_RGBA8, info.getResolvedInternalFormat());
assertEquals(GL11.GL_RGBA8, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testGenericCompressedPreservesAsPassedInternalFormat() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertEquals(GL13.GL_COMPRESSED_RGBA, info.getInternalFormat());
assertTrue(info.needsInternalFormatResolve());
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testRepeatedQueryAfterResolveUsesCache() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertTrue(info.needsInternalFormatResolve());
int first = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertFalse(info.needsInternalFormatResolve());
int second = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertEquals(first, second);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testIsGenericCompressedInternalFormatHelper() {
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RED));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RG));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGBA));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB_ALPHA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA8));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(0));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(-1));
}
/**
* Texture deletion is server-side; cache must be invalidated regardless of client caching state.
*/
@@ -21,6 +21,8 @@ class SharedDrawableVaoTest {
private static final String VERT_330 = "#version 330 core\nvoid main() { gl_Position = vec4(0.0); }\n";
private static final String FRAG_330 = "#version 330 core\nout vec4 o;\nvoid main() { o = vec4(0.0); }\n";
private static boolean ownsDisplay = false;
@BeforeAll
static void createContext() throws LWJGLException {
assumeTrue(!Display.isCreated(), "inherited Display -- skipping to avoid profile drift");
@@ -31,11 +33,13 @@ class SharedDrawableVaoTest {
new PixelFormat().withDepthBits(24).withStencilBits(8),
new ContextAttribs(4, 1).withProfileCore(true).withForwardCompatible(true));
GL30.glBindVertexArray(GL30.glGenVertexArrays());
ownsDisplay = true;
}
@AfterAll
static void destroyContext() {
if (Display.isCreated()) Display.destroy();
if (ownsDisplay && Display.isCreated()) Display.destroy();
ownsDisplay = false;
}
@Test
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(0);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, org.lwjgl.opengl.GL11.GL_COMPILE);
@@ -128,7 +128,7 @@ class FFPCombineShaderTest {
setupXaeroState();
FragmentKey fk = FragmentKey.fromState();
VertexKey vk = VertexKey.fromState(true, false, true, false);
VertexKey vk = VertexKey.fromState(true, false, true, false, fk.enabledUnitMask());
String fragSource = FragmentShaderGenerator.generate(fk);
String vertSource = VertexShaderGenerator.generate(vk);
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ExtendWith(GLSMCoreExtension.class)
class ProgramUniformTrackingTest {
private static Program buildProgram(boolean hasColor) {
final VertexKey vk = VertexKey.fromState(hasColor, false, false, false, 0);
final FragmentKey fk = FragmentKey.fromState();
return Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
}
@Test
void matrixTrackingIsPerProgram() {
final Uniforms uniforms = new Uniforms();
final Program a = buildProgram(true);
final Program b = buildProgram(false);
assertNotEquals(a.getProgramId(), b.getProgramId(), "distinct vertex keys must yield distinct programs");
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
final int genA = GLStateManager.mvGeneration;
GL20.glUseProgram(a.getProgramId());
uniforms.upload(a);
assertEquals(genA, a.uploadState.mvGen, "A records the mv generation it uploaded");
GLStateManager.glTranslatef(4.0F, 0.0F, 0.0F);
final int genB = GLStateManager.mvGeneration;
assertNotEquals(genA, genB);
GL20.glUseProgram(b.getProgramId());
uniforms.upload(b);
assertEquals(genB, b.uploadState.mvGen, "B records the new mv generation");
assertEquals(genA, a.uploadState.mvGen, "A is unchanged -- tracking is per-program, not global");
// Return to A with the modelview still at genB: A is stale -> re-uploads and catches up.
GL20.glUseProgram(a.getProgramId());
uniforms.upload(a);
assertEquals(genB, a.uploadState.mvGen, "A re-uploads on return and catches up to genB");
// Re-upload A with no change: gate (mvGeneration == A.mvGen) is satisfied -> skip.
uniforms.upload(a);
assertEquals(genB, a.uploadState.mvGen, "A already current -> skipped");
GLStateManager.glPopMatrix();
} finally {
GL20.glUseProgram(0);
a.destroy();
b.destroy();
}
}
}
@@ -0,0 +1,61 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelUnpackStateTest {
@Test
void defaults() {
assertTrue(PixelUnpackState.DEFAULT.isDefault());
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelUnpackState s = PixelUnpackState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packAndUnknownPnamesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
}
}
@@ -23,6 +23,9 @@ import org.lwjgl.opengl.GL44C;
import org.lwjgl.opengl.GL45C;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLDebugMessageCallback;
import org.lwjgl.sdl.SDL_DropEvent;
import org.lwjgl.sdl.SDL_EventFilter;
import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
@@ -31,6 +34,10 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* LWJGL3 GL implementation of {@link RenderBackend}.
@@ -43,6 +50,10 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
private GLDebugMessageCallback debugCallback;
private boolean debugOutputActive;
private SDL_EventFilter dropEventFilter;
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
private volatile boolean watchingDrops;
@Override
public void init() {
caps = GL.getCapabilities();
@@ -50,7 +61,49 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void shutdown() {
// no-op
stopFileDrop();
}
@Override
public boolean supportsFileDrop() {
return true;
}
@Override
public void startFileDrop() {
if (watchingDrops) return;
dropEventFilter = SDL_EventFilter.create((userData, eventPtr) -> {
if (SDL_DropEvent.ntype(eventPtr) == SDLEvents.SDL_EVENT_DROP_FILE) {
final String path = SDL_DropEvent.ndataString(eventPtr);
if (path != null && !path.isEmpty()) {
droppedFiles.add(path);
}
}
return true;
});
SDLEvents.SDL_AddEventWatch(dropEventFilter, 0L);
watchingDrops = true;
}
@Override
public void stopFileDrop() {
if (!watchingDrops) return;
SDLEvents.SDL_RemoveEventWatch(dropEventFilter, 0L);
dropEventFilter.free();
dropEventFilter = null;
droppedFiles.clear();
watchingDrops = false;
}
@Override
public List<String> pollDroppedFiles() {
if (droppedFiles.isEmpty()) return Collections.emptyList();
final List<String> out = new ArrayList<>();
String p;
while ((p = droppedFiles.poll()) != null) {
out.add(p);
}
return out;
}
@Override
@@ -491,11 +544,6 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL11C.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11C.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13C.glSampleCoverage(value, invert);
+1 -1
View File
@@ -16,7 +16,7 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.25")
}
include("glsm")
@@ -560,6 +560,9 @@ public class BatchingFontRenderer {
boolean curWave = false;
boolean curDinnerbone = false;
boolean curGradient = false;
boolean curShadow = false;
boolean curShadowCustomColor = false;
int curShadowColorOverride = 0;
int gradientStartRgb = 0, gradientEndRgb = 0;
int gradientCharIndex = 0, gradientTotalChars = 0;
float gradientStep = 0f;
@@ -651,6 +654,19 @@ public class BatchingFontRenderer {
curWave = !curWave;
} else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) {
curDinnerbone = !curDinnerbone;
} else if (fmtCode == 'u' && AngelicaConfig.enableDropShadow) {
int customRgb = (charIdx + SECTION_X_LENGTH < stringEnd)
? ColorCodeUtils.parseSectionXAt(string, charIdx + 1)
: -1;
if (customRgb != -1) {
curShadow = true;
curShadowCustomColor = true;
curShadowColorOverride = customRgb;
charIdx += SECTION_X_LENGTH;
} else {
curShadow = !curShadow;
if (!curShadow) curShadowCustomColor = false;
}
} else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) {
int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1);
int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH);
@@ -674,6 +690,8 @@ public class BatchingFontRenderer {
curWave = false;
curDinnerbone = false;
curGradient = false;
curShadow = false;
curShadowCustomColor = false;
curColor = color;
curShadowColor = shadowColor;
}
@@ -682,6 +700,8 @@ public class BatchingFontRenderer {
continue;
}
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
if (replacement != 0) {
@@ -747,13 +767,17 @@ public class BatchingFontRenderer {
renderY += (float) Math.sin(visibleCharIndex * WAVE_FREQUENCY + time) * AngelicaConfig.waveAmplitude;
}
if (enableShadow) {
final boolean drawShadow = enableShadow || curShadow;
if (drawShadow) {
final int effectiveShadowColor = curShadowCustomColor
? ((curColor & 0xFF000000) | (curShadowColorOverride & 0x00FFFFFF))
: curShadowColor;
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(curX + shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(curX + 2.0f * shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
}
}
}
@@ -775,7 +799,7 @@ public class BatchingFontRenderer {
both: 4(1 + 2 * shadowCopies + boldCopies)
*/
int charCount = 1;
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
if (drawShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
if (curBold) { charCount += boldCopies; }
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
@@ -822,6 +846,7 @@ public class BatchingFontRenderer {
}
public float getCharWidthFine(char chr) {
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
if (chr == FORMATTING_CHAR) { return -1; }
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
@@ -18,6 +18,7 @@ public final class ColorCodeUtils {
private ColorCodeUtils() {}
public static final char FORMATTING_CHAR = '§';
public static final char ESCAPED_AMPERSAND = '';
public static final int SECTION_X_PAYLOAD = 12;
public static final int SECTION_X_LENGTH = 14;
@@ -25,7 +26,7 @@ public final class ColorCodeUtils {
public static final int GRADIENT_LENGTH = 30;
/** Valid single {@code &} codes. {@code g} is excluded; {@code &g} only converts as part of a gradient. */
public static final String VALID_SINGLE_CODES = "0123456789abcdefklmnorqzv";
public static final String VALID_SINGLE_CODES = "0123456789abcdefklmnorqzvu";
private static final String[] SECTION_PREFIX = new String[128];
static {
@@ -87,6 +88,14 @@ public final class ColorCodeUtils {
StringBuilder sb = null;
int last = 0;
while (idx != -1 && idx + 1 < len) {
if (idx > 0 && text.charAt(idx - 1) == '\\') {
if (sb == null) sb = new StringBuilder(len + 16);
sb.append(text, last, idx - 1);
sb.append(ESCAPED_AMPERSAND);
last = idx + 1;
idx = text.indexOf('&', last);
continue;
}
if (text.charAt(idx + 1) == '#' && idx + 7 < len) {
boolean validHex = true;
for (int i = 2; i <= 7; i++) {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
@@ -14,6 +15,8 @@ public class ModStatus {
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isEIDLoaded;
public static boolean isMetadataExtended;
public static boolean isLotrLoaded;
public static boolean isChunkAPILoaded;
public static boolean isEIDBiomeLoaded;
@@ -35,9 +38,9 @@ public class ModStatus {
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isEIDLoaded = Loader.isModLoaded("endlessids");
isLotrLoaded = Loader.isModLoaded("lotr");
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
@@ -65,5 +68,15 @@ public class ModStatus {
isNEIDMetadataExtended = true;
}
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
eidBlockItemExtended = GeneralConfig.extendBlockItem;
eidBiomeExtended = GeneralConfig.extendBiome;
}
isEIDBiomeLoaded = eidBiomeExtended || Loader.isModLoaded("endlessids_biome");
isMetadataExtended = isNEIDMetadataExtended || eidBlockItemExtended;
}
}
@@ -132,6 +132,10 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean enableDinnerboneText;
@Config.Comment("Enable per-segment drop shadow toggle (&u) and colored shadow (&u&#RRGGBB)")
@Config.DefaultBoolean(true)
public static boolean enableDropShadow;
@Config.Comment("Wave text amplitude (how far characters bounce)")
@Config.DefaultFloat(2.0f)
@Config.RangeFloat(min = 1.0f, max = 8.0f)
@@ -306,6 +310,16 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowEars;
@Config.Comment("Narrow Fisk's Superheroes transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowFiskHeroes;
@Config.Comment("Narrow FoamFix transformer exclusions to allow GL redirection in its repackaged Ears")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowFoamFix;
}
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.event;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ChunkBiomeDataChangedEvent extends MutableEvent {
public static final EventBus<ChunkBiomeDataChangedEvent> BUS = EventBus.create(ChunkBiomeDataChangedEvent.class);
private static final ChunkBiomeDataChangedEvent INSTANCE = new ChunkBiomeDataChangedEvent();
public int chunkX;
public int chunkZ;
public static void post(int chunkX, int chunkZ) {
if (!BUS.hasListeners()) return;
INSTANCE.chunkX = chunkX;
INSTANCE.chunkZ = chunkZ;
BUS.post(INSTANCE);
}
}
@@ -0,0 +1,25 @@
package com.gtnewhorizons.angelica.event;
import net.minecraftforge.eventbus.api.bus.CancellableEventBus;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
/**
* Posted from {@link net.coderbot.iris.pipeline.HandRenderer}'s {@code canRender}
* <p>
* Cancel the event to prevent the hand from rendering.
*/
public record RenderHandEvent() implements RecordEvent, Cancellable {
public static final CancellableEventBus<RenderHandEvent> BUS = CancellableEventBus.create(RenderHandEvent.class);
private static final RenderHandEvent INSTANCE = new RenderHandEvent();
/**
* @return true if a listener cancelled the event, meaning the hand should NOT render.
*/
public static boolean post() {
if (!BUS.hasListeners()) return false;
return BUS.post(INSTANCE);
}
}
@@ -26,6 +26,7 @@ public class IrisGLSMBridge {
private static Runnable fogStartListener = null;
private static Runnable fogEndListener = null;
private static Runnable fogDensityListener = null;
private static Runnable colorModulatorListener = null;
static {
StateUpdateNotifiers.alphaFuncNotifier = listener -> alphaFuncListener = listener;
@@ -35,6 +36,7 @@ public class IrisGLSMBridge {
StateUpdateNotifiers.fogStartNotifier = listener -> fogStartListener = listener;
StateUpdateNotifiers.fogEndNotifier = listener -> fogEndListener = listener;
StateUpdateNotifiers.fogDensityNotifier = listener -> fogDensityListener = listener;
StateUpdateNotifiers.colorModulatorNotifier = listener -> colorModulatorListener = listener;
}
public static void register() {
@@ -108,6 +110,10 @@ public class IrisGLSMBridge {
}
});
GLSMHooks.SHADER_COLOR_CHANGE.addListener(event -> {
if (Iris.enabled && colorModulatorListener != null) colorModulatorListener.run();
});
GLSMHooks.FOG_STATE_CHANGE.addListener(event -> {
if (Iris.enabled) {
if (fogModeListener != null) fogModeListener.run();
@@ -157,17 +163,17 @@ public class IrisGLSMBridge {
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
if (!Iris.enabled) return;
if (event.postBind) return;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return;
if (!drp.shouldOverrideShaders()) return;
if (drp.getActivePassProgramId() == -1) return;
if (DepthColorStorage.isOwnedProgram(event.newProgram)) {
DepthColorStorage.unlockDepthColor();
}
});
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
if (!Iris.enabled) return;
if (!event.postBind) return;
@@ -53,8 +53,15 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
Launch.classLoader.addTransformerExclusion("com.gtnewhorizons.angelica.loading");
Launch.classLoader.addTransformerExclusion("com.gtnewhorizons.angelica.glsm.GLStateManager");
if (Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE) {
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (rfbLoaded) {
addLwjgl3ifyExclusions();
} else {
// Fucking java 8 and non RFB
try {
Class.forName("com.gtnewhorizon.gtnhlib.core.GTNHLibCore", true, Launch.classLoader);
} catch (ClassNotFoundException ignored) {
}
}
try {
@@ -84,7 +91,6 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
// packages to avoid interfering with mixin delegation on game classes. AngelicaLateTweaker removes this registration once
// mixins are setup.
if (FMLLaunchHandler.side().isClient()) {
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (!rfbLoaded) {
final String transformer = "com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer";
FMLRelaunchLog.finer("Registering transformer %s", transformer);
@@ -180,6 +186,8 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
narrowTransformerConfig("AdvancedLightsabers", AngelicaConfig.transformerCompat.narrowAdvancedLightsabers);
narrowTransformerConfig("Alfheim", AngelicaConfig.transformerCompat.narrowAlfheim);
narrowTransformerConfig("Ears", AngelicaConfig.transformerCompat.narrowEars);
narrowTransformerConfig("FiskHeroes", AngelicaConfig.transformerCompat.narrowFiskHeroes);
narrowTransformerConfig("FoamFix", AngelicaConfig.transformerCompat.narrowFoamFix);
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.IncompatibleModsDisablerTweaker");
if (AngelicaConfig.enableHudCaching) {
@@ -17,7 +17,6 @@ public class CeleritasBlockTransformer implements IClassTransformer {
public CeleritasBlockTransformer() {
this.inner = new CeleritasBlockTransform(AngelicaClientTweaker.isObfEnv());
this.exclusions = inner.getTransformerExclusions();
this.inner.setCeleritasSetting();
}
/**
@@ -52,12 +52,20 @@ public class RFBAngelicaRedirector implements RfbClassTransformer {
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest,
@NotNull String className, @NotNull ClassNodeHandle classNode) {
final boolean changed = inner.transformClassNode(className, classNode.getNode());
if (changed) {
classNode.computeMaxs();
AngelicaClassDump.dumpRFBClass(className, classNode, this);
}
return changed;
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
}
}
@@ -59,12 +59,20 @@ public class RFBCeleritasBlockTransformer implements RfbClassTransformer {
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest,
@NotNull String className, @NotNull ClassNodeHandle classNode) {
final boolean changed = inner.transformClassNode(className, classNode.getNode());
if (changed) {
classNode.computeMaxs();
AngelicaClassDump.dumpRFBClass(className, classNode, this);
}
return changed;
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
}
}
@@ -49,11 +49,19 @@ public class RFBIsbrhTessellatorAbuseTransformer implements RfbClassTransformer
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
if (inner.transformClassNode(classNode.getNode(), this.isObf)) {
final boolean changed = inner.transformClassNode(classNode.getNode(), this.isObf);
if (changed) {
classNode.computeMaxs();
AngelicaClassDump.dumpRFBClass(className, classNode, this);
}
return changed;
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
}
}
@@ -31,16 +31,21 @@ public final class CeleritasBlockTransform implements Opcodes {
private static final Logger LOGGER = LogManager.getLogger("CeleritasBlockTransformer");
private static final String BlockClass = "net/minecraft/block/Block";
private static final String ThreadedBlockData = "com/gtnewhorizons/angelica/client/rendering/ThreadedBlockData";
/** All classes under <tt>net.minecraft.block.Block*</tt> are Block subclasses save for these. */
private static final String[] VanillaBlockExclusions = {
"net/minecraft/block/IGrowable",
"net/minecraft/block/ITileEntityProvider",
"net/minecraft/block/BlockEventData",
"net/minecraft/block/BlockSourceImpl",
"net/minecraft/block/material/"
};
private final Map<String, String> fieldNameToRedirect = new HashMap<>();
private final Set<String> blockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<String> moddedBlockSubclasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Block subclass owners we shouldn't redirect because they shadow some fields we want to redirect
private final Set<String> blockSubclassExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final ClassConstantPoolParser cstPoolParser;
private final MethodHandle celeritasEnabledGetter;
private boolean isCeleritasEnabled;
public CeleritasBlockTransform(boolean isObf) {
final List<Pair<String, String>> mappings = ImmutableList.of(
Pair.of("minX", "field_149759_B"),
@@ -56,37 +61,30 @@ public final class CeleritasBlockTransform implements Opcodes {
}
this.cstPoolParser = new ClassConstantPoolParser(this.fieldNameToRedirect.keySet().toArray(new String[0]));
}
blockSubclasses.add(BlockClass);
try {
// Needed because the config is loaded in LaunchClassLoader, but we need to access it in the parent system loader.
final Class<?> angelicaConfig = Class.forName("com.gtnewhorizons.angelica.config.AngelicaConfig", true, Launch.classLoader);
celeritasEnabledGetter = MethodHandles.lookup().findStaticGetter(angelicaConfig, "enableCeleritas", boolean.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
private boolean isVanillaBlockSubclass(String className) {
if (!className.startsWith(BlockClass)) {
return false;
}
}
public void setCeleritasSetting() {
isCeleritasEnabled = true;
}
private boolean isCeleritasEnabled() {
if (isCeleritasEnabled) return true;
try {
return (boolean) celeritasEnabledGetter.invokeExact();
} catch (Throwable e) {
throw new RuntimeException(e);
for (String exclusion : VanillaBlockExclusions) {
if (className.startsWith(exclusion)) {
return false;
}
}
return true;
}
// This method needs to be called for every class, including the ones we don't want to transform
private boolean isBlockSubclass(String className) {
return isVanillaBlockSubclass(className) || moddedBlockSubclasses.contains(className);
}
// This method needs to be called for every class, including the ones we don't want to transform.
// Vanilla blocks are recognized by name; only modded subclasses need tracking, and FML's
// EventSubscriptionTransformer guarantees a modded class's superclass loads first.
public void trackBlockSubclasses(String className, String superClassName) {
// It works because the deepest subclasses are always transformed first due to
// ForgeEventTransformer recursive superclass loading
if (blockSubclasses.contains(superClassName)) {
blockSubclasses.add(className);
if (!isVanillaBlockSubclass(className) && isBlockSubclass(superClassName)) {
moddedBlockSubclasses.add(className);
if (blockSubclassExclusions.contains(superClassName)) {
LOGGER.info("Class {} extends a class with shadowed block bounds fields and will be skipped from redirecting", className);
@@ -110,9 +108,6 @@ public final class CeleritasBlockTransform implements Opcodes {
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) {
if (!isCeleritasEnabled()) {
return false;
}
boolean changed = false;
if (BlockClass.equals(cn.name)) {
changed = cn.fields.removeIf(field -> fieldNameToRedirect.containsKey(field.name));
@@ -124,7 +119,8 @@ public final class CeleritasBlockTransform implements Opcodes {
}
private void trackBlockShadowingFields(ClassNode cn) {
if (blockSubclasses.contains(cn.name)) {
// Only modded subclasses can shadow; vanilla declares these fields solely in Block.
if (moddedBlockSubclasses.contains(cn.name)) {
for (FieldNode field : cn.fields) {
if (fieldNameToRedirect.containsKey(field.name)) {
LOGGER.info("Class '{}' shadows one or more block bounds fields, these accesses won't be redirected!", cn.name);
@@ -141,7 +137,7 @@ public final class CeleritasBlockTransform implements Opcodes {
for (AbstractInsnNode node = mn.instructions.getFirst(); node != null; node = node.getNext()) {
if ((node.getOpcode() == GETFIELD || node.getOpcode() == PUTFIELD) && node instanceof FieldInsnNode fNode) {
String newFieldName = fieldNameToRedirect.get(fNode.name);
if (newFieldName != null && blockSubclasses.contains(fNode.owner) && !blockSubclassExclusions.contains(fNode.owner)) {
if (newFieldName != null && isBlockSubclass(fNode.owner) && !blockSubclassExclusions.contains(fNode.owner)) {
if (LOG_SPAM) {
LOGGER.info("Redirecting Block.{} in {} to thread-safe wrapper", fNode.name, transformedName);
}
@@ -45,7 +45,9 @@ public enum Mixins implements IMixins {
, "angelica.bugfixes.MixinRenderCreeper_AuraDepth"
, "angelica.bugfixes.MixinRenderGlobal_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
, "angelica.bugfixes.MixinRenderMooshroom_MushroomTint"
, "angelica.bugfixes.MixinRendererLivingEntity_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
, "angelica.bugfixes.MixinRenderWither_ArmorCentering"
, "angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
, "angelica.debug.MixinMinecraft_FPSCap"
@@ -275,6 +277,8 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderNameTag"
, "shaders.MixinRenderPlayerArmor"
, "shaders.MixinTileEntityBeaconRenderer"
, "shaders.MixinWorldNbtCache"
, "shaders.MixinNetHandlerPlayClient_NbtRemesh"
, "shaders.MixinRenderEndPortal"
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
@@ -325,6 +329,15 @@ public enum Mixins implements IMixins {
)
),
IRIS_SHADERS_CHISEL_BEACON(new MixinBuilder("Iris Chisel Beacon Beam")
.setPhase(Phase.LATE)
.setApplyIf(() -> AngelicaConfig.enableIris)
.addRequiredMod(TargetedMod.CHISEL)
.addClientMixins(
"chisel.MixinRenderCarvableBeacon"
)
),
ANGELICA_TEXTURE(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris || AngelicaConfig.enableCeleritas)
@@ -10,6 +10,7 @@ public enum TargetedMod implements ITargetMod {
ARCHAICFIX("org.embeddedt.archaicfix.ArchaicCore", "archaicfix"),
BACKHAND("xonin.backhand.coremod.BackhandLoadingPlugin", "backhand"),
CAMPFIRE_BACKPORT(null, "campfirebackport"),
CHISEL(null, "chisel"),
COFHCORE( "cofh.asm.LoadingPlugin", "CoFHCore"),
DYNAMIC_SURROUNDINGS_MIST("org.blockartistry.mod.DynSurround.mixinplugin.DynamicSurroundingsEarlyMixins", "dsurround"),
DYNAMIC_SURROUNDINGS_ORIGINAL("org.blockartistry.mod.DynSurround.asm.TransformLoader", "dsurround"),
@@ -0,0 +1,75 @@
package com.gtnewhorizons.angelica.render;
import java.nio.ByteBuffer;
final class CloudEdges {
private static final int CELL_EMPTY_THRESHOLD = 10;
final int width, height;
private final int wMask, hMask;
private final boolean whPow2;
private final long[] opaqueBits;
final int opaqueCount;
CloudEdges(int width, int height, ByteBuffer rgba) {
this.width = width;
this.height = height;
this.wMask = width - 1;
this.hMask = height - 1;
this.whPow2 = (width & wMask) == 0 && (height & hMask) == 0;
final int total = width * height;
final long[] bits = new long[(total + 63) >>> 6];
int op = 0;
for (int i = 0; i < total; i++) {
if ((rgba.get(i * 4 + 3) & 0xFF) >= CELL_EMPTY_THRESHOLD) {
bits[i >>> 6] |= 1L << (i & 63);
op++;
}
}
this.opaqueBits = bits;
this.opaqueCount = op;
}
boolean cellOpaque(int x, int z) {
final int xw, zh;
if (whPow2) {
xw = x & wMask;
zh = z & hMask;
} else {
xw = Math.floorMod(x, width);
zh = Math.floorMod(z, height);
}
final int idx = xw + zh * width;
return ((opaqueBits[idx >>> 6] >>> (idx & 63)) & 1L) != 0L;
}
long readOpaqueRowBits(int x0, int z) {
final int zMod, x0Mod;
if (whPow2) {
zMod = z & hMask;
x0Mod = x0 & wMask;
} else {
zMod = Math.floorMod(z, height);
x0Mod = Math.floorMod(x0, width);
}
if (x0Mod + 10 <= width) {
return readContigBits(opaqueBits, (long) zMod * width + x0Mod, 10);
}
final int colsBeforeWrap = width - x0Mod;
final long firstPart = readContigBits(opaqueBits, (long) zMod * width + x0Mod, colsBeforeWrap);
final long secondPart = readContigBits(opaqueBits, (long) zMod * width, 10 - colsBeforeWrap);
return firstPart | (secondPart << colsBeforeWrap);
}
private static long readContigBits(long[] bits, long startBitIdx, int count) {
final int wordIdx = (int) (startBitIdx >>> 6);
final int bitOff = (int) (startBitIdx & 63);
final long mask = count == 64 ? -1L : (1L << count) - 1L;
final long w0 = bits[wordIdx];
if (bitOff + count <= 64) {
return (w0 >>> bitOff) & mask;
}
final long w1 = bits[wordIdx + 1];
return ((w0 >>> bitOff) | (w1 << (64 - bitOff))) & mask;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.render;
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
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;
final class CloudUniforms {
static final float ALPHA = 0.8f;
final GlUniformMatrix4f mvp;
final GlUniformFloat4v colorMult;
final GlUniformFloat4v fogParams;
final GlUniformFloat4v fogColor;
final GlUniformInt fogEnabled;
final GlUniformInt tex;
private int lastFogEnabled = -1;
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN;
private final float[] uploadBuf = new float[4];
CloudUniforms(ShaderBindingContext ctx) {
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new);
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
fogEnabled = ctx.bindUniform("u_FogEnabled", GlUniformInt::new);
tex = ctx.bindUniform("u_Tex", 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;
uploadBuf[0] = r;
uploadBuf[1] = g;
uploadBuf[2] = b;
uploadBuf[3] = 1.0f;
fogColor.set(uploadBuf);
}
}
void setFogParams(float x, float y) {
if (x != lastFogParamX || y != lastFogParamY) {
lastFogParamX = x;
lastFogParamY = y;
uploadBuf[0] = x;
uploadBuf[1] = y;
uploadBuf[2] = 0f;
uploadBuf[3] = 0f;
fogParams.set(uploadBuf);
}
}
void setColorMult(float r, float g, float b) {
if (r != lastColorR || g != lastColorG || b != lastColorB) {
lastColorR = r;
lastColorG = g;
lastColorB = b;
uploadBuf[0] = r;
uploadBuf[1] = g;
uploadBuf[2] = b;
uploadBuf[3] = ALPHA;
colorMult.set(uploadBuf);
}
}
}
@@ -5,6 +5,10 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.resources.FallbackResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.client.resources.SimpleReloadableResourceManager;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
@@ -50,8 +54,23 @@ public class EmissiveTextureAutoloader {
loc.getResourceDomain(),
"textures/blocks/" + loc.getResourcePath() + ".png"
);
final IResourceManager resMan = Minecraft.getMinecraft().getResourceManager();
if (resMan instanceof SimpleReloadableResourceManager simple) {
FallbackResourceManager fallback = simple.domainResourceManagers.get(full.getResourceDomain());
if (fallback != null) {
for (IResourcePack rp : fallback.resourcePacks) {
if (rp != null && rp.resourceExists(full)) {
return true;
}
}
return false;
}
}
// Fallback
try {
Minecraft.getMinecraft().getResourceManager().getResource(full);
resMan.getResource(full);
return true;
} catch (Exception ignored) {
return false;
@@ -1,28 +1,45 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.client.rendering.GlUniformFloat2v;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.util.AxisAlignedBB;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
public final class SelectionBoxRenderer {
private static GlProgram<SelectionBoxUniforms> program;
private static int emptyVao;
private static IVertexArrayObject vao;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final float[] colorBuf = new float[4];
private static final int VERT_COUNT = 24;
private static final float[] UNIT_EDGES = {
0,0,0, 1,0,0, 1,0,0, 1,0,1,
1,0,1, 0,0,1, 0,0,1, 0,0,0,
0,1,0, 1,1,0, 1,1,0, 1,1,1,
1,1,1, 0,1,1, 0,1,1, 0,1,0,
0,0,0, 0,1,0, 1,0,0, 1,1,0,
1,0,1, 1,1,1, 0,0,1, 0,1,1
};
private SelectionBoxRenderer() {}
public static void init() {
@@ -40,8 +57,6 @@ public final class SelectionBoxRenderer {
program = builder.link(ctx -> new SelectionBoxUniforms(
ctx.bindUniform("u_MVP", GlUniformMatrix4f::new),
ctx.bindUniform("u_Min", GlUniformFloat3v::new),
ctx.bindUniform("u_Max", GlUniformFloat3v::new),
ctx.bindUniform("u_Color", GlUniformFloat4v::new),
needsGS ? ctx.bindUniform("u_ViewportSize", GlUniformFloat2v::new) : null,
needsGS ? ctx.bindUniform("u_LineWidth", GlUniformFloat::new) : null));
@@ -50,20 +65,34 @@ public final class SelectionBoxRenderer {
fs.destroy();
if (gs != null) gs.destroy();
emptyVao = GLStateManager.glGenVertexArrays();
final ByteBuffer buf = BufferUtils.createByteBuffer(UNIT_EDGES.length * Float.BYTES);
buf.asFloatBuffer().put(UNIT_EDGES);
vao = VertexBufferType.IMMUTABLE.allocate(DefaultVertexFormat.POSITION, GL11.GL_LINES, buf, VERT_COUNT);
}
public static void draw(AxisAlignedBB aabb, int color) {
if (program == null) return;
if (program == null || vao == null) return;
final float minX = (float) aabb.minX, minY = (float) aabb.minY, minZ = (float) aabb.minZ;
final float extX = (float) (aabb.maxX - aabb.minX);
final float extY = (float) (aabb.maxY - aabb.minY);
final float extZ = (float) (aabb.maxZ - aabb.minZ);
if (AngelicaConfig.enableIris && IrisApi.getInstance().isShaderPackInUse()) {
drawThroughShader(minX, minY, minZ, extX, extY, extZ, color);
} else {
drawFast(minX, minY, minZ, extX, extY, extZ, color);
}
}
private static void drawFast(float minX, float minY, float minZ, float extX, float extY, float extZ, int color) {
final SelectionBoxUniforms uniforms = program.getInterface();
program.bind();
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), mvpMatrix);
mvpMatrix.translate(minX, minY, minZ).scale(extX, extY, extZ);
uniforms.mvp.set(mvpMatrix);
uniforms.min.set((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ);
uniforms.max.set((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
if (color != -1) {
colorBuf[0] = ((color >> 16) & 0xFF) / 255.0f;
@@ -84,25 +113,42 @@ public final class SelectionBoxRenderer {
uniforms.lineWidth.setFloat(GLStateManager.getLineState().getWidth());
}
GLStateManager.glBindVertexArray(emptyVao);
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 24);
GLStateManager.glBindVertexArray(0);
vao.render();
program.unbind();
}
private static void drawThroughShader(float minX, float minY, float minZ, float extX, float extY, float extZ, int color) {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(minX, minY, minZ);
GLStateManager.glScalef(extX, extY, extZ);
final Color4 prev = GLStateManager.getColor();
final boolean overrideColor = color != -1;
if (overrideColor) {
GLStateManager.glColor4f(((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, 1.0f);
}
vao.render();
if (overrideColor) {
GLStateManager.glColor4f(prev.getRed(), prev.getGreen(), prev.getBlue(), prev.getAlpha());
}
GLStateManager.glPopMatrix();
}
public static void destroy() {
if (program != null) {
program.destroy();
program = null;
}
if (emptyVao != 0) {
GLStateManager.glDeleteVertexArrays(emptyVao);
emptyVao = 0;
if (vao != null) {
vao.delete();
vao = null;
}
}
private record SelectionBoxUniforms(GlUniformMatrix4f mvp, GlUniformFloat3v min, GlUniformFloat3v max, GlUniformFloat4v color, GlUniformFloat2v viewportSize, GlUniformFloat lineWidth) {
private record SelectionBoxUniforms(GlUniformMatrix4f mvp, GlUniformFloat4v color, GlUniformFloat2v viewportSize, GlUniformFloat lineWidth) {
}
}
@@ -14,6 +14,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.light.VanillaDiffuseProvid
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import lombok.Getter;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
@@ -184,6 +185,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
animatedSprites.add(sprite);
}
final boolean isFluid = blockRenderContext.renderType == ExtendedDataHelper.FLUID_RENDER_TYPE;
// If block used vanilla AO, then we can safely compute our own lighting data and ignore what vanilla
// computed. We suppress vanilla emitting the brightness/color in MixinRenderBlocks.
// Otherwise, we need to use the block's lightmap values as they are as the ISBRH may have
@@ -213,6 +216,17 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
for (int vIdx = 0; vIdx < 4; vIdx++) {
vertices[vIdx].trueNormal = trueNormal;
}
if (useAoCalculation && isFluid) {
quadView.setup(trueNormal, blockX, blockY, blockZ);
final ModelQuadFacing lightFace = quadView.getLightFace();
final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline;
final ModelQuadFacing cullFace = quadView.getCullFace();
pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true);
for (int vIdx = 0; vIdx < 4; vIdx++) {
vertices[vIdx].light = quadLightData.lm[vIdx];
}
}
}
// Apply dynamic lights after AO calculation so they're not overwritten
@@ -12,8 +12,11 @@ import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockDoublePlant;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
@@ -118,6 +121,10 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final Map<Block, BlockRenderLayer> blockTypeIds = provider != null ? provider.getBlockTypeIds() : null;
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
final NbtConditionalIdMap<Block> teMap = BlockRenderingSettings.INSTANCE.getBlockNbtMap();
final net.minecraft.world.World mcWorld = Minecraft.getMinecraft().theWorld;
final long currentTick = mcWorld != null ? mcWorld.getTotalWorldTime() : 0L;
final boolean threaded = isThreaded();
final List<DeferredBlock> deferredBlocks = threaded ? new ArrayList<>() : null;
@@ -192,7 +199,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
continue;
}
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride);
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride, teMap, currentTick);
}
}
@@ -205,7 +212,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
// Process deferred blocks on main thread if any
if (deferredBlocks != null && !deferredBlocks.isEmpty()) {
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ);
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ, teMap, currentTick);
}
final Reference2ReferenceMap<TerrainRenderPass, BuiltSectionMeshParts> meshes =
@@ -236,7 +243,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride) {
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
final var blockMaterial = block.getMaterial();
final boolean isFluid = blockMaterial == net.minecraft.block.material.Material.water || blockMaterial == net.minecraft.block.material.Material.lava;
@@ -262,16 +269,39 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
if (contextEncoder != null) {
final byte lightValue = (byte) block.getLightValue();
if (isFluid) {
contextEncoder.prepareToRenderFluid(blockRenderContext, block, lightValue);
contextEncoder.prepareToRenderFluid(blockRenderContext, block, metadata, lightValue);
} else {
int effectiveMeta = metadata;
if (BlockRenderingSettings.INSTANCE.hasSnowyEntries()
&& BlockRenderingSettings.INSTANCE.getSnowyBlocks().contains(block)
&& RenderBlocksUtils.isSnowCovered(renderBlocks.blockAccess, x, y, z)) {
effectiveMeta |= BlockMaterialMapping.SNOWY_META_BIT;
} else if (block instanceof BlockDoublePlant doublePlant && BlockDoublePlant.func_149887_c(metadata)) {
effectiveMeta = 0x8 | (doublePlant.func_149885_e(renderBlocks.blockAccess, x, y, z) & 7);
}
contextEncoder.prepareToRenderBlock(blockRenderContext, block, effectiveMeta,
ExtendedDataHelper.BLOCK_RENDER_TYPE, lightValue);
// Check for TileEntity NBT-conditional shader block ID override
if (teMap != null && teMap.hasConditions(block)) {
final long packedPos = BlockRenderingSettings.packBlockPos(x, y, z);
int teBlockId = BlockRenderingSettings.getCachedTeNbtId(packedPos, currentTick);
if (teBlockId == BlockRenderingSettings.CACHE_MISS) {
final TileEntity te = getBlockAccess().getTileEntity(x, y, z);
if (te != null) {
final NBTTagCompound teNbt = new NBTTagCompound();
te.writeToNBT(teNbt);
teBlockId = teMap.resolve(block, teNbt);
} else {
teBlockId = -1;
}
BlockRenderingSettings.cacheTeNbtId(packedPos, teBlockId, currentTick);
}
if (teBlockId != -1) {
blockRenderContext.blockId = (short) teBlockId;
}
}
}
}
@@ -294,7 +324,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ) {
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
@@ -308,7 +338,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
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());
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride(), teMap, currentTick);
}
} finally {
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
@@ -21,6 +21,8 @@ public class AngelicaRenderPassConfiguration {
private record AngelicaPipelineState(int pass, boolean disableAlphaTest) implements TerrainRenderPass.PipelineState {
@Override
public void setup() {
GLStateManager.glDepthMask(true);
if (pass == 0) {
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
}
@@ -7,6 +7,8 @@ import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHold
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -40,6 +42,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
private final WorldClient world;
private final ClonedChunkSectionCache sectionCache;
private final ChunkTaskProvider taskProvider;
private final LongOpenHashSet biomeRebuildColumns = new LongOpenHashSet();
public AngelicaRenderSectionManager(RenderPassConfiguration<?> configuration, WorldClient world, int renderDistance, CommandList commandList, int minSection, int maxSection, int requestedThreads, ChunkTaskProvider taskProvider) {
super(configuration, () -> new AngelicaChunkBuildContext(configuration, world), AngelicaChunkRenderer::new, renderDistance, commandList, minSection, maxSection, requestedThreads, true /* hasShadowPass = true for Iris */);
@@ -144,9 +147,35 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
this.sectionCache.invalidate(section.getChunkX(), section.getChunkY(), section.getChunkZ());
}
public void onBiomesChanged(int chunkX, int chunkZ) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
this.biomeRebuildColumns.add(PositionUtil.packChunk(chunkX + dx, chunkZ + dz));
}
}
}
private void flushBiomeRebuilds() {
if (this.biomeRebuildColumns.isEmpty()) {
return;
}
final int sectionCountY = this.world.getHeight() >> 4;
for (final LongIterator it = this.biomeRebuildColumns.iterator(); it.hasNext(); ) {
final long column = it.nextLong();
final int cx = PositionUtil.unpackChunkX(column);
final int cz = PositionUtil.unpackChunkZ(column);
for (int cy = 0; cy < sectionCountY; cy++) {
this.sectionCache.invalidate(cx, cy, cz);
this.scheduleRebuild(cx, cy, cz, false);
}
}
this.biomeRebuildColumns.clear();
}
@Override
public void updateChunks(boolean updateImmediately) {
this.sectionCache.cleanup();
this.flushBiomeRebuilds();
super.updateChunks(updateImmediately);
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
import org.embeddedt.embeddium.impl.gl.device.GLRenderDevice;
@@ -12,8 +13,16 @@ public class CeleritasSetup {
public static void ensureInitialized() {
if (!initialized && AngelicaConfig.enableCeleritas) {
GLRenderDevice.VANILLA_STATE_RESETTER = () -> GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
ChunkBiomeDataChangedEvent.BUS.addListener(CeleritasSetup::onChunkBiomeDataChanged);
AngelicaClientTweaker.LOGGER.debug("Celeritas init");
initialized = true;
}
}
private static void onChunkBiomeDataChanged(ChunkBiomeDataChangedEvent event) {
final CeleritasWorldRenderer renderer = CeleritasWorldRenderer.getInstanceOrNull();
if (renderer != null && renderer.isActive()) {
renderer.getRenderSectionManager().onBiomesChanged(event.chunkX, event.chunkZ);
}
}
}
@@ -74,6 +74,10 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
return instance;
}
public static CeleritasWorldRenderer getInstanceOrNull() {
return instance;
}
public AngelicaRenderSectionManager getRenderSectionManager() {
return this.renderSectionManager;
}
@@ -118,7 +122,16 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
protected ChunkRenderMatrices createChunkRenderMatrices() {
final boolean shadowPass = renderSectionManager.isInShadowPass();
final Matrix4f projection = shadowPass ? ShadowRenderer.PROJECTION : RenderingState.INSTANCE.getProjectionMatrix();
final Matrix4f modelView = shadowPass ? ShadowRenderer.MODELVIEW : RenderingState.INSTANCE.getModelViewMatrix();
Matrix4f modelView = shadowPass ? ShadowRenderer.MODELVIEW : RenderingState.INSTANCE.getModelViewMatrix();
if (!shadowPass) {
// Minecraft's setupCameraTransform bakes translate(0, -eyeHeight, 0) into the modelview.
// Chunk rendering passes eye position to drawChunkLayer so vertices arrive at
// the shader as (vert_world - eye).
final Entity view = mc.renderViewEntity;
if (view != null) {
modelView = new Matrix4f(modelView).translate(0f, view.getEyeHeight(), 0f);
}
}
return new ChunkRenderMatrices(projection, modelView);
}
@@ -7,7 +7,7 @@ public interface ContextAwareChunkVertexEncoder extends ChunkVertexEncoder {
void prepareToRenderBlock(BlockRenderContext ctx, Block block, int metadata, short renderType, byte lightValue);
void prepareToRenderFluid(BlockRenderContext ctx, Block block, byte lightValue);
void prepareToRenderFluid(BlockRenderContext ctx, Block block, int metadata, byte lightValue);
void finishRenderingBlock();
}
@@ -7,11 +7,14 @@ import static com.gtnewhorizons.angelica.rendering.celeritas.iris.IrisExtendedCh
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Setter;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.vertices.ExtendedDataHelper;
import net.coderbot.iris.vertices.NormalHelper;
import net.coderbot.iris.vertices.NormI8;
import net.minecraft.block.Block;
import net.minecraft.block.BlockDynamicLiquid;
import net.minecraft.block.BlockStaticLiquid;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material;
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexEncoder;
import org.joml.Vector3f;
@@ -26,6 +29,11 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
private static final int NORMAL_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("iris_Normal").getPointer();
private static final int MC_ENTITY_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("mc_Entity").getPointer();
private static final int MID_BLOCK_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("at_midBlock").getPointer();
private static final int A_TEXCOORD_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("a_TexCoord").getPointer();
// One unit of UV-space shift toward the quad centroid. Stops bilinear /
// mipmap sampling from bleeding pixels from neighboring atlas cells.
private static final float TEX_CENTROID_BIAS = 1.0f / 32768.0f;
private final ChunkVertexEncoder baseEncoder = IrisExtendedChunkVertexType.BASE_TYPE.createEncoder();
private final CeleritasQuadView quad = new CeleritasQuadView();
@@ -48,20 +56,44 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
public void prepareToRenderBlock(BlockRenderContext ctx, Block block, int metadata, short renderType, byte lightValue) {
this.context = ctx;
Int2IntMap metaMap = blockMetaMatches != null ? blockMetaMatches.get(block) : null;
ctx.blockId = (short) (metaMap != null ? metaMap.get(metadata) : -1);
ctx.blockId = (short) (metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1);
ctx.renderType = renderType;
ctx.lightValue = lightValue;
}
@Override
public void prepareToRenderFluid(BlockRenderContext ctx, Block block, byte lightValue) {
public void prepareToRenderFluid(BlockRenderContext ctx, Block block, int metadata, byte lightValue) {
this.context = ctx;
Int2IntMap metaMap = blockMetaMatches != null ? blockMetaMatches.get(block) : null;
ctx.blockId = (short) (metaMap != null ? metaMap.get(0) : -1);
ctx.blockId = (short) resolveFluidBlockId(block, metadata);
ctx.renderType = ExtendedDataHelper.FLUID_RENDER_TYPE;
ctx.lightValue = lightValue;
}
/**
* Try and prevent material flickering
*/
private int resolveFluidBlockId(Block block, int metadata) {
if (blockMetaMatches == null) return -1;
int id = lookupFluidMeta(block, metadata);
if (id == -1) {
final Block counterpart = liquidCounterpart(block);
if (counterpart != null) id = lookupFluidMeta(counterpart, metadata);
}
return id;
}
private int lookupFluidMeta(Block block, int metadata) {
final Int2IntMap metaMap = blockMetaMatches.get(block);
return metaMap != null ? BlockMaterialMapping.resolveId(metaMap, metadata) : -1;
}
private static Block liquidCounterpart(Block block) {
if (block instanceof BlockStaticLiquid) return Block.getBlockById(Block.getIdFromBlock(block) - 1);
if (block instanceof BlockDynamicLiquid) return Block.getBlockById(Block.getIdFromBlock(block) + 1);
return null;
}
@Override
public void finishRenderingBlock() {
if (context != null) {
@@ -98,6 +130,14 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 2, midUV);
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 3, midUV);
for (int vIdx = 0; vIdx < 4; vIdx++) {
final long uvBase = ptr - (long) (3 - vIdx) * STRIDE + A_TEXCOORD_OFFSET;
final float vU = memGetFloat(uvBase);
final float vV = memGetFloat(uvBase + 4L);
memPutFloat(uvBase, vU + (vU < midU ? TEX_CENTROID_BIAS : -TEX_CENTROID_BIAS));
memPutFloat(uvBase + 4L, vV + (vV < midV ? TEX_CENTROID_BIAS : -TEX_CENTROID_BIAS));
}
quad.setup(ptr, STRIDE);
NormalHelper.computeFaceNormal(normal, quad);
final int packedNormal = NormI8.pack(normal);
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.rendering.celeritas.world.cloned;
import com.falsepattern.endlessids.mixin.helpers.ChunkBiomeHook;
import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ExtendedBlockStorageExt;
@@ -20,6 +19,7 @@ import net.minecraft.world.ChunkPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.WorldChunkManager;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
@@ -62,50 +62,51 @@ public class ClonedChunkSection {
this.pos = pos;
this.data = new ExtendedBlockStorageExt(chunk, section);
final int bArrLength;
if (ModStatus.isEIDBiomeLoaded) {
bArrLength = ((ChunkBiomeHook) chunk).getBiomeShortArray().length;
} else {
bArrLength = chunk.getBiomeArray().length;
}
this.biomeData = new BiomeGenBase[bArrLength];
this.biomeData = new BiomeGenBase[16 * 16];
copyBlockEntities(chunk, pos);
// Fill biome data
for (int z = pos.getMinZ(); z <= pos.getMaxZ(); z++) {
for (int x = pos.getMinX(); x <= pos.getMaxX(); x++) {
this.biomeData[(x & 15) | ((z & 15) << 4)] = world.getBiomeGenForCoords(x, z);
}
}
fillBiomeData(chunk);
this.sectionLightData = BlockLightProvider.getInstance().prepareSectionData(chunk, pos.y);
}
private void fillBiomeData(Chunk chunk) {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < 16; lz++) {
for (int lx = 0; lx < 16; lx++) {
this.biomeData[lx | (lz << 4)] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
}
}
}
@SuppressWarnings("unchecked")
private void copyBlockEntities(Chunk chunk, ChunkSectionPos pos) {
this.tileEntities.clear();
final ConcurrentTileEntityMap map = ((IChunkTileEntityMapHolder) chunk).angelica$getConcurrentTEMap();
map.withReadLock(() -> {
map.readLock();
try {
final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = map.getDelegate();
if (delegate.isEmpty()) return;
if (!delegate.isEmpty()) {
final int minY = pos.getMinY();
final int maxY = pos.getMaxY();
final int minY = pos.getMinY();
final int maxY = pos.getMaxY();
for (Object2ObjectMap.Entry<ChunkPosition, TileEntity> entry : Object2ObjectMaps.fastIterable(delegate)) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
for (Object2ObjectMap.Entry<ChunkPosition, TileEntity> entry : Object2ObjectMaps.fastIterable(delegate)) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
this.tileEntities.put(ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15), te);
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
this.tileEntities.put(ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15), te);
}
}
}
});
} finally {
map.readUnlock();
}
this.tileEntities.trim();
}
@@ -21,7 +21,13 @@ public class ClonedChunkSectionCache {
public synchronized void cleanup() {
this.time = getMonotonicTimeSource();
this.byPosition.values().removeIf(entry -> this.time > (entry.getLastUsedTimestamp() + MAX_CACHE_DURATION));
while (!this.byPosition.isEmpty()) {
final ClonedChunkSection oldest = this.byPosition.get(this.byPosition.firstLongKey());
if (this.time <= oldest.getLastUsedTimestamp() + MAX_CACHE_DURATION) {
break;
}
this.byPosition.removeFirst();
}
}
public synchronized ClonedChunkSection acquire(int x, int y, int z) {
@@ -32,18 +38,17 @@ public class ClonedChunkSectionCache {
while (this.byPosition.size() >= MAX_CACHE_SIZE) {
this.byPosition.removeFirst();
}
section = this.createSection(x, y, z);
section = this.createSection(key, x, y, z);
}
section.setLastUsedTimestamp(this.time);
return section;
}
private ClonedChunkSection createSection(int x, int y, int z) {
private ClonedChunkSection createSection(long key, int x, int y, int z) {
final ClonedChunkSection section = new ClonedChunkSection(this, this.world);
final ChunkSectionPos pos = ChunkSectionPos.from(x, y, z);
section.init(pos);
this.byPosition.putAndMoveToLast(pos.asLong(), section);
section.init(ChunkSectionPos.from(x, y, z));
this.byPosition.putAndMoveToLast(key, section);
return section;
}
@@ -42,6 +42,14 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
}
}
public void readLock() {
lock.readLock().lock();
}
public void readUnlock() {
lock.readLock().unlock();
}
public void withWriteLock(Runnable action) {
lock.writeLock().lock();
try {
@@ -20,11 +20,7 @@ final class BlockOrientation extends RenderBlockState {
// 1 2 3
// c: coordinate (x,y,z) 0-2
private static final int[][][] NEIGHBOR_OFFSET = new int[][][] {
(
fixedBottomFaceUV
? makeNeighborOffset(EAST_FACE, SOUTH_FACE, WEST_FACE, NORTH_FACE) // BOTTOM_FACE fixedBottomFaceUV
: makeNeighborOffset(WEST_FACE, SOUTH_FACE, EAST_FACE, NORTH_FACE) // BOTTOM_FACE not fixed
),
makeNeighborOffset(EAST_FACE, SOUTH_FACE, WEST_FACE, NORTH_FACE), // BOTTOM_FACE
makeNeighborOffset(WEST_FACE, SOUTH_FACE, EAST_FACE, NORTH_FACE), // TOP_FACE
makeNeighborOffset(EAST_FACE, BOTTOM_FACE, WEST_FACE, TOP_FACE), // NORTH_FACE
makeNeighborOffset(WEST_FACE, BOTTOM_FACE, EAST_FACE, TOP_FACE), // SOUTH_FACE
@@ -48,6 +44,7 @@ final class BlockOrientation extends RenderBlockState {
private int textureFace;
private int textureFaceOrig;
private int rotateUV;
private boolean flipBottom;
@Override
public void clear() {
@@ -60,6 +57,7 @@ final class BlockOrientation extends RenderBlockState {
offsetsComputed = false;
haveOffsets = false;
dx = dy = dz = 0;
flipBottom = !fixedBottomFaceUV;
}
@Override
@@ -104,7 +102,7 @@ final class BlockOrientation extends RenderBlockState {
@Override
public int[] getOffset(int blockFace, int relativeDirection) {
return NEIGHBOR_OFFSET[blockFace][rotateUV(relativeDirection)];
return NEIGHBOR_OFFSET[flipBottom && blockFace == 0 ? 1 : blockFace][rotateUV(relativeDirection)];
}
@Override
@@ -147,6 +145,11 @@ final class BlockOrientation extends RenderBlockState {
return haveOffsets;
}
public void setFlipBottom(){
flipBottom = true;
}
@Override
public boolean shouldConnectByBlock(Block neighbor, int neighborX, int neighborY, int neighborZ) {
return block == neighbor
@@ -172,6 +175,7 @@ final class BlockOrientation extends RenderBlockState {
copy.textureFace = textureFace;
copy.textureFaceOrig = textureFaceOrig;
copy.rotateUV = rotateUV;
copy.flipBottom = flipBottom;
copy.blockAccess = blockAccess;
copy.block = block;
@@ -204,6 +208,7 @@ final class BlockOrientation extends RenderBlockState {
rotateUV = 0;
textureFace = blockFaceToTextureFace(blockFace);
metadataBits = (1 << metadata) | (1 << altMetadata);
flipBottom = !fixedBottomFaceUV;
}
void setBlockMetadata(Block block, int metadata, int face) {
@@ -53,6 +53,9 @@ public final class CompactCtmQuadProcessor {
}
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon) {
if(renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.setFlipBottom();
}
int neighborBits = override.getNeighborBits(renderBlockState, origIcon);
int ctmIndex = TileOverride.neighborMap[neighborBits & 0xFF];
@@ -289,9 +289,6 @@ public abstract class TileOverride implements Comparable<TileOverride> {
if (token.isEmpty()) {
// nothing
} else if (token.contains("/")) {
if (!token.endsWith(".png")) {
token += ".png";
}
ResourceLocation resource = TexturePackAPI.parseResourceLocation(properties.getResource(), token);
if (resource != null) {
list.add(resource.toString());
@@ -540,56 +540,63 @@ public class SodiumGameOptionPages {
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("RGB Colors")
.setTooltip("Enable hex colors via &#RRGGBB syntax")
.setName(I18n.format("options.angelica.texteffects.rgb_colors"))
.setTooltip(I18n.format("options.angelica.texteffects.rgb_colors.tooltip", "\\&#RRGGBB"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableRGBColors = value,
opts -> AngelicaConfig.enableRGBColors)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Gradients")
.setTooltip("Smooth color transitions via &g&#start&#end")
.setName(I18n.format("options.angelica.texteffects.gradients"))
.setTooltip(I18n.format("options.angelica.texteffects.gradients.tooltip") + " \\&g\\&#start\\&#end")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableGradients = value,
opts -> AngelicaConfig.enableGradients)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Rainbow")
.setTooltip("Cycling rainbow colors via &q")
.setName(I18n.format("options.angelica.texteffects.rainbow"))
.setTooltip(I18n.format("options.angelica.texteffects.rainbow.tooltip") + " \\&q")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableRainbow = value,
opts -> AngelicaConfig.enableRainbow)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Wave Text")
.setTooltip("Animated bouncing text via &z")
.setName(I18n.format("options.angelica.texteffects.wave_text"))
.setTooltip(I18n.format("options.angelica.texteffects.wave_text.tooltip") + " \\&z")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableWaveText = value,
opts -> AngelicaConfig.enableWaveText)
.build())
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
.setName("Wave Amplitude")
.setTooltip("How far wave text bounces (default 2.0)")
.setName(I18n.format("options.angelica.texteffects.wave_amplitude"))
.setTooltip(I18n.format("options.angelica.texteffects.wave_amplitude.tooltip"))
.setControl(option -> new SliderControl(option, 10, 80, 5, v -> (v / 10) + "." + (v % 10)))
.setBinding((opts, value) -> AngelicaConfig.waveAmplitude = value / 10f, opts -> Math.round(AngelicaConfig.waveAmplitude * 10f))
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Dinnerbone Text")
.setTooltip("Flipped upside down text via &v")
.setName(I18n.format("options.angelica.texteffects.dinnerbone_text"))
.setTooltip(I18n.format("options.angelica.texteffects.dinnerbone_text.tooltip") + " \\&v")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableDinnerboneText = value,
opts -> AngelicaConfig.enableDinnerboneText)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName("Ampersand Conversion")
.setTooltip("Convert & prefix codes to formatting codes at render time")
.setName(I18n.format("options.angelica.texteffects.drop_shadow"))
.setTooltip(I18n.format("options.angelica.texteffects.drop_shadow.tooltip", "\\&u") + " \\&u\\&#RRGGBB")
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableDropShadow = value,
opts -> AngelicaConfig.enableDropShadow)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName(I18n.format("options.angelica.texteffects.ampersand_conversion"))
.setTooltip(I18n.format("options.angelica.texteffects.ampersand_conversion.tooltip", "\\&"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> AngelicaConfig.enableAmpersandConversion = value,
opts -> AngelicaConfig.enableAmpersandConversion)
.build())
.build());
return new OptionPage("Text Effects", ImmutableList.copyOf(groups));
return new OptionPage(I18n.format("sodium.options.pages.text"), ImmutableList.copyOf(groups));
}
}
+30 -4
View File
@@ -13,6 +13,7 @@ import cpw.mods.fml.common.gameevent.InputEvent;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Getter;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.celeritas.IrisCeleritasShaderProvider;
import net.coderbot.iris.compat.dh.DHCompat;
@@ -72,7 +73,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.zip.ZipError;
import java.util.zip.ZipException;
public class Iris {
@@ -458,6 +458,33 @@ public class Iris {
}
}
/**
* Toggles shader debug mode. When enabled, patched shader sources are dumped to disk (see
* {@link net.coderbot.iris.pipeline.PatchedShaderPrinter}) and logging is more verbose.
*/
public static void setDebug(boolean enable) {
try {
irisConfig.setDebugEnabled(enable);
irisConfig.save();
reload();
} catch (IOException e) {
logger.error("Failed to " + (enable ? "enable" : "disable") + " debug mode", e);
}
logger.info("Debug functionality is " + (enable ? "enabled, logging will be more verbose!" : "disabled."));
}
/**
* Toggles whether unrecognized shader packs may load, exposing the {@code ALLOWS_UNKNOWN_SHADERS} macro.
*/
public static void setAllowUnknownShaders(boolean allow) {
try {
irisConfig.setUnknown(allow);
reload();
} catch (IOException e) {
logger.error("Failed to " + (allow ? "allow" : "disallow") + " unknown shaders", e);
}
}
public static void loadShaderpack() {
if (irisConfig == null) {
if (!initialized) {
@@ -678,8 +705,7 @@ public class Iris {
try (Stream<Path> stream = Files.walk(root)) {
return stream.filter(Files::isDirectory).anyMatch(path -> path.endsWith("shaders"));
}
} catch (ZipError zipError) {
// Java 8 seems to throw a ZipError instead of a subclass of IOException
} catch (ZipException e) {
Iris.logger.warn("The ZIP at " + pack + " is corrupt");
} catch (IOException ignored) {
// ignored, not a valid shader pack.
@@ -923,7 +949,7 @@ public class Iris {
return;
final Int2IntMap metaMap = blockMetaMatches.get(block);
final int blockId = metaMap != null ? metaMap.get(meta) : -1;
final int blockId = metaMap != null ? BlockMaterialMapping.resolveId(metaMap, meta) : -1;
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) blockId);
@@ -1,5 +1,6 @@
package net.coderbot.iris.block_rendering;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -14,6 +15,7 @@ import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
import net.coderbot.iris.shaderpack.materialmap.FlatteningMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.minecraft.block.Block;
import net.minecraft.block.BlockDoublePlant;
import net.minecraft.init.Blocks;
import net.minecraft.util.ResourceLocation;
@@ -23,26 +25,37 @@ import java.util.Set;
public class BlockMaterialMapping {
/** Meta-key bit OR'd in at runtime when a snowy-tagged block has snow above it. */
public static final int SNOWY_META_BIT = 0x10;
public record BlockIdMaps(Reference2ObjectMap<Block, Int2IntMap> blockMetaMap, NbtConditionalIdMap<Block> tileEntityMap) {}
// Meta-key bits OR'd in at runtime
public static final int SNOWY_META_BIT = 1 << 16;
public static final int DOUBLE_PLANT_TOP_BIT = 0x8;
public static final int WILDCARD_META_KEY = 1 << 30;
/**
* Creates a two-level map structure for block material IDs.
* Based on Iris's BlockState mapping approach adapted for 1.7.10's metadata system.
* Creates the standard block meta ID map, the TileEntity NBT-conditional map, and registers
* snowy blocks on {@link BlockRenderingSettings}.
*/
public static Reference2ObjectMap<Block, Int2IntMap> createBlockMetaIdMap(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
public static BlockIdMaps createBlockIdMaps(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
final Reference2ObjectMap<Block, Int2IntMap> blockMatches = new Reference2ObjectOpenHashMap<>();
final NbtConditionalIdMap<Block> tileEntityMap = new NbtConditionalIdMap<>();
final ReferenceSet<Block> snowyBlocks = new ReferenceOpenHashSet<>();
blockPropertiesMap.forEach((intId, entries) -> {
for (BlockEntry entry : entries) {
addBlockMetas(entry, blockMatches, intId, skipFlattening, snowyBlocks);
if (entry.hasNbtProperties()) {
addTileEntityEntry(entry, tileEntityMap, intId);
} else {
addBlockMetas(entry, blockMatches, tileEntityMap, intId, skipFlattening, snowyBlocks);
}
}
});
BlockRenderingSettings.INSTANCE.setHasSnowyEntries(!snowyBlocks.isEmpty());
BlockRenderingSettings.INSTANCE.setSnowyBlocks(snowyBlocks);
return blockMatches;
return new BlockIdMaps(blockMatches, tileEntityMap);
}
public static Map<Block, BlockRenderLayer> createBlockTypeMap(Map<NamespacedId, BlockRenderType> blockPropertiesMap) {
@@ -55,7 +68,8 @@ public class BlockMaterialMapping {
if (block == null && "minecraft".equals(id.getNamespace())) {
final List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
if (legacyEntries != null) {
block = resolveBlockOrNull(legacyEntries.getFirst().getId());
// Use the first entry for render type (render type applies per-block)
block = resolveBlockOrNull(legacyEntries.getFirst().id());
}
}
@@ -80,33 +94,74 @@ public class BlockMaterialMapping {
};
}
/**
* Creates an NBT-conditional ID map keyed by NamespacedId, for items or entities.
*/
public static NbtConditionalIdMap<NamespacedId> createNamespacedNbtMap(Int2ObjectMap<List<BlockEntry>> nbtEntries) {
NbtConditionalIdMap<NamespacedId> map = new NbtConditionalIdMap<>();
nbtEntries.forEach((intId, entries) -> {
for (BlockEntry entry : entries) {
if (entry.hasNbtProperties()) {
map.addCondition(entry.id(), entry.nbtProperties(), intId);
}
}
});
return map;
}
private static void addTileEntityEntry(BlockEntry entry, NbtConditionalIdMap<Block> teMap, int intId) {
final NamespacedId id = entry.id();
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
if (block == null || block == Blocks.air) {
return;
}
teMap.addCondition(block, entry.nbtProperties(), intId);
}
/**
* Adds block+metadata combinations to the material ID map.
* Based on Iris's addBlockStates method, adapted for 1.7.10 metadata system.
*/
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
final NamespacedId id = entry.getId();
final Map<String, String> stateProps = entry.getStateProperties();
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap,
NbtConditionalIdMap<Block> tileEntityMap, int intId,
boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
final NamespacedId id = entry.id();
final Map<String, String> stateProps = entry.stateProperties();
final String snowy = stateProps.get("snowy");
final int snowyBit = "true".equals(snowy) ? SNOWY_META_BIT : 0;
final boolean wantsDoublePlantTop = "upper".equals(stateProps.get("half"));
// Vanilla modern names go through FlatteningMap; legacy-section packs and modded blocks
// resolve directly from the registry.
List<BlockEntry> targets = null;
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.getMetas().isEmpty())) {
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.metas().isEmpty())) {
targets = FlatteningMap.toLegacy(id.getName(), stateProps);
}
if (targets == null) targets = List.of(entry);
for (BlockEntry target : targets) {
final Block block = resolveBlockOrNull(target.getId());
if (target.hasNbtProperties()) {
addTileEntityEntry(target, tileEntityMap, intId);
continue;
}
final Block block = resolveBlockOrNull(target.id());
if (block == null) continue;
applyMetas(block, target.getMetas(), idMap, intId, snowyBit);
int extraBits = snowyBit;
if (wantsDoublePlantTop && block instanceof BlockDoublePlant) {
extraBits |= DOUBLE_PLANT_TOP_BIT;
}
applyMetas(block, target.metas(), idMap, intId, extraBits);
if (snowy != null) snowyBlocks.add(block);
}
}
private static void applyMetas(Block block, Set<Integer> metas, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, int snowyBit) {
private static void applyMetas(Block block, Set<Integer> metas, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, int extraBits) {
Int2IntMap metaMap = idMap.get(block);
if (metaMap == null) {
metaMap = new Int2IntOpenHashMap();
@@ -115,12 +170,18 @@ public class BlockMaterialMapping {
}
if (metas.isEmpty()) {
for (int meta = 0; meta < 16; meta++) metaMap.putIfAbsent(meta | snowyBit, intId);
for (int meta = 0; meta < 16; meta++) metaMap.putIfAbsent(meta | extraBits, intId);
if (ModStatus.isMetadataExtended) metaMap.putIfAbsent(WILDCARD_META_KEY | extraBits, intId);
} else {
for (int meta : metas) metaMap.putIfAbsent(meta | snowyBit, intId);
for (int meta : metas) metaMap.putIfAbsent(meta | extraBits, intId);
}
}
public static int resolveId(Int2IntMap metaMap, int metaKey) {
final int id = metaMap.get(metaKey);
return id != -1 ? id : metaMap.get(WILDCARD_META_KEY | (metaKey & SNOWY_META_BIT));
}
/**
* Returns the registered Block for an id, or null if unknown.
* The registry returns Blocks.air (its default) for missing keys, which we coerce to null.
@@ -2,6 +2,7 @@ package net.coderbot.iris.block_rendering;
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
@@ -18,16 +19,76 @@ import java.util.Map;
public class BlockRenderingSettings {
public static final BlockRenderingSettings INSTANCE = new BlockRenderingSettings();
public static final int CACHE_MISS = Integer.MIN_VALUE;
private static final int NBT_CACHE_INTERVAL_TICKS = 20;
private static final int TE_NBT_CACHE_MAX = 8192;
private static final Long2LongLinkedOpenHashMap teNbtIdCache = new Long2LongLinkedOpenHashMap(256);
private static final Object teNbtIdCacheLock = new Object();
static {
teNbtIdCache.defaultReturnValue(-1L);
}
public static long packBlockPos(int x, int y, int z) {
return ((long) x & 0x3FFFFFFL) << 38 | ((long) y & 0xFFFL) << 26 | ((long) z & 0x3FFFFFFL);
}
public static void invalidateTeNbtCache(int x, int y, int z) {
final long key = packBlockPos(x, y, z);
synchronized (teNbtIdCacheLock) {
teNbtIdCache.remove(key);
}
}
public static int getCachedTeNbtId(long packedPos, long currentTick) {
final long entry;
synchronized (teNbtIdCacheLock) {
entry = teNbtIdCache.get(packedPos);
}
if (entry == -1L) {
return CACHE_MISS;
}
final long entryTick = entry >>> 32;
if (currentTick - entryTick >= NBT_CACHE_INTERVAL_TICKS) {
return CACHE_MISS;
}
return (int) entry;
}
public static void cacheTeNbtId(long packedPos, int shaderId, long currentTick) {
// Mask tick into low 31 bits so packed value never collides with the -1L sentinel.
final long packed = ((currentTick & 0x7FFFFFFFL) << 32) | (shaderId & 0xFFFFFFFFL);
synchronized (teNbtIdCacheLock) {
teNbtIdCache.put(packedPos, packed);
while (teNbtIdCache.size() > TE_NBT_CACHE_MAX) {
teNbtIdCache.removeFirstLong();
}
}
}
public static void clearTeNbtCache() {
synchronized (teNbtIdCacheLock) {
teNbtIdCache.clear();
}
}
@Getter
private boolean reloadRequired;
private Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
private NbtConditionalIdMap<Block> blockNbtMap;
private Map<Block, BlockRenderLayer> blockTypeIds;
// note: no reload needed, entities are rebuilt every frame.
@Setter
private Object2IntFunction<NamespacedId> entityIds;
@Setter
private NbtConditionalIdMap<NamespacedId> entityNbtMap;
// note: no reload needed, items are rendered every frame.
@Setter
private Object2IntFunction<NamespacedId> itemIds;
@Setter
private NbtConditionalIdMap<NamespacedId> itemNbtMap;
@Getter
private float ambientOcclusionLevel;
private boolean disableDirectionalShading;
@@ -41,6 +102,7 @@ public class BlockRenderingSettings {
public BlockRenderingSettings() {
reloadRequired = false;
blockMetaMatches = null;
blockNbtMap = null;
blockTypeIds = null;
ambientOcclusionLevel = 1.0F;
disableDirectionalShading = false;
@@ -82,11 +144,32 @@ public class BlockRenderingSettings {
return itemIds;
}
@Nullable
public NbtConditionalIdMap<Block> getBlockNbtMap() {
return blockNbtMap;
}
@Nullable
public NbtConditionalIdMap<NamespacedId> getItemNbtMap() {
return itemNbtMap;
}
@Nullable
public NbtConditionalIdMap<NamespacedId> getEntityNbtMap() {
return entityNbtMap;
}
public void setBlockMetaMatches(Reference2ObjectMap<Block, Int2IntMap> blockMetaIds) {
this.reloadRequired = true;
this.blockMetaMatches = blockMetaIds;
}
public void setBlockNbtMap(NbtConditionalIdMap<Block> blockNbtMap) {
this.reloadRequired = true;
this.blockNbtMap = blockNbtMap;
clearTeNbtCache();
}
public boolean hasSnowyEntries() {
return hasSnowyEntries;
}
@@ -0,0 +1,233 @@
package net.coderbot.iris.block_rendering;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.materialmap.PropertiesTokenizer;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.ResourceLocation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Maps objects with specific NBT properties to shader IDs. Generic over the
* key type so it works for blocks, items, and entities.
*
* Example:
* block.37=flower_pot[Item=minecraft:red_flower,Data=0]
* item.10=diamond_sword[display.Name='Pointy Stick']
*/
public class NbtConditionalIdMap<K> {
/**
* A single property matcher within a condition.
*/
record PropertyMatcher(String key, String[] path, String expectedValue,
long expectedLong, boolean isExpectedLong,
double expectedDouble, boolean isExpectedDouble) {
boolean isPathMatch() { return path != null; }
boolean isExistenceOnly() { return expectedValue == null; }
}
record NbtCondition(PropertyMatcher[] matchers, int shaderId) {}
private final Map<K, List<NbtCondition>> conditionsByKey;
public NbtConditionalIdMap() {
this.conditionsByKey = new HashMap<>();
}
/**
* Registers an NBT condition. Unquoted values are resolved via item/block
* registries at registration time. Single-quoted values are kept as literals.
* Dot notation paths are pre-split here so we prevent allocations.
*/
public void addCondition(K key, Map<String, PropertiesTokenizer.NbtValue> nbtProperties, int shaderId) {
final PropertyMatcher[] matchers = new PropertyMatcher[nbtProperties.size()];
int i = 0;
for (Map.Entry<String, PropertiesTokenizer.NbtValue> entry : nbtProperties.entrySet()) {
final String nbtKey = entry.getKey();
final PropertiesTokenizer.NbtValue nbtValue = entry.getValue();
final String resolvedValue;
if (nbtValue == null) {
resolvedValue = null;
} else if (nbtValue.literal()) {
resolvedValue = nbtValue.value();
} else {
resolvedValue = resolveValue(nbtValue.value());
}
final String[] path = nbtKey.contains(".") ? nbtKey.split("\\.") : null;
long expectedLong = 0;
boolean isExpectedLong = false;
double expectedDouble = 0;
boolean isExpectedDouble = false;
if (resolvedValue != null) {
try {
expectedLong = Long.parseLong(resolvedValue);
isExpectedLong = true;
} catch (NumberFormatException ignored) {}
try {
expectedDouble = Double.parseDouble(resolvedValue);
isExpectedDouble = true;
} catch (NumberFormatException ignored) {}
}
matchers[i++] = new PropertyMatcher(nbtKey, path, resolvedValue,
expectedLong, isExpectedLong, expectedDouble, isExpectedDouble);
}
conditionsByKey.computeIfAbsent(key, k -> new ArrayList<>())
.add(new NbtCondition(matchers, shaderId));
}
public boolean hasConditions(K key) {
return conditionsByKey.containsKey(key);
}
public boolean isEmpty() {
return conditionsByKey.isEmpty();
}
/**
* Resolves the shader ID by matching the provided NBT data.
*/
public int resolve(K key, NBTTagCompound nbt) {
final List<NbtCondition> conditions = conditionsByKey.get(key);
if (conditions == null) {
return -1;
}
for (NbtCondition condition : conditions) {
if (matchesNbt(nbt, condition.matchers())) {
return condition.shaderId;
}
}
return -1;
}
private static boolean matchesNbt(NBTTagCompound nbt, PropertyMatcher[] matchers) {
for (PropertyMatcher matcher : matchers) {
if (matcher.isPathMatch()) {
if (!matchesPath(nbt, matcher.path(), 0, matcher)) {
return false;
}
} else {
if (!nbt.hasKey(matcher.key())) {
return false;
}
if (!matcher.isExistenceOnly()) {
if (!matchesNbtValue(nbt.getTag(matcher.key()), matcher)) {
return false;
}
}
}
}
return true;
}
/**
* Search through dot-separated path in NBT data.
*/
private static boolean matchesPath(NBTBase tag, String[] path, int depth, PropertyMatcher matcher) {
if (depth == path.length) {
if (matcher.isExistenceOnly()) {
return true;
}
return matchesNbtValue(tag, matcher);
}
final String segment = path[depth];
if (tag instanceof NBTTagCompound compound) {
if (!compound.hasKey(segment)) {
return false;
}
return matchesPath(compound.getTag(segment), path, depth + 1, matcher);
}
if (tag instanceof NBTTagList list) {
for (int i = 0; i < list.tagCount(); i++) {
if (matchesPath(list.getCompoundTagAt(i), path, depth, matcher)) {
return true;
}
}
return false;
}
return false;
}
/**
* Compares an NBT tag's value against an expected string using type-specific
* accessors to avoid toString() allocations where possible.
* Makes it so you don't need to specify the type either in the .properties files.
*/
private static boolean matchesNbtValue(NBTBase tag, PropertyMatcher matcher) {
return switch (tag.getId()) {
case 1 -> // TAG_Byte
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagByte) tag).func_150290_f() == matcher.expectedLong();
case 2 -> // TAG_Short
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagShort) tag).func_150289_e() == matcher.expectedLong();
case 3 -> // TAG_Int
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagInt) tag).func_150287_d() == matcher.expectedLong();
case 4 -> // TAG_Long
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagLong) tag).func_150291_c() == matcher.expectedLong();
case 5 -> // TAG_Float
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagFloat) tag).func_150288_h() == (float) matcher.expectedDouble();
case 6 -> // TAG_Double
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagDouble) tag).func_150286_g() == matcher.expectedDouble();
case 8 -> // TAG_String
matcher.expectedValue().equals(((net.minecraft.nbt.NBTTagString) tag).func_150285_a_());
default -> // Compounds, lists, arrays — fall back to toString
matcher.expectedValue().equals(PropertiesTokenizer.stripQuotes(tag.toString()));
};
}
/**
* Tries to resolve a value as a registry name (item or block) to its numeric ID.
* If the value is already numeric or can't be resolved, return it unchanged.
*/
private static String resolveValue(String value) {
try {
Integer.parseInt(value);
return value;
} catch (NumberFormatException ignored) {}
if (!value.contains(":")) {
Iris.logger.warn("NBT value '{}' is missing a namespace, please use the full form e.g. 'minecraft:{}'", value, value);
return value;
}
final ResourceLocation loc = new ResourceLocation(value);
final Item item = (Item) Item.itemRegistry.getObject(loc.toString());
if (item != null) {
return String.valueOf(Item.getIdFromItem(item));
}
final Block block = (Block) Block.blockRegistry.getObject(loc.toString());
if (block != null) {
return String.valueOf(Block.getIdFromBlock(block));
}
Iris.logger.warn("Could not resolve '{}' to an item or block ID", value);
return value;
}
}
@@ -38,6 +38,8 @@ public class IrisConfig {
*/
private boolean enableDebugOptions;
private boolean allowUnknownShaders;
private final Path propertiesPath;
public IrisConfig(Path propertiesPath) {
@@ -45,6 +47,7 @@ public class IrisConfig {
enableShaders = true;
disableUpdateMessage = false;
enableDebugOptions = false;
allowUnknownShaders = false;
this.propertiesPath = propertiesPath;
}
@@ -113,6 +116,25 @@ public class IrisConfig {
return enableDebugOptions;
}
/**
* Sets whether debug features are enabled.
*/
public void setDebugEnabled(boolean enabled) {
this.enableDebugOptions = enabled;
}
public boolean shouldAllowUnknownShaders() {
return allowUnknownShaders;
}
/**
* Sets whether unrecognized shader packs may load.
*/
public void setUnknown(boolean allow) throws IOException {
this.allowUnknownShaders = allow;
save();
}
/**
* loads the config file and then populates the string, int, and boolean entries with the parsed entries
*
@@ -132,6 +154,7 @@ public class IrisConfig {
shaderPackName = properties.getProperty("shaderPack");
enableShaders = !"false".equals(properties.getProperty("enableShaders"));
enableDebugOptions = "true".equals(properties.getProperty("enableDebugOptions"));
allowUnknownShaders = "true".equals(properties.getProperty("allowUnknownShaders"));
disableUpdateMessage = "true".equals(properties.getProperty("disableUpdateMessage"));
// TODO: GUI
try {
@@ -159,6 +182,7 @@ public class IrisConfig {
properties.setProperty("shaderPack", getShaderPackName().orElse(""));
properties.setProperty("enableShaders", enableShaders ? "true" : "false");
properties.setProperty("enableDebugOptions", enableDebugOptions ? "true" : "false");
properties.setProperty("allowUnknownShaders", allowUnknownShaders ? "true" : "false");
properties.setProperty("disableUpdateMessage", disableUpdateMessage ? "true" : "false");
properties.setProperty("maxShadowRenderDistance", String.valueOf(IrisVideoSettings.shadowDistance));
// NB: This uses ISO-8859-1 with unicode escapes as the encoding
@@ -18,6 +18,7 @@ public enum RenderCondition {
HAND_TRANSLUCENT,
RAIN_SNOW,
WORLD_BORDER,
LIGHTNING,
SHADOW_TRANSLUCENT,
// NB: Must be last due to implementation details of DeferredWorldRenderingPipeline
SHADOW
@@ -3,5 +3,6 @@ package net.coderbot.iris.gbuffer_overrides.matching;
public enum SpecialCondition {
ENTITY_EYES,
BEACON_BEAM,
GLINT
GLINT,
LIGHTNING
}
@@ -2,16 +2,16 @@ package net.coderbot.iris.gl.buffer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import lombok.Getter;
import org.embeddedt.embeddium.impl.gl.debug.GLDebug;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43;
public class ShaderStorageBuffer {
protected final int index;
protected final ShaderStorageInfo info;
protected int id;
@Getter protected int id;
public ShaderStorageBuffer(int index, ShaderStorageInfo info) {
this.id = RenderSystem.createBuffers();
@@ -45,16 +45,13 @@ public class ShaderStorageBuffer {
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, newId);
// Calculation time
int newWidth = (int) (width * info.scaleX());
int newHeight = (int) (height * info.scaleY());
int finalSize = (newHeight * newWidth) * info.size();
long newWidth = (long) (width * info.scaleX());
long newHeight = (long) (height * info.scaleY());
long finalSize = (newHeight * newWidth) * info.size();
RenderSystem.bufferStorage(GL43.GL_SHADER_STORAGE_BUFFER, finalSize, 0);
RenderSystem.clearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 0, finalSize, GL11.GL_RED, GL11.GL_BYTE, new int[]{0});
RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, index, newId);
id = newId;
}
public int getId() {
return id;
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl.buffer;
public record ShaderStorageInfo(int size, boolean relative, float scaleX, float scaleY) {
public record ShaderStorageInfo(long size, boolean relative, float scaleX, float scaleY) {
}
@@ -4,15 +4,18 @@ import lombok.Getter;
import net.coderbot.iris.gl.GlResource;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
public final class Program extends GlResource {
@Getter
private final ProgramUniforms uniforms;
@Getter
private final ProgramSamplers samplers;
@Getter
private final ProgramImages images;
@@ -25,15 +28,16 @@ public final class Program extends GlResource {
DepthColorStorage.registerOwnedProgram(program);
}
public void use() {
RenderSystem.memoryBarrier(GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT | GL43.GL_SHADER_STORAGE_BARRIER_BIT);
GLStateManager.glUseProgram(getGlId());
uniforms.update();
samplers.update();
images.update();
}
public static void unbind() {
ProgramUniforms.clearActiveUniforms();
ProgramSamplers.clearActiveSamplers();
@@ -45,7 +49,7 @@ public final class Program extends GlResource {
DepthColorStorage.unregisterOwnedProgram(getGlId());
GLStateManager.glDeleteProgram(getGlId());
}
public int getProgramId() {
return getGlId();
}
@@ -4,10 +4,12 @@ import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.Tags;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import cpw.mods.fml.common.Loader;
import net.coderbot.iris.Iris;
import net.coderbot.iris.compat.dh.DHCompat;
import net.coderbot.iris.parsing.BiomeCategories;
import net.coderbot.iris.pipeline.HandRenderer;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.shaderpack.IrisLimits;
import net.coderbot.iris.shaderpack.StringPair;
import net.coderbot.iris.texture.format.TextureFormat;
import net.coderbot.iris.texture.format.TextureFormatLoader;
@@ -81,12 +83,19 @@ public class StandardMacros {
define(standardDefines, "IS_ANGELICA");
if (AngelicaConfig.defineIsIris) {
define(standardDefines, "IS_IRIS");
define(standardDefines, "IRIS_VERSION", "10803");
define(standardDefines, "IRIS_HAS_TRANSLUCENCY_SORTING");
define(standardDefines, "MAX_COLOR_BUFFERS", String.valueOf(IrisLimits.MAX_COLOR_BUFFERS));
}
if (DHCompat.hasRenderingEnabled()) {
define(standardDefines, "DISTANT_HORIZONS");
}
if (Iris.getIrisConfig().shouldAllowUnknownShaders()) {
define(standardDefines, "ALLOWS_UNKNOWN_SHADERS");
}
define(standardDefines, "DH_BLOCK_UNKNOWN", String.valueOf(0));
define(standardDefines, "DH_BLOCK_LEAVES", String.valueOf(1));
define(standardDefines, "DH_BLOCK_STONE", String.valueOf(2));
@@ -11,6 +11,7 @@ public class StateUpdateNotifiers {
public static ValueUpdateNotifier alphaFuncNotifier;
public static ValueUpdateNotifier alphaTestNotifier;
public static ValueUpdateNotifier blendFuncNotifier;
public static ValueUpdateNotifier colorModulatorNotifier;
public static ValueUpdateNotifier bindTextureNotifier;
public static ValueUpdateNotifier normalTextureChangeNotifier;
public static ValueUpdateNotifier specularTextureChangeNotifier;
@@ -4,10 +4,13 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import cpw.mods.fml.client.config.GuiUtils;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
/**
* Class serving as abstraction and
@@ -111,6 +114,32 @@ public final class GuiUtil {
font.drawStringWithShadow(text, x + 4, y + 4, 0xFFFFFF);
}
public static void drawScrollingText(FontRenderer font, String text, int centerX, int minX, int maxX, int minY, int maxY, int color) {
final int textWidth = font.getStringWidth(text);
final int boxWidth = maxX - minX;
final int textY = ((minY + maxY) - font.FONT_HEIGHT) / 2 + 1;
if (textWidth > boxWidth) {
final int overflow = textWidth - boxWidth;
final double time = System.currentTimeMillis() / 1000.0;
final double period = Math.max(overflow * 0.5, 3.0);
final double t = (Math.sin((Math.PI / 2.0) * Math.cos((Math.PI * 2.0) * time / period)) / 2.0) + 0.5;
final int scroll = (int) (t * overflow);
final Minecraft mc = client();
final int scale = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight).getScaleFactor();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(minX * scale, mc.displayHeight - (maxY * scale), boxWidth * scale, (maxY - minY) * scale);
font.drawStringWithShadow(text, minX - scroll, textY, color);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
} else {
int textX = centerX - (textWidth / 2);
textX = Math.min(textX, maxX - textWidth);
textX = Math.max(textX, minX);
font.drawStringWithShadow(text, textX, textY, color);
}
}
/**
* Shorten a text to a specific length, adding an ellipsis (...)
* to the end if shortened.
@@ -140,13 +169,25 @@ public final class GuiUtil {
* @return the translated text if found, otherwise the default provided
*/
public static String translateOrDefault(String defaultText, String translationDesc, Object ... format) {
final String translated = I18n.format(translationDesc, format);
final String translated = format.length == 0
? translateLenient(translationDesc)
: I18n.format(translationDesc, format);
if(!translated.equals(translationDesc)) {
return translated;
}
return defaultText;
}
private static final String FORMAT_ERROR_PREFIX = "Format error: ";
/**
* Nasty dirty hack, but it works.
*/
public static String translateLenient(String key) {
final String translated = I18n.format(key);
return translated.startsWith(FORMAT_ERROR_PREFIX) ? translated.substring(FORMAT_ERROR_PREFIX.length()) : translated;
}
/**
* Plays the {@code UI_BUTTON_CLICK} sound event as a
* master sound effect.
@@ -155,7 +196,7 @@ public final class GuiUtil {
* or other action.
*/
public static void playButtonClickSound() {
// client().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1));
client().getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F));
}
/**
@@ -21,7 +21,9 @@ public class IrisElementRow {
private final int spacing;
private int x;
private int y;
private int width;
@Getter
private int width;
private int height;
public IrisElementRow(int spacing) {
@@ -19,7 +19,7 @@ public abstract class IrisGuiSlot extends GuiSlot {
super(mc, width, height, top, bottom, slotHeight);
// Set Center Vertically to false
this.field_148163_i = false;
this.headerPadding = 2;
}
@Override
@@ -37,11 +37,15 @@ public abstract class IrisGuiSlot extends GuiSlot {
}
@Override
protected void drawSelectionBox(int x, int y, int mouseX, int mouseY) {
final int oldPadding = this.headerPadding;
this.headerPadding = 2;
super.drawSelectionBox(x, y, mouseX, mouseY);
this.headerPadding = oldPadding;
public int func_148124_c/*getSlotIndexFromScreenCoords*/(int x, int y) {
final int left = this.left + this.width / 2 - this.getListWidth() / 2 + hitLeftInset() + hitXShift();
final int right = this.left + this.width / 2 + this.getListWidth() / 2 - hitRightInset() + hitXShift();
final int relativeY = y - this.top + (int) this.amountScrolled - hitYOffset();
final int index = relativeY / this.slotHeight;
if (y <= this.top || y >= this.bottom) {
return -1;
}
return x < this.getScrollBarX() && x >= left && x <= right && index >= 0 && relativeY >= 0 && index < this.getSize() ? index : -1;
}
@Override
@@ -53,6 +57,23 @@ public abstract class IrisGuiSlot extends GuiSlot {
return false;
}
protected int hitYOffset() {
return 4;
}
protected int hitRightInset() {
return 0;
}
protected int hitLeftInset() {
return 0;
}
protected int hitXShift() {
return 0;
}
public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) {
if (!this.func_148125_i/*enabled*/()) {
return false;
@@ -60,15 +81,15 @@ public abstract class IrisGuiSlot extends GuiSlot {
final int size = this.getSize();
final int scrollBarX = this.getScrollBarX();
final int rightEdge = scrollBarX + 6;
final int elementLeft = this.width / 2 - this.getListWidth() / 2;
final int elementRight = this.width / 2 + this.getListWidth() / 2;
final int relativeY = mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4;
final int elementLeft = this.width / 2 - this.getListWidth() / 2 + hitLeftInset() + hitXShift();
final int elementRight = this.width / 2 + this.getListWidth() / 2 - hitRightInset() + hitXShift();
final int relativeY = mouseY - this.top + (int) this.amountScrolled - hitYOffset();
boolean handled = false;
final boolean leftMouseDown = Mouse.isButtonDown(0);
final boolean rightMouseDown = Mouse.isButtonDown(1);
if (mouseX <= this.left || mouseX >= this.right || mouseY <= this.top || mouseY >= this.bottom) {
return handled;
return false;
}
if (leftMouseDown && mouseX >= scrollBarX && mouseX <= rightEdge) {
scrolling = true;
@@ -25,7 +25,7 @@ public class ShaderPackOptionList extends IrisGuiSlot {
private final List<BaseEntry> entries = new ArrayList<>();
public ShaderPackOptionList(ShaderPackScreen screen, NavigationController navigation, ShaderPack pack, Minecraft client, int width, int height, int top, int bottom, int left, int right) {
super(client, width, height, top, bottom, 20);
super(client, width, height, top, bottom, 24);
this.navigation = navigation;
this.screen = screen;
@@ -72,7 +72,7 @@ public class ShaderPackOptionList extends IrisGuiSlot {
}
}
if (row.size() > 0) {
if (!row.isEmpty()) {
while (row.size() < columns) {
row.add(AbstractElementWidget.EMPTY);
}
@@ -93,7 +93,12 @@ public class ShaderPackOptionList extends IrisGuiSlot {
}
@Override
public boolean mouseReleased( int mouseX, int mouseY, int button) {
final int relativeY = mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4;
this.scrolling = false;
if (mouseY <= this.top || mouseY >= this.bottom) {
return false;
}
final int relativeY = mouseY - this.top + (int) this.amountScrolled - hitYOffset();
final int index = relativeY / this.slotHeight;
if (index < 0 || index >= this.entries.size())
@@ -113,11 +118,31 @@ public class ShaderPackOptionList extends IrisGuiSlot {
// noop
}
@Override
protected int hitYOffset() {
return 3;
}
@Override
protected int hitRightInset() {
return 4;
}
@Override
protected int hitLeftInset() {
return -1;
}
@Override
protected int hitXShift() {
return 2;
}
@Override
protected void drawSlot(int index, int x, int y, int i1, Tessellator tessellator, int mouseX, int mouseY) {
final BaseEntry entry = this.entries.get(index);
final boolean isMouseOver = this.func_148124_c/*getSlotIndexFromScreenCoords*/(mouseX, mouseY) == index;
entry.drawEntry(screen, index, x - 2, y + 4, this.getListWidth(), this.slotHeight, tessellator, mouseX, mouseY, isMouseOver);
entry.drawEntry(screen, index, x, y - 2, this.getListWidth(), this.slotHeight, tessellator, mouseX, mouseY, isMouseOver);
}
@@ -4,13 +4,23 @@ import lombok.Getter;
import lombok.Setter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.element.shaderselection.BaseEntry;
import net.coderbot.iris.gui.element.shaderselection.DownloadEntry;
import net.coderbot.iris.gui.element.shaderselection.LabelEntry;
import net.coderbot.iris.gui.element.shaderselection.ShaderPackEntry;
import net.coderbot.iris.gui.element.shaderselection.TopButtonRowEntry;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -31,15 +41,70 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
private final List<BaseEntry> entries = new ArrayList<>();
private final WatchService watcher;
private final WatchKey key;
private boolean keyValid;
public ShaderPackSelectionList(ShaderPackScreen screen, Minecraft client, int width, int height, int top, int bottom, int left, int right) {
super(client, width, height, top, bottom, 20);
this.screen = screen;
this.topButtonRow = new TopButtonRowEntry(this, Iris.getIrisConfig().areShadersEnabled());
WatchService watcher1;
WatchKey key1;
try {
watcher1 = FileSystems.getDefault().newWatchService();
key1 = Iris.getShaderpacksDirectory().register(watcher1,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
keyValid = true;
} catch (IOException e) {
Iris.logger.error("Couldn't register shaderpacks directory file watcher!", e);
watcher1 = null;
key1 = null;
keyValid = false;
}
this.watcher = watcher1;
this.key = key1;
refresh();
}
// Poll for new shaderpacks
public void poll() {
if (!keyValid) {
return;
}
boolean changed = false;
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
continue;
}
changed = true;
}
if (changed) {
refresh();
}
keyValid = key.reset();
}
public void close() throws IOException {
if (key != null) {
key.cancel();
}
if (watcher != null) {
watcher.close();
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
poll();
super.drawScreen(mouseX, mouseY, partialTicks);
}
public void refresh() {
this.entries.clear();
@@ -68,8 +133,13 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
this.entries.add(topButtonRow);
// Download something man
if (names.isEmpty()) {
this.entries.add(new DownloadEntry(this, "Download Shaders", "https://modrinth.com/shaders"));
}
// Only allow the enable/disable shaders button if the user has added a shader pack. Otherwise, the button will be disabled.
topButtonRow.allowEnableShadersButton = names.size() > 0;
topButtonRow.allowEnableShadersButton = !names.isEmpty();
int index = 0;
@@ -78,6 +148,11 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
addPackEntry(index, name);
}
// Try not to gaslight users in case they swap back and forth from lwjgl2 and lwjgl3
final String footerKey = BackendManager.RENDER_BACKEND.supportsFileDrop()
? "pack.iris.list.label"
: "pack.iris.list.label.lwjgl2";
addLabelEntries(EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format(footerKey));
}
public void addPackEntry(int index, String name) {
@@ -128,6 +203,8 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
return true;
} else if( entry instanceof TopButtonRowEntry topButtonRowEntry) {
return topButtonRowEntry.mouseClicked(mouseX, mouseY, 0);
} else if (entry instanceof DownloadEntry downloadEntry) {
return downloadEntry.click(this.screen);
}
return false;
}
@@ -152,7 +229,7 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
protected void drawSlot(int index, int x, int y, int i1, Tessellator tessellator, int mouseX, int mouseY) {
final BaseEntry entry = this.entries.get(index);
final boolean isMouseOver = this.func_148124_c/*getSlotIndexFromScreenCoords*/(mouseX, mouseY) == index;
entry.drawEntry(screen, index, x - 2, y + 4, this.getListWidth(), tessellator, mouseX, mouseY, isMouseOver);
entry.drawEntry(screen, index, x, y + 4, this.getListWidth(), tessellator, mouseX, mouseY, isMouseOver);
}
}
@@ -14,6 +14,7 @@ public class ElementRowEntry extends BaseEntry {
private int cachedWidth;
private int cachedPosX;
private float cachedSingleWidth;
public ElementRowEntry(NavigationController navigation, List<AbstractElementWidget<?>> widgets) {
super(navigation);
@@ -32,25 +33,43 @@ public class ElementRowEntry extends BaseEntry {
// Width of a single widget
final float singleWidgetWidth = (float) totalWidthWithoutMargins / widgets.size();
this.cachedSingleWidth = singleWidgetWidth;
for (int i = 0; i < widgets.size(); i++) {
final AbstractElementWidget<?> widget = widgets.get(i);
final boolean widgetHovered = isMouseOver && (getHoveredWidget(mouseX) == i);
widget.drawScreen(x + (int) ((singleWidgetWidth + 2) * i), y, (int) singleWidgetWidth, slotHeight + 2, mouseX, mouseY, 0, widgetHovered);
widget.drawScreen(x + (int) ((singleWidgetWidth + 2) * i), y, (int) singleWidgetWidth, slotHeight - 2, mouseX, mouseY, 0, widgetHovered);
screen.setElementHoveredStatus(widget, widgetHovered);
}
}
public int getHoveredWidget(int mouseX) {
final float positionAcrossWidget = ((float) MathHelper.clamp_int(mouseX - cachedPosX, 0, cachedWidth)) / cachedWidth;
final float CONTENT_ANCHOR_OFFSET = 2f;
final float rel = (mouseX - cachedPosX) + CONTENT_ANCHOR_OFFSET;
final float clamped = Math.clamp(rel, 0f, cachedWidth);
final float positionAcrossWidget = clamped / cachedWidth;
return MathHelper.clamp_int((int) Math.floor(widgets.size() * positionAcrossWidget), 0, widgets.size() - 1);
}
private int widgetAt(int mouseX) {
final int i = getHoveredWidget(mouseX);
final int widgetX = this.cachedPosX + (int) ((this.cachedSingleWidth + 2) * i);
if (mouseX < widgetX || mouseX >= widgetX + (int) this.cachedSingleWidth) {
return -1;
}
return i;
}
@Override
public boolean mouseClicked(int mouseX, int mouseY, int button) {
return this.widgets.get(getHoveredWidget(mouseX)).mouseClicked(mouseX, mouseY, button);
final int i = widgetAt(mouseX);
if (i < 0) {
return false;
}
return this.widgets.get(i).mouseClicked(mouseX, mouseY, button);
}
@Override
@@ -7,6 +7,7 @@ import net.coderbot.iris.gui.element.IrisElementRow;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
@@ -60,13 +61,12 @@ public class HeaderEntry extends BaseEntry {
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int slotWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
// Draw dividing line
// fill(x - 3, (y + slotHeight) - 2, x + slotWidth, (y + slotHeight) - 1, 0x66BEBEBE);
Gui.drawRect(x - 3, (y + slotHeight) - 3, x + slotWidth, (y + slotHeight) - 2, 0x66BEBEBE);
final int tickDelta = 0;
final FontRenderer font = Minecraft.getMinecraft().fontRenderer;
// Draw header text
this.screen.drawCenteredString(font, text, x + (int) (slotWidth * 0.5), y + 5, 0xFFFFFF);
GuiUtil.drawScrollingText(font, text, x + (int) (slotWidth * 0.5), x + 5,
((x + slotWidth) - 10) - this.utilityButtons.getWidth(), y + 5, y + 15, 0xFFFFFF);
GuiUtil.bindIrisWidgetsTexture();
@@ -0,0 +1,33 @@
package net.coderbot.iris.gui.element.shaderselection;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.element.ShaderPackSelectionList;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.renderer.Tessellator;
/**
* Show a download button when no shaderpacks exist in the shaderpacks folder.
*/
public class DownloadEntry extends BaseEntry {
private final String label;
private final String url;
public DownloadEntry(ShaderPackSelectionList list, String label, String url) {
super(list);
this.label = label;
this.url = url;
}
public boolean click(ShaderPackScreen screen) {
GuiUtil.playButtonClickSound();
screen.openLinkConfirm(url);
return true;
}
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int listWidth, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
GuiUtil.bindIrisWidgetsTexture();
GuiUtil.drawButton(x - 2, y - 5, listWidth, 18, isMouseOver, false);
screen.drawCenteredString(label, (x + listWidth / 2) - 2, y, 0xFFFFFF);
}
}
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff. Show more