Compare commits

..
Author SHA1 Message Date
Jason Mitchell f3ed9aa82b Force invalidate dh depth tex when reconnecting DH Textures (#1862) 2026-06-14 14:59:01 -07:00
Jason Mitchell 307c563b6d Rework DH matrix handling (#1861) 2026-06-14 14:20:19 -07:00
Eclipse-Sol f92ce1d24e Fix enchant glint fix so that it looks proper (#1856) 2026-06-13 17:28:12 -07:00
sisyphussy a7fac16d97 VAO stuff (#1858) 2026-06-13 17:26:09 -07:00
Jason Mitchell 20c3caaed4 A few redirects for calls that DH was using that we weren't redirecting (#1859) 2026-06-13 16:50:44 -07:00
Jason Mitchell bf08cd50d9 Support glPixelStore in display lists (#1854) 2026-06-11 20:29:14 -07:00
Eclipse-Sol 805a266059 Support extended metas for NEID and EID (#1853) 2026-06-11 17:58:22 -07:00
Eclipse-Sol d3c9963280 Fix ctm crash due to null (#1852) 2026-06-11 18:02:08 -04:00
Eclipse-Sol 4630bc4fd8 Fix shaders not loading the correct dim (#1844) 2026-06-09 23:12:13 -07:00
Jason Mitchell ce32a3d3df Emulate glTexParameter(pname=GL_GENERATE_MIPMAP) on core (#1845) 2026-06-09 23:03:42 -07:00
Eclipse-Sol 1991dc58ec Fix Steadfast not loading right (#1838) 2026-06-08 20:06:29 -07:00
Eclipse-Sol ae19236e1a Remove force hand animation (#1839) 2026-06-08 20:05:49 -07:00
Kotmatross 7ad368c937 Be less lazy (#1834) 2026-06-08 17:03:36 -07:00
Eclipse-Sol 5c86c87bcf Allow devs to target the dragon death rays as its own entity id (#1836) 2026-06-08 17:03:05 -07:00
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
109 changed files with 3468 additions and 1292 deletions

No files matched your search

+1 -1
View File
@@ -66,7 +66,7 @@ configurations.all {
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
@@ -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,8 +7,6 @@ import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -17,6 +15,8 @@ import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Per-program compat uniform location cache and upload manager.
*
@@ -59,7 +59,10 @@ public class CompatUniformManager {
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -89,6 +92,8 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
@@ -103,8 +108,15 @@ public class CompatUniformManager {
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
private static final class ProgramUniforms {
final int[] locs;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
}
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
@@ -123,15 +135,6 @@ public class CompatUniformManager {
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
/**
@@ -154,45 +157,49 @@ public class CompatUniformManager {
}
if (hasAny) {
programLocations.put(program, locs);
programUniforms.put(program, new ProgramUniforms(locs));
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
/**
* Uploads the emulated uniforms.
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
pu.lastMvGen = mvGen;
pu.lastMvLinearGen = mvLinearGen;
pu.lastProjGen = projGen;
pu.lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
@@ -201,14 +208,16 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
return true;
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -225,12 +234,19 @@ public class CompatUniformManager {
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
}
}
}
if (mvLinearChanged) {
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
@@ -244,7 +260,6 @@ public class CompatUniformManager {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
@@ -261,10 +276,15 @@ public class CompatUniformManager {
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
if (locs[LOC_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
}
}
}
@@ -414,14 +434,15 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
programUniforms.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
return programUniforms.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
final ProgramUniforms pu = programUniforms.get(program);
return pu == null ? null : pu.locs;
}
}
@@ -111,6 +111,7 @@ public class DisplayListManager {
int listMode,
CommandRecorder recorder,
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
@@ -131,6 +132,19 @@ public class DisplayListManager {
return currentRecorder != null && Thread.currentThread() == recordingThread;
}
public static UnsupportedOperationException unsupportedInList(String what) {
return new UnsupportedOperationException(what + " in display lists not yet implemented - if you see this, please report! " + describeCurrentCompilation());
}
public static String describeCurrentCompilation() {
if (currentRecorder == null) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
final int depth = compilationStack.size() + 1;
return "while compiling display list " + glListId + " (" + mode + (depth > 1 ? ", nesting depth " + depth : "") + ")";
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
@@ -159,7 +173,9 @@ public class DisplayListManager {
* Only use this if you are 100% that the transforms will not be of any use after the display list draw call.
*/
public static void discardMatrixTransforms() {
resetRelativeTransform();
if (isRecording()) {
resetRelativeTransform();
}
}
/**
@@ -748,7 +764,7 @@ public class DisplayListManager {
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws,
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
@@ -784,9 +800,21 @@ public class DisplayListManager {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
}
try {
finishCurrentList();
} finally {
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
final boolean isNested = !compilationStack.isEmpty();
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
@@ -803,8 +831,9 @@ public class DisplayListManager {
final boolean hasCommands = !commandBuffer.isEmpty();
if (hasCommands) {
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
DisplayListVBO compiledBuffers = null;
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
@@ -824,16 +853,23 @@ public class DisplayListManager {
}
}
compiled = new CompiledDisplayList(commandBuffer.finish(), commandBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
currentRecorder = null;
} catch (Exception e) {
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
e.printStackTrace();
commandBuffer.delete();
currentRecorder = null;
if (compiledBuffers != null) {
compiledBuffers.delete();
}
return;
}
} else {
// Free the recorder even if empty
commandBuffer.delete();
currentRecorder = null;
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
compiled = CompiledDisplayList.EMPTY;
}
@@ -846,6 +882,10 @@ public class DisplayListManager {
if (LOG_DISPLAY_LIST_COMPILATION) {
logCompiledDisplayList(glListId, compiled, compilationStackTrace);
}
}
private static void popCompilationContext() {
final boolean isNested = !compilationStack.isEmpty();
// Handle nested compilation - restore parent context
if (isNested) {
@@ -856,6 +896,7 @@ public class DisplayListManager {
glListMode = parentContext.listMode;
currentRecorder = parentContext.recorder;
accumulatedDraws = parentContext.draws;
transformCallback = parentContext.callback;
compilationStackTrace = parentContext.stackTrace;
pendingTransformOps = parentContext.pendingOps;
multMatrixSources = parentContext.matrixSources;
@@ -869,6 +910,7 @@ public class DisplayListManager {
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
compilationStackTrace = null;
pendingTransformOps = null;
multMatrixSources = null;
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
@@ -9,13 +9,13 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Software emulation of GL feedback mode
*/
@@ -127,12 +127,12 @@ public class FeedbackManager {
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
final int s = VAOManager.getAttribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
return VAOManager.getAttribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
@@ -151,7 +151,7 @@ public class FeedbackManager {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int elementSize = VAOManager.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
@@ -255,7 +255,7 @@ public class FeedbackManager {
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VertexAttribState.get(0).vboId;
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
if (positionVbo != 0 && positionVbo != previousVbo) {
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -9,7 +9,6 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
@@ -17,6 +16,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
@@ -83,6 +83,7 @@ public final class QuadConverter {
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
GLStateManager.preDraw();
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
@@ -106,6 +107,7 @@ public final class QuadConverter {
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
@@ -144,7 +146,7 @@ public final class QuadConverter {
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int bytesPerIndex = VAOManager.Attrib.glTypeSizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
@@ -7,7 +7,6 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@@ -507,11 +507,6 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13.glSampleCoverage(value, invert);
@@ -1043,6 +1038,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -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) {}
@@ -237,6 +246,7 @@ public abstract class RenderBackend {
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract boolean isVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
@@ -26,6 +26,16 @@ public class Program {
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// Per-program uniform dirty-tracking
static final class UploadState {
int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
int texGenGen = -1, clipPlaneGen = -1;
float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
int viewportWidth = -1, viewportHeight = -1;
}
final UploadState uploadState = new UploadState();
// Uniform locations (-1 = not present in this variant)
// Matrices
public int locModelViewMatrix = -1;
@@ -1,15 +1,13 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
@@ -22,7 +20,7 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
/**
* FFP shader manager
*/
public class ShaderManager {
public final class ShaderManager {
private static final class Holder {
@@ -31,11 +29,11 @@ public class ShaderManager {
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
@Getter
private boolean active = false;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
@@ -56,10 +54,8 @@ public class ShaderManager {
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
@@ -72,12 +68,6 @@ public class ShaderManager {
public void enable() {
enabled = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
currentVertexFlags = format.getVertexFlags();
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
return false;
});
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -87,35 +77,39 @@ 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();
public void preDraw() {
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
if (!active) {
if (enabled) {
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
CompatUniformManager.onUseProgram(currentProgramId);
return;
}
active = true;
} else {
return;
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
@@ -127,33 +121,16 @@ public class ShaderManager {
uploadUniforms();
}
public void preDraw(int vertexFlags) {
currentVertexFlags = vertexFlags;
preDraw(
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
);
}
public void preDraw() {
preDraw(currentVertexFlags);
}
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
commitVariant(vkPacked, fkLen);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
}
private void uploadUniforms() {
@@ -180,22 +157,6 @@ public class ShaderManager {
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
public int getCurrentVertexFlags() {return currentVertexFlags;}
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
public void onBindVertexArray(int vaoId) {
final int flags = vaoVertexFlags.get(vaoId);
if (flags != -1) {
currentVertexFlags = flags;
}
}
public void onDeleteVertexArray(int vaoId) {
vaoVertexFlags.remove(vaoId);
}
public void destroy() {
cache.destroy();
uniforms.destroy();
@@ -46,116 +46,99 @@ public class Uniforms {
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
// Dirty tracking: last-uploaded generation per category + program ID.
// Program change forces full re-upload since uniform locations differ.
private int lastProgramId = -1;
private int lastMvGen = -1;
private int lastProjGen = -1;
private int lastTexMatGen = -1;
private int lastLightingGen = -1;
private int lastFragmentGen = -1;
private int lastColorGen = -1;
private int lastNormalGen = -1;
private int lastTexCoordGen = -1;
private float lastLightmapX = Float.NaN;
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
private float lastLineWidth = Float.NaN;
private int lastViewportWidth = -1;
private int lastViewportHeight = -1;
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
* Uses generation counters to skip categories that haven't changed since last upload.
*/
public void upload(Program program) {
final boolean programChanged = program.getProgramId() != lastProgramId;
lastProgramId = program.getProgramId();
final Program.UploadState st = program.uploadState;
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != st.mvGen;
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen;
final boolean projChanged = projGen != st.projGen;
final boolean texMatChanged = texMatGen != st.texMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged);
st.mvGen = mvGen;
st.mvLinearGen = mvLinearGen;
st.projGen = projGen;
st.texMatGen = texMatGen;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
if (litGen != st.lightingGen) {
uploadLighting(program);
lastLightingGen = litGen;
st.lightingGen = litGen;
}
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (programChanged || colorGen != lastColorGen) {
if (colorGen != st.colorGen) {
uploadCurrentColor(program);
lastColorGen = colorGen;
st.colorGen = colorGen;
}
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (programChanged || normalGen != lastNormalGen) {
if (normalGen != st.normalGen) {
uploadCurrentNormal(program);
lastNormalGen = normalGen;
st.normalGen = normalGen;
}
}
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
if (texGen != st.texCoordGen) {
uploadCurrentTexCoords(program);
lastTexCoordGen = texGen;
st.texCoordGen = texGen;
}
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
st.lightmapX = GLSMConfig.lastBrightnessX;
st.lightmapY = GLSMConfig.lastBrightnessY;
}
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (programChanged || tgGen != lastTexGenGen) {
if (tgGen != st.texGenGen) {
uploadTexGen(program);
lastTexGenGen = tgGen;
st.texGenGen = tgGen;
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
if (cpGen != st.clipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
st.clipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
if (fragGen != st.fragmentGen) {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
st.fragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
uploadWideLineUniforms(program);
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private void uploadMatrices(Program program, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -165,7 +148,9 @@ public class Uniforms {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
}
if (mvLinearChanged) {
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
@@ -314,12 +299,13 @@ public class Uniforms {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
final var color = GLStateManager.getColor();
tempVec4.set(
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.clear();
vec4Buf.put(Math.clamp(0f, 1f, color.getRed()));
vec4Buf.put(Math.clamp(0f, 1f, color.getGreen()));
vec4Buf.put(Math.clamp(0f, 1f, color.getBlue()));
vec4Buf.put(Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.flip();
tempVec4.get(vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
@@ -342,10 +328,7 @@ public class Uniforms {
// Upload current normal from FFPShaderManager's tracked normal
final var normal = ShaderManager.getCurrentNormal();
vec3Buf.clear();
vec3Buf.put(normal.x);
vec3Buf.put(normal.y);
vec3Buf.put(normal.z);
vec3Buf.flip();
normal.get(vec3Buf);
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
@@ -381,10 +364,7 @@ public class Uniforms {
private void uploadPlane(int loc, float[] plane) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(plane[0]);
vec4Buf.put(plane[1]);
vec4Buf.put(plane[2]);
vec4Buf.put(plane[3]);
vec4Buf.put(plane, 0, 4);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -470,29 +450,26 @@ public class Uniforms {
}
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
private void uploadWideLineUniforms(Program program) {
final Program.UploadState st = program.uploadState;
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
if (lineWidth != st.lineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
st.lineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
if (vw != st.viewportWidth || vh != st.viewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
st.viewportWidth = vw;
st.viewportHeight = vh;
}
}
private void uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(v.x);
vec4Buf.put(v.y);
vec4Buf.put(v.z);
vec4Buf.put(v.w);
vec4Buf.flip();
v.get(vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -0,0 +1,312 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private static int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
current.ebo = boundEBO;
current.attribs = currentAttribs;
}
VAOData data = vaoMap.get(vaoId);
if (data == null) {
data = new VAOData();
vaoMap.put(vaoId, data);
}
current = data;
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
vaoMap.remove(vaoId);
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = 0;
a.clientPointer = pointer;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
}
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public static void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public static boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (isClientSideAttrib(i)) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
/**
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
}
if (totalBytes == 0) return;
if (clientArraysVBO == 0) {
clientArraysVBO = RENDER_BACKEND.genBuffers();
}
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, clientArraysVBO);
if (totalBytes > clientArraysVBOCapacity) {
int newCap = Math.max(4096, clientArraysVBOCapacity);
while (newCap < totalBytes) newCap *= 2;
clientArraysVBOCapacity = newCap;
}
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, clientArraysVBOCapacity, GL15.GL_STREAM_DRAW);
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static final class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -35,11 +35,30 @@ public final class EcosystemNarrowRules {
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("FiskHeroes", "com.fiskmods.heroes.asm", List.of(
"com.fiskmods.heroes.asm.SHLoadingPlugin",
"com.fiskmods.heroes.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("FoamFix", "pl.asie.foamfix", List.of(
"pl.asie.foamfix.bugfixmod",
"pl.asie.foamfix.coremod",
"pl.asie.foamfix.forkage",
"pl.asie.foamfix.ghostbuster",
"pl.asie.foamfix.FoamFixMod",
"pl.asie.foamfix.HackyMessageFactoryWrapper",
"pl.asie.foamfix.LaunchWrapperRuntimeFix",
"pl.asie.foamfix.ProxyClient",
"pl.asie.foamfix.ProxyCommon",
"pl.asie.foamfix.repack.com.unascribed.ears.asm",
"pl.asie.foamfix.repack.com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
@@ -15,11 +15,11 @@ public final class AttribSnapshot {
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
return stride != 0 ? stride : size * VAOManager.Attrib.glTypeSizeBytes(type);
}
public int typeSizeBytes() {
return VertexAttribState.Attrib.glTypeSizeBytes(type);
return VAOManager.Attrib.glTypeSizeBytes(type);
}
}
@@ -56,7 +56,7 @@ public final class AttribSnapshot {
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
@@ -67,11 +67,11 @@ public final class AttribSnapshot {
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.vboId == 0) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
final long typeBytes = (long) a.size * VAOManager.Attrib.glTypeSizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
@@ -103,9 +103,9 @@ public final class AttribSnapshot {
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
@@ -653,12 +653,19 @@ public final class CommandRecorder {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
memFree(buffer);
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
@@ -666,7 +673,10 @@ public final class CommandRecorder {
}
final int size = size();
buffer.limit(size);
return memRealloc(buffer, size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public DisplayListCommand[] getComplexObjects() {
@@ -5,7 +5,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -293,7 +293,39 @@ public final class ImmediateModeRecorder {
}
private static float readComponent(AttribReader r, int base, int component) {
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
final int typeSize = VAOManager.Attrib.glTypeSizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
@@ -3,14 +3,14 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -68,7 +68,7 @@ public final class IndexedDrawCapture {
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VertexAttribState.Attrib.glTypeSizeBytes(srcIndexType);
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
@@ -106,7 +106,7 @@ public final class IndexedDrawCapture {
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
int enabledCount = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
@@ -118,7 +118,7 @@ public final class IndexedDrawCapture {
final int[] types = new int[enabledCount];
final boolean[] normalized = new boolean[enabledCount];
int k = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
locations[k] = i;
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
final class PixelDataSnapshot {
private PixelDataSnapshot() {}
static @Nullable ByteBuffer copy(@Nullable ByteBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining()).order(ByteOrder.nativeOrder());
out.put(pixels);
out.flip();
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable IntBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asIntBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable FloatBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asFloatBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable DoubleBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 8).order(ByteOrder.nativeOrder());
out.asDoubleBuffer().put(pixels);
pixels.position(pos);
return out;
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexImage2DCmd(
int border,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexSubImage2DCmd(
int height,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexSubImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -126,6 +126,7 @@ public class GLSMRedirector {
.add("glFrustum")
.add("glGetBoolean")
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetLight")
.add("glGetMaterial")
@@ -296,6 +297,7 @@ public class GLSMRedirector {
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("nglShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
@@ -321,6 +323,7 @@ public class GLSMRedirector {
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
@@ -342,6 +345,7 @@ public class GLSMRedirector {
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -587,18 +591,16 @@ public class GLSMRedirector {
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
final String glsmName = redirects != null ? redirects.get(mNode.name) : null;
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
}
}
// Redirect <init> calls for quadric classes
@@ -617,8 +619,7 @@ public class GLSMRedirector {
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null;
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
@@ -27,65 +27,75 @@ public class LightModelState implements ISettableState<LightModelState> {
twoSide = 0.0F;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setColorControl(int val) {
public boolean setColorControl(int val) {
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
this.colorControl = val;
return true;
}
return false;
}
public void setColorControl(IntBuffer newBuffer) {
setColorControl(newBuffer.get());
public boolean setColorControl(IntBuffer newBuffer) {
return setColorControl(newBuffer.get());
}
public void setLocalViewer(float val) {
public boolean setLocalViewer(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
this.localViewer = val;
return true;
}
return false;
}
public void setLocalViewer(FloatBuffer newBuffer) {
setLocalViewer(newBuffer.get());
public boolean setLocalViewer(FloatBuffer newBuffer) {
return setLocalViewer(newBuffer.get());
}
public void setLocalViewer(IntBuffer newBuffer) {
setLocalViewer((float) newBuffer.get());
public boolean setLocalViewer(IntBuffer newBuffer) {
return setLocalViewer((float) newBuffer.get());
}
public void setLocalViewer(int val) {
setLocalViewer((float) val);
public boolean setLocalViewer(int val) {
return setLocalViewer((float) val);
}
public void setTwoSide(float val) {
public boolean setTwoSide(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
this.twoSide = val;
return true;
}
return false;
}
public void setTwoSide(FloatBuffer newBuffer) {
setTwoSide(newBuffer.get());
public boolean setTwoSide(FloatBuffer newBuffer) {
return setTwoSide(newBuffer.get());
}
public void setTwoSide(IntBuffer newBuffer) {
setTwoSide((float) newBuffer.get());
public boolean setTwoSide(IntBuffer newBuffer) {
return setTwoSide((float) newBuffer.get());
}
public void setTwoSide(int val) {
setTwoSide((float) val);
public boolean setTwoSide(int val) {
return setTwoSide((float) val);
}
@Override
@@ -17,6 +17,7 @@ public class LightState implements ISettableState<LightState> {
private static final Vector4i vector4i = new Vector4i();
private static final Vector4f vector4f = new Vector4f();
private static final Vector3f vector3f = new Vector3f();
private static final Vector3i vector3i = new Vector3i();
private static final Matrix3f matrix3f = new Matrix3f();
@@ -27,6 +28,10 @@ public class LightState implements ISettableState<LightState> {
public final Vector4f specular;
public final Vector4f position;
public final Vector3f spotDirection;
private final Vector4f rawPosition;
private int posMvGeneration = -1;
private final Vector3f rawSpotDirection;
private int spotDirLinearGeneration = -1;
public float spotExponent;
public float spotCutoff;
public float spotCosCutoff;
@@ -66,178 +71,221 @@ public class LightState implements ISettableState<LightState> {
this.linearAttenuation = linearAttenuation;
this.quadraticAttenuation = quadraticAttenuation;
this.rawPosition = new Vector4f(position);
this.position = position;
this.position.mul(GLStateManager.getModelViewMatrix());
this.posMvGeneration = GLStateManager.mvGeneration;
this.rawSpotDirection = new Vector3f(spotDirection);
this.spotDirection = spotDirection;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.mul(matrix3f);
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setPosition(FloatBuffer newBuffer) {
// We are bypassing cache everytime for position, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
this.position.set(newBuffer);
this.position.mul(GLStateManager.getModelViewMatrix());
public boolean setPosition(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
return applyPosition(vector4f);
}
public void setPosition(IntBuffer newBuffer) {
public boolean setPosition(IntBuffer newBuffer) {
vector4i.set(newBuffer);
this.position.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
this.position.mul(GLStateManager.getModelViewMatrix());
vector4f.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
return applyPosition(vector4f);
}
public void setSpotDirection(FloatBuffer newBuffer) {
// We are bypassing cache everytime for spot direction, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(newBuffer);
this.spotDirection.mul(matrix3f);
private boolean applyPosition(Vector4f raw) {
final int mvGen = GLStateManager.mvGeneration;
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
this.rawPosition.set(raw);
this.posMvGeneration = mvGen;
this.position.set(raw);
this.position.mul(GLStateManager.getModelViewMatrix());
return true;
}
return false;
}
public void setSpotDirection(IntBuffer newBuffer) {
public boolean setSpotDirection(FloatBuffer newBuffer) {
vector3f.set(newBuffer);
return applySpotDirection(vector3f);
}
public boolean setSpotDirection(IntBuffer newBuffer) {
vector3i.set(newBuffer);
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
this.spotDirection.mul(matrix3f);
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
return applySpotDirection(vector3f);
}
public void setSpotExponent(FloatBuffer newBuffer) {
setSpotExponent(newBuffer.get());
private boolean applySpotDirection(Vector3f raw) {
final int mvLinearGen = GLStateManager.mvLinearGeneration;
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
this.rawSpotDirection.set(raw);
this.spotDirLinearGeneration = mvLinearGen;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(raw);
this.spotDirection.mul(matrix3f);
return true;
}
return false;
}
public void setSpotExponent(IntBuffer newBuffer) {
setSpotExponent((float) newBuffer.get());
public boolean setSpotExponent(FloatBuffer newBuffer) {
return setSpotExponent(newBuffer.get());
}
public void setSpotExponent(int i) {
setSpotExponent((float) i);
public boolean setSpotExponent(IntBuffer newBuffer) {
return setSpotExponent((float) newBuffer.get());
}
public void setSpotExponent(float f) {
public boolean setSpotExponent(int i) {
return setSpotExponent((float) i);
}
public boolean setSpotExponent(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
this.spotExponent = f;
return true;
}
return false;
}
public void setSpotCutoff(FloatBuffer newBuffer) {
setSpotCutoff(newBuffer.get());
public boolean setSpotCutoff(FloatBuffer newBuffer) {
return setSpotCutoff(newBuffer.get());
}
public void setSpotCutoff(IntBuffer newBuffer) {
setSpotCutoff((float) newBuffer.get());
public boolean setSpotCutoff(IntBuffer newBuffer) {
return setSpotCutoff((float) newBuffer.get());
}
public void setSpotCutoff(int i) {
setSpotCutoff((float) i);
public boolean setSpotCutoff(int i) {
return setSpotCutoff((float) i);
}
public void setSpotCutoff(float f) {
public boolean setSpotCutoff(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
this.spotCutoff = f;
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
return true;
}
return false;
}
public void setConstantAttenuation(FloatBuffer newBuffer) {
setConstantAttenuation(newBuffer.get());
public boolean setConstantAttenuation(FloatBuffer newBuffer) {
return setConstantAttenuation(newBuffer.get());
}
public void setConstantAttenuation(IntBuffer newBuffer) {
setConstantAttenuation((float) newBuffer.get());
public boolean setConstantAttenuation(IntBuffer newBuffer) {
return setConstantAttenuation((float) newBuffer.get());
}
public void setConstantAttenuation(int i) {
setConstantAttenuation((float) i);
public boolean setConstantAttenuation(int i) {
return setConstantAttenuation((float) i);
}
public void setConstantAttenuation(float f) {
public boolean setConstantAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
this.constantAttenuation = f;
return true;
}
return false;
}
public void setLinearAttenuation(FloatBuffer newBuffer) {
setLinearAttenuation(newBuffer.get());
public boolean setLinearAttenuation(FloatBuffer newBuffer) {
return setLinearAttenuation(newBuffer.get());
}
public void setLinearAttenuation(IntBuffer newBuffer) {
setLinearAttenuation((float) newBuffer.get());
public boolean setLinearAttenuation(IntBuffer newBuffer) {
return setLinearAttenuation((float) newBuffer.get());
}
public void setLinearAttenuation(int i) {
setLinearAttenuation((float) i);
public boolean setLinearAttenuation(int i) {
return setLinearAttenuation((float) i);
}
public void setLinearAttenuation(float f) {
public boolean setLinearAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
this.linearAttenuation = f;
return true;
}
return false;
}
public void setQuadraticAttenuation(FloatBuffer newBuffer) {
setQuadraticAttenuation(newBuffer.get());
public boolean setQuadraticAttenuation(FloatBuffer newBuffer) {
return setQuadraticAttenuation(newBuffer.get());
}
public void setQuadraticAttenuation(IntBuffer newBuffer) {
setQuadraticAttenuation((float) newBuffer.get());
public boolean setQuadraticAttenuation(IntBuffer newBuffer) {
return setQuadraticAttenuation((float) newBuffer.get());
}
public void setQuadraticAttenuation(int i) {
setQuadraticAttenuation((float) i);
public boolean setQuadraticAttenuation(int i) {
return setQuadraticAttenuation((float) i);
}
public void setQuadraticAttenuation(float f) {
public boolean setQuadraticAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
this.quadraticAttenuation = f;
return true;
}
return false;
}
@Override
@@ -246,7 +294,11 @@ public class LightState implements ISettableState<LightState> {
this.diffuse.set(state.diffuse);
this.specular.set(state.specular);
this.position.set(state.position);
this.rawPosition.set(state.rawPosition);
this.posMvGeneration = state.posMvGeneration;
this.spotDirection.set(state.spotDirection);
this.rawSpotDirection.set(state.rawSpotDirection);
this.spotDirLinearGeneration = state.spotDirLinearGeneration;
this.spotExponent = state.spotExponent;
this.spotCutoff = state.spotCutoff;
this.spotCosCutoff = state.spotCosCutoff;
@@ -37,82 +37,100 @@ public class MaterialState implements ISettableState<MaterialState> {
colorIndexes = new Vector3f(0.0F, 1.0F, 1.0F);
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setEmission(FloatBuffer newBuffer) {
public boolean setEmission(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setEmission(IntBuffer newBuffer) {
public boolean setEmission(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setShininess(float val) {
public boolean setShininess(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
this.shininess = val;
return true;
}
return false;
}
// On the buffer versions of shininess, we need to call `get(0)` explicitly specifying the index
// otherwise calling `get()` advances the buffer position, and some mods may re-use the same buffer
// in subsequent calls, resulting in a BufferUnderflowException.
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get(0)); }
public boolean setShininess(FloatBuffer newBuffer) { return setShininess(newBuffer.get(0)); }
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get(0)); }
public boolean setShininess(IntBuffer newBuffer) { return setShininess((float) newBuffer.get(0)); }
public void setShininess(int val) { setShininess((float) val);}
public boolean setShininess(int val) { return setShininess((float) val);}
public void setColorIndexes(FloatBuffer newBuffer) {
public boolean setColorIndexes(FloatBuffer newBuffer) {
// You are reading this correctly, nvidia drivers flip the y and z values in
// glMaterial specifically for GL_COLOR_INDEXES. Other drivers do not do this.
// This probably breaks things but who knows, GLSM is setup to track to what the
@@ -125,15 +143,19 @@ public class MaterialState implements ISettableState<MaterialState> {
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
public void setColorIndexes(IntBuffer newBuffer) {
public boolean setColorIndexes(IntBuffer newBuffer) {
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
@Override
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,198 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
private static int clientSideEnabledCount;
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
clientSideEnabledCount = 0;
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
clientSideEnabledCount = 0;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (was) clientSideEnabledCount--;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
final boolean now = a.isClientSide();
if (was != now) clientSideEnabledCount += now ? 1 : -1;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].isClientSide()) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -7,7 +7,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -204,14 +203,8 @@ public class TessellatorStreamingDrawer {
firstVertex = 0;
}
GLStateManager.prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw(flags);
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_NORMAL_ARRAY);
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
@@ -17,6 +17,7 @@ public class TextureInfo {
@Getter @Setter protected int magFilter = GL11.GL_LINEAR;
@Getter @Setter protected int wrapS = GL11.GL_REPEAT;
@Getter @Setter protected int wrapT = GL11.GL_REPEAT;
@Getter @Setter protected int baseLevel = 0;
@Getter @Setter protected int maxLevel = 1000;
@Getter @Setter protected int minLod = -1000;
@Getter @Setter protected int maxLod = 1000;
@@ -24,6 +25,8 @@ public class TextureInfo {
@Getter @Setter protected float maxAnisotropy = 1.0f;
@Getter @Setter protected boolean generateMipmap = false;
protected TextureInfo(int id) {
this.id = id;
}
@@ -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);
}
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.util.GLSMUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -133,7 +133,7 @@ public class GLSM_VAO_UnitTest {
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.boundVAO = array;
VertexAttribState.onBindVertexArray(array);
VAOManager.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, org.lwjgl.opengl.GL11.GL_COMPILE);
@@ -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();
}
}
}
@@ -3,10 +3,10 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -307,10 +307,10 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VertexAttribState.set(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData, 0);
VertexAttribState.setEnabled(0, true);
VertexAttribState.set(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice, 0);
VertexAttribState.setEnabled(1, true);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -0,0 +1,61 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelUnpackStateTest {
@Test
void defaults() {
assertTrue(PixelUnpackState.DEFAULT.isDefault());
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelUnpackState s = PixelUnpackState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packAndUnknownPnamesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
}
}
@@ -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);
@@ -1028,6 +1076,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30C.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20C.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -1318,11 +1371,14 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public boolean supportsDebugOutput() {
return caps != null && caps.OpenGL43;
return caps != null && (caps.OpenGL43 || caps.GL_KHR_debug);
}
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
debugCallback = GLDebugMessageCallback.create(
(source, type, id, severity, length, message, userParam) -> {
final String msg = GLDebugMessageCallback.getMessage(length, message);
@@ -1350,6 +1406,9 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int disableDebugOutput() {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
GL43C.glDebugMessageCallback(null, 0L);
if (debugCallback != null) {
debugCallback.free();
+1 -1
View File
@@ -16,7 +16,7 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.25")
}
include("glsm")
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
@@ -14,6 +15,8 @@ public class ModStatus {
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isEIDLoaded;
public static boolean isMetadataExtended;
public static boolean isLotrLoaded;
public static boolean isChunkAPILoaded;
public static boolean isEIDBiomeLoaded;
@@ -35,9 +38,9 @@ public class ModStatus {
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isEIDLoaded = Loader.isModLoaded("endlessids");
isLotrLoaded = Loader.isModLoaded("lotr");
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
@@ -65,5 +68,15 @@ public class ModStatus {
isNEIDMetadataExtended = true;
}
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
eidBlockItemExtended = GeneralConfig.extendBlockItem;
eidBiomeExtended = GeneralConfig.extendBiome;
}
isEIDBiomeLoaded = eidBiomeExtended || Loader.isModLoaded("endlessids_biome");
isMetadataExtended = isNEIDMetadataExtended || eidBlockItemExtended;
}
}
@@ -227,13 +227,13 @@ public class AngelicaConfig {
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
"While using \"Compact Vertex Format\" makes the situation even worse.",
"This option fixes it.",
"Requires texture reloading (F3 + T) after changing this option to take effect"})
@Config.DefaultBoolean(true)
public static boolean fixBlockCrack;
"This option fixes it, though may lead to other visual artifacts.",
"Requires game restart after changing this option to take effect"})
@Config.DefaultBoolean(false)
public static boolean blockCrackFix;
@Config.Comment({
"The \"epsilon\" value for the fixBlockCrack option. ",
"The \"epsilon\" value for the blockCrackFix option. ",
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
"May cause intense flickering (z-fighting) between blocks if the value is too high"
})
@@ -241,7 +241,7 @@ public class AngelicaConfig {
@Config.DefaultDouble(0.001)
public static double blockCrackFixEpsilon;
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
@Config.Comment("Block classes that have bugs when rendering with the blockCrackFix can be put here to avoid manipulating them")
@Config.DefaultStringList({
"net.minecraft.block.BlockCauldron",
"net.minecraft.block.BlockStairs"
@@ -249,7 +249,7 @@ public class AngelicaConfig {
public static String[] blockCrackFixBlacklist;
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
"Add a block class here if you see flickering (z-fighting) with blockCrackFix enabled"
})
@Config.DefaultStringList({
"gregtech.common.blocks.BlockOres",
@@ -310,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.")
@@ -186,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();
}
/**
@@ -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);
}
@@ -117,7 +117,7 @@ public enum Mixins implements IMixins {
.setPhase(Phase.EARLY)
.addClientMixins("angelica.bugfixes.MixinRenderBlocks_CrackFix")
.addExcludedMod(TargetedMod.FALSETWEAKS)
.setApplyIf(() -> AngelicaConfig.fixBlockCrack)),
.setApplyIf(() -> AngelicaConfig.blockCrackFix)),
ANGELICA_FIX_FLUID_RENDERER_CHECKING_BLOCK_AGAIN(
new MixinBuilder("Fix RenderBlockFluid reading the block type from the world access multiple times")
@@ -329,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"),
@@ -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
@@ -15,6 +15,7 @@ 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;
@@ -268,13 +269,15 @@ 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);
@@ -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,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;
@@ -53,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) {
@@ -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());
+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;
@@ -25,8 +27,12 @@ public class BlockMaterialMapping {
public record BlockIdMaps(Reference2ObjectMap<Block, Int2IntMap> blockMetaMap, NbtConditionalIdMap<Block> tileEntityMap) {}
/** 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;
// 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 the standard block meta ID map, the TileEntity NBT-conditional map, and registers
@@ -129,6 +135,7 @@ public class BlockMaterialMapping {
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.
@@ -145,12 +152,16 @@ public class BlockMaterialMapping {
}
final Block block = resolveBlockOrNull(target.id());
if (block == null) continue;
applyMetas(block, target.metas(), 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();
@@ -159,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.
@@ -161,6 +161,7 @@ public class DHCompatInternal {
if (((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion() != cachedVersion) {
cachedVersion = ((IRenderTargetExt) Minecraft.getMinecraft().getFramebuffer()).iris$getDepthBufferVersion();
createDepthTex(Minecraft.getMinecraft().getFramebuffer().framebufferWidth, Minecraft.getMinecraft().getFramebuffer().framebufferHeight);
storedDepthTex = -1;
}
if (storedDepthTex != depthTex && dhTerrainFramebuffer != null) {
storedDepthTex = depthTex;
@@ -31,7 +31,6 @@ import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
@@ -46,12 +45,6 @@ import java.util.Map;
public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgram {
// Uniforms
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
private final int id;
private final ProgramUniforms uniforms;
private final CustomUniforms customUniforms;
@@ -62,7 +55,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
private final Matrix4f tempModel = new Matrix4f();
private final Matrix4f tempProj = new Matrix4f();
private final Matrix4f tempMat4 = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
private final int instancedShaderOffsetChunkUniform;
private final int instancedShaderOffsetSubChunkUniform;
@@ -143,12 +135,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GLStateManager.glEnableVertexAttribArray(0);
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
this.instancedShaderOffsetChunkUniform = this.tryGetUniformLocation2("uOffsetChunk");
this.instancedShaderOffsetSubChunkUniform = this.tryGetUniformLocation2("uOffsetSubChunk");
this.instancedShaderCameraChunkPosUniform = this.tryGetUniformLocation2("uCameraPosChunk");
@@ -213,18 +199,6 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind(DhApiRenderParam renderParam) {
GLStateManager.glBindVertexArray(va);
@@ -238,14 +212,8 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
toJOML(tempModel, renderParam.dhModelViewMatrix);
toJOML(tempProj, renderParam.dhProjectionMatrix);
setUniform(modelViewUniform, tempModel);
setUniform(projectionUniform, tempProj);
this.setUniform(this.instancedShaderProjectionModelViewMatrixUniform, tempMat4.set(tempProj).mul(tempModel));
tempModel.invert();
tempProj.invert();
setUniform(modelViewInverseUniform, tempModel);
setUniform(projectionInverseUniform, tempProj);
setUniform(normalMatrix3fUniform, tempModel.transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(tempProj, tempModel);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
@@ -259,6 +227,7 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
}
public void unbind() {
LodRendererEvents.popDhMatrices();
GLStateManager.glBindVertexArray(0);
GLStateManager.glUseProgram(0);
ProgramUniforms.clearActiveUniforms();
@@ -3,7 +3,6 @@ package net.coderbot.iris.compat.dh;
import com.google.common.primitives.Ints;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import com.mitchej123.lwjgl.MemoryStack;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import net.coderbot.iris.gl.blending.BlendModeOverride;
@@ -26,15 +25,12 @@ import net.coderbot.iris.uniforms.builtin.BuiltinReplacementUniforms;
import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -44,11 +40,6 @@ public class IrisLodRenderProgram {
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int mircoOffsetUniform;
public final int modelViewUniform;
public final int modelViewInverseUniform;
public final int projectionUniform;
public final int projectionInverseUniform;
public final int normalMatrix3fUniform;
// Fog/Clip Uniforms
public final int clipDistanceUniform;
private final int id;
@@ -58,9 +49,6 @@ public class IrisLodRenderProgram {
private final ProgramImages images;
private final BlendModeOverride blend;
private final BufferBlendOverride[] bufferBlendOverrides;
private final Matrix4f tempMat4a = new Matrix4f();
private final Matrix4f tempMat4b = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
// This will bind AbstractVertexAttribute
private IrisLodRenderProgram(String name, boolean isShadowPass, boolean translucent, BlendModeOverride override, BufferBlendOverride[] bufferBlendOverrides, String vertex, String tessControl, String tessEval, String geometry, String fragment, CustomUniforms customUniforms, DeferredWorldRenderingPipeline pipeline) {
@@ -132,11 +120,6 @@ public class IrisLodRenderProgram {
modelOffsetUniform = tryGetUniformLocation2("modelOffset");
worldYOffsetUniform = tryGetUniformLocation2("worldYOffset");
mircoOffsetUniform = tryGetUniformLocation2("mircoOffset");
projectionUniform = tryGetUniformLocation2("iris_ProjectionMatrix");
projectionInverseUniform = tryGetUniformLocation2("iris_ProjectionMatrixInverse");
modelViewUniform = tryGetUniformLocation2("iris_ModelViewMatrix");
modelViewInverseUniform = tryGetUniformLocation2("iris_ModelViewMatrixInverse");
normalMatrix3fUniform = tryGetUniformLocation2("iris_NormalMatrix");
// Fog/Clip Uniforms
clipDistanceUniform = tryGetUniformLocation2("clipDistance");
@@ -176,30 +159,6 @@ public class IrisLodRenderProgram {
return GLStateManager.glGetUniformLocation(this.id, name);
}
public void setUniform(int index, Matrix4fc matrix) {
if (index == -1 || matrix == null) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(16);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix4(index, false, buffer);
}
}
public void setUniform(int index, Matrix3f matrix) {
if (index == -1) return;
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buffer = stack.callocFloat(9);
matrix.get(buffer);
buffer.rewind();
GLStateManager.glUniformMatrix3(index, false, buffer);
}
}
// Override ShaderProgram.bind()
public void bind() {
GLStateManager.glUseProgram(id);
@@ -227,11 +186,8 @@ public class IrisLodRenderProgram {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + IrisSamplers.LIGHTMAP_TEXTURE_UNIT);
DynamicTexture lightmapTexture = ((EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer).getLightmapTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture.getGlTextureId());
setUniform(modelViewUniform, modelView);
setUniform(modelViewInverseUniform, modelView.invert(tempMat4a));
setUniform(projectionUniform, projection);
setUniform(projectionInverseUniform, projection.invert(tempMat4b));
setUniform(normalMatrix3fUniform, tempMat4a.set(modelView).invert().transpose3x3(tempMat3));
LodRendererEvents.pushDhMatrices(projection, modelView);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
@@ -33,6 +33,7 @@ import net.coderbot.iris.shadows.ShadowRenderingState;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.irisshaders.iris.api.v0.IrisApi;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
public class LodRendererEvents {
@@ -46,6 +47,28 @@ public class LodRendererEvents {
private static int textureHeight;
private static final Matrix4f tempMat4 = new Matrix4f();
private static boolean dhMatricesActive = false;
private static final Matrix4f savedModelView = new Matrix4f();
private static final Matrix4f savedProjection = new Matrix4f();
static void pushDhMatrices(Matrix4fc projection, Matrix4fc modelView) {
if (!dhMatricesActive) {
savedModelView.set(GLStateManager.getModelViewMatrix());
savedProjection.set(GLStateManager.getProjectionMatrix());
dhMatricesActive = true;
}
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.setProjectionMatrix(projection);
}
static void popDhMatrices() {
if (dhMatricesActive) {
GLStateManager.setModelViewMatrix(savedModelView);
GLStateManager.setProjectionMatrix(savedProjection);
dhMatricesActive = false;
}
}
// constructor //
@@ -181,6 +204,7 @@ public class LodRendererEvents {
DhApiBeforeRenderCleanupEvent beforeCleanupEvent = new DhApiBeforeRenderCleanupEvent() {
@Override
public void beforeCleanup(DhApiEventParam<DhApiRenderParam> event) {
popDhMatrices();
if (getInstance().shouldOverride) {
if (ShadowRenderingState.areShadowsCurrentlyBeingRendered()) {
getInstance().getShadowShader().unbind();
@@ -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
@@ -4,6 +4,7 @@ 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;
@@ -91,6 +92,10 @@ public class StandardMacros {
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));
@@ -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);
}
}
@@ -14,6 +14,6 @@ public class LabelEntry extends BaseEntry {
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int listWidth, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
screen.drawCenteredString(label, x, y, 0xFFFFFF);
screen.drawCenteredString(label, (x + listWidth / 2) - 2, y, 0xFFFFFF);
}
}
@@ -1,6 +1,7 @@
package net.coderbot.iris.gui.element.shaderselection;
import lombok.Getter;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.element.ShaderPackSelectionList;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.gui.FontRenderer;
@@ -37,6 +38,8 @@ public class ShaderPackEntry extends BaseEntry {
}
if(isMouseOver) {
GuiUtil.bindIrisWidgetsTexture();
GuiUtil.drawButton(x - 2, y - 6, listWidth, 20, true, false);
name = EnumChatFormatting.BOLD + name;
}
@@ -13,12 +13,10 @@ public class TopButtonRowEntry extends BaseEntry {
private static final String NONE_PRESENT_LABEL = I18n.format("options.iris.shaders.nonePresent"); //.withStyle(ChatFormatting.GRAY);
private static final String SHADERS_DISABLED_LABEL = I18n.format("options.iris.shaders.disabled");
private static final String SHADERS_ENABLED_LABEL = I18n.format("options.iris.shaders.enabled");
private static final int REFRESH_BUTTON_WIDTH = 18;
private final ShaderPackSelectionList shaderPackSelectionList;
private final IrisElementRow buttons = new IrisElementRow();
private final EnableShadersButtonElement enableDisableButton;
private final IrisElementRow.Element refreshPacksButton;
public boolean allowEnableShadersButton = true;
public boolean shadersEnabled;
@@ -38,15 +36,7 @@ public class TopButtonRowEntry extends BaseEntry {
return false;
});
this.refreshPacksButton = new IrisElementRow.IconButtonElement(
GuiUtil.Icon.REFRESH,
button -> {
this.shaderPackSelectionList.refresh();
GuiUtil.playButtonClickSound();
return true;
});
this.buttons.add(this.enableDisableButton, 0).add(this.refreshPacksButton, REFRESH_BUTTON_WIDTH);
this.buttons.add(this.enableDisableButton, 0);
}
public void setShadersEnabled(boolean shadersEnabled) {
@@ -57,7 +47,7 @@ public class TopButtonRowEntry extends BaseEntry {
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int listWidth, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
this.buttons.setWidth(this.enableDisableButton, (listWidth - 1) - REFRESH_BUTTON_WIDTH);
this.buttons.setWidth(this.enableDisableButton, listWidth);
this.enableDisableButton.centerX = x + (int)(listWidth * 0.5);
this.buttons.drawScreen(x - 2, y - 3, 18, mouseX, mouseY, 0, isMouseOver);
@@ -90,9 +90,9 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
// Draw the label
font.drawStringWithShadow(this.trimmedLabel, x + 6, y + 7, 0xFFFFFF);
font.drawStringWithShadow(this.trimmedLabel, x + 6, y + 7, this.isValueModified() ? 0xFFC94A : 0xFFFFFF);
// Draw the value label
font.drawStringWithShadow(this.valueLabel, (x + (width - 2)) - (int)(this.valueSectionWidth * 0.5) - (int)(font.getStringWidth(this.valueLabel) * 0.5), y + 7, 0xFFFFFF);
font.drawStringWithShadow(this.valueLabel, (x + (width - 2)) - (int)(this.valueSectionWidth * 0.5) - (int)(font.getStringWidth(this.valueLabel) * 0.5), y + 7, getValueColor());
}
protected final void renderOptionWithValue(int x, int y, int width, int height, boolean hovered) {
@@ -119,17 +119,15 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
}
protected final String createTrimmedLabel() {
String label = GuiUtil.shortenText(Minecraft.getMinecraft().fontRenderer, this.label, this.maxLabelWidth);
if (this.isValueModified()) {
label = label + " (*)"; //.withStyle(style -> style.withColor(TextColor.fromRgb(0xffc94a)));
}
return label;
return GuiUtil.shortenText(Minecraft.getMinecraft().fontRenderer, this.label, this.maxLabelWidth);
}
protected abstract String createValueLabel();
protected int getValueColor() {
return 0xFFFFFF;
}
public abstract boolean applyNextValue();
public abstract boolean applyPreviousValue();
@@ -147,7 +145,13 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
@Override
public Optional<String> getCommentBody() {
return Optional.ofNullable(getCommentKey()).map(I18n::format);
final String key = getCommentKey();
if (key == null) {
return Optional.empty();
}
final String translated = GuiUtil.translateLenient(key);
// Don't show comment boxes without a translation, otherwise you get something like option.{commentname}.comment
return translated.equals(key) ? Optional.empty() : Optional.of(translated);
}
@Override
@@ -70,6 +70,14 @@ public class BooleanElementWidget extends BaseOptionElementWidget<OptionMenuBool
return this.value ? TEXT_TRUE : TEXT_FALSE;
}
@Override
protected int getValueColor() {
if (this.value == this.defaultValue) {
return 0xFFFFFF;
}
return this.value ? 0x55FF55 : 0xFF5555;
}
@Override
public String getCommentKey() {
return "option." + this.option.getName() + ".comment";
@@ -6,13 +6,10 @@ import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuLinkElement;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.resources.I18n;
import java.util.Optional;
public class LinkElementWidget extends CommentedElementWidget<OptionMenuLinkElement> {
private static final String ARROW = new String(">");
private final String targetScreenId;
private final String label;
@@ -39,7 +36,7 @@ public class LinkElementWidget extends CommentedElementWidget<OptionMenuLinkElem
final FontRenderer font = Minecraft.getMinecraft().fontRenderer;
final int maxLabelWidth = width - 9;
final int maxLabelWidth = width - 6;
if (font.getStringWidth(this.label) > maxLabelWidth) {
this.isLabelTrimmed = true;
@@ -51,8 +48,7 @@ public class LinkElementWidget extends CommentedElementWidget<OptionMenuLinkElem
int labelWidth = font.getStringWidth(this.trimmedLabel);
font.drawStringWithShadow(this.trimmedLabel, x + (int)(width * 0.5) - (int)(labelWidth * 0.5) - (int)(0.5 * Math.max(labelWidth - (width - 18), 0)), y + 7, 0xFFFFFF);
font.drawString(ARROW, (x + width) - 9, y + 7, 0xFFFFFF);
font.drawStringWithShadow(this.trimmedLabel, x + (int)(width * 0.5) - (int)(labelWidth * 0.5), y + 7, 0xFFFFFF);
if (hovered && this.isLabelTrimmed) {
// To prevent other elements from being drawn on top of the tooltip
@@ -79,7 +75,7 @@ public class LinkElementWidget extends CommentedElementWidget<OptionMenuLinkElem
@Override
public Optional<String> getCommentBody() {
final String translation = "screen." + this.targetScreenId + ".comment";
final String translated = I18n.format(translation);
return Optional.ofNullable(!translated.equals(translation) ? I18n.format(translation) : null);
final String translated = GuiUtil.translateLenient(translation);
return Optional.ofNullable(!translated.equals(translation) ? translated : null);
}
}
@@ -1,6 +1,7 @@
package net.coderbot.iris.gui.element.widget;
import net.coderbot.iris.Iris;
import net.minecraft.util.EnumChatFormatting;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
import net.coderbot.iris.gui.element.ShaderPackOptionList;
@@ -64,7 +65,7 @@ public final class OptionMenuConstructor {
static {
registerScreen(OptionMenuMainElementScreen.class, screen ->
new ElementWidgetScreenData(Iris.getCurrentPackName() + (Iris.isFallback() ? " (fallback)" : ""), false));
new ElementWidgetScreenData(EnumChatFormatting.BOLD + Iris.getCurrentPackName() + (Iris.isFallback() ? " (fallback)" : ""), false));
registerScreen(OptionMenuSubElementScreen.class, screen ->
new ElementWidgetScreenData(GuiUtil.translateOrDefault(new String(screen.screenId), "screen." + screen.screenId), true));
@@ -59,6 +59,11 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
return this.profileLabel;
}
@Override
protected int getValueColor() {
return PROFILE_CUSTOM.equals(this.profileLabel) ? 0xFFFF55 : 0xFFFFFF;
}
@Override
public Optional<String> getCommentTitle() {
return Optional.of(PROFILE_LABEL);
@@ -108,7 +108,7 @@ public class SliderElementWidget extends StringElementWidget {
@Override
public boolean mouseReleased(double mx, double my, int button) {
if (button == 0) {
if (button == 0 && this.mouseDown) {
this.onReleased();
return true;
@@ -60,6 +60,11 @@ public class StringElementWidget extends BaseOptionElementWidget<OptionMenuStrin
return GuiUtil.translateOrDefault(getValue(), "value." + this.option.getName() + "." + getValue());
}
@Override
protected int getValueColor() {
return 0x6688FF;
}
@Override
public String getCommentKey() {
return "option." + this.option.getName() + ".comment";
@@ -2,6 +2,7 @@ package net.coderbot.iris.gui.screen;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
@@ -16,8 +17,12 @@ import net.coderbot.iris.shaderpack.ShaderPack;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiConfirmOpenLink;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
@@ -25,12 +30,19 @@ import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -41,8 +53,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
*/
public static final Set<Runnable> TOP_LAYER_RENDER_QUEUE = new HashSet<>();
private static final String SELECT_TITLE = I18n.format("pack.iris.select.title");
private static final String CONFIGURE_TITLE = I18n.format("pack.iris.configure.title");
private static final String SELECT_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title");
private static final String CONFIGURE_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title");
private static final int COMMENT_PANEL_WIDTH = 314;
private final GuiScreen parent;
@@ -72,7 +84,6 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
private boolean guiHidden = false;
private boolean dirty = false;
private float guiButtonHoverTimer = 0.0f;
public ShaderPackScreen(GuiScreen parent) {
this.title = I18n.format("options.iris.shaderPackSelection.title");
@@ -102,6 +113,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
this.initGui();
}
handleDroppedFiles();
if (this.mc.theWorld == null) {
super.drawDefaultBackground();
} else if (!this.guiHidden) {
@@ -122,11 +135,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
hoveredElementCommentTimer = 0;
}
final float previousHoverTimer = this.guiButtonHoverTimer;
super.drawScreen(mouseX, mouseY, delta);
if (previousHoverTimer == this.guiButtonHoverTimer) {
this.guiButtonHoverTimer = 0.0f;
}
if (!this.guiHidden) {
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
@@ -143,12 +152,9 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
// Draw the comment panel
if (this.isDisplayingComment()) {
// Determine panel height and position
final int panelHeight = Math.max(50, 18 + (this.hoveredElementCommentBody.size() * 10));
int x = mouseX + 5;
if (x + 314 >= (this.width - 4)) x = this.width - (318);
int y = mouseY + 8;
if (y + panelHeight >= (this.height - 4)) y = this.height - (panelHeight + 4);
final int x = (int) (0.5 * this.width) - 157;
final int y = this.height - (panelHeight + 4);
// Draw panel
GuiUtil.drawPanel(x, y, COMMENT_PANEL_WIDTH, panelHeight);
// Draw text
@@ -181,10 +187,21 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
@Override
public void initGui() {
super.initGui();
BackendManager.RENDER_BACKEND.startFileDrop();
final int bottomCenter = this.width / 2 - 50;
final int topCenter = this.width / 2 - 76;
final boolean inWorld = this.mc.theWorld != null;
if (this.shaderPackList != null) {
try {
this.shaderPackList.close();
} catch (IOException e) {
Iris.logger.error("Failed to close previous shaderpack selection watcher!", e);
}
}
this.shaderPackList = new ShaderPackSelectionList(this, this.mc, this.width, this.height, 32, this.height - 58, 0, this.width);
if (Iris.getCurrentPack().isPresent() && this.navigation != null) {
@@ -339,12 +356,30 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
public void refreshScreenSwitchButton() {
if (this.screenSwitchButton != null) {
this.screenSwitchButton.displayString = optionMenuOpen ? I18n.format("options.iris.shaderPackList") : I18n.format("options.iris.shaderPackSettings");
this.screenSwitchButton.enabled = optionMenuOpen || shaderPackList.getTopButtonRow().shadersEnabled;
// Disable the settings switch when shaders are off or the selected pack exposes no options
this.screenSwitchButton.enabled = optionMenuOpen || (shaderPackList.getTopButtonRow().shadersEnabled
&& Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(true));
}
}
@Override
protected void keyTyped(char typedChar, int keyCode) {
if (GuiScreen.isCtrlKeyDown() && keyCode == Keyboard.KEY_D) {
this.mc.displayGuiScreen(new GuiYesNo((result, id) -> {
Iris.setDebug(result);
this.mc.displayGuiScreen(this);
}, "Shader debug mode toggle",
"Debug mode helps investigate problems and shows shader errors. Would you like to enable it?", 0));
return;
}
if (GuiScreen.isCtrlKeyDown() && keyCode == Keyboard.KEY_G) {
this.mc.displayGuiScreen(new GuiYesNo((result, id) -> {
Iris.setAllowUnknownShaders(result);
this.mc.displayGuiScreen(this);
}, "Unknown shader toggle",
"This allows unknown shaders to load in.", 0));
return;
}
if (keyCode == Keyboard.KEY_ESCAPE) {
if (this.guiHidden) {
this.guiHidden = false;
@@ -359,6 +394,21 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
return;
}
}
if (keyCode == Keyboard.KEY_F1 && this.mc.theWorld != null) {
this.guiHidden = !this.guiHidden;
this.initGui();
return;
}
if (keyCode == Keyboard.KEY_TAB) {
final boolean canOpenOptions = shaderPackList.getTopButtonRow().shadersEnabled
&& Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(false);
if (this.optionMenuOpen || canOpenOptions) {
this.optionMenuOpen = !this.optionMenuOpen;
this.applyChanges();
this.initGui();
}
return;
}
super.keyTyped(typedChar, keyCode);
}
@@ -367,6 +417,84 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
this.notificationDialogTimer = 100;
}
private void handleDroppedFiles() {
final List<String> dropped = BackendManager.RENDER_BACKEND.pollDroppedFiles();
if (dropped.isEmpty()) {
return;
}
final List<Path> paths = dropped.stream().map(Paths::get).collect(Collectors.toList());
if (this.optionMenuOpen) {
onOptionMenuFilesDrop(paths);
} else {
onPackListFilesDrop(paths);
}
}
public void onPackListFilesDrop(List<Path> paths) {
final List<Path> packs = paths.stream().filter(Iris::isValidShaderpack).toList();
for (Path pack : packs) {
final String fileName = pack.getFileName().toString();
try {
Iris.getShaderpacksDirectoryManager().copyPackIntoDirectory(fileName, pack);
} catch (FileAlreadyExistsException e) {
displayNotification(I18n.format("options.iris.shaderPackSelection.copyErrorAlreadyExists", fileName));
this.shaderPackList.refresh();
return;
} catch (IOException e) {
Iris.logger.warn("Error copying dragged shader pack", e);
displayNotification(I18n.format("options.iris.shaderPackSelection.copyError", fileName));
this.shaderPackList.refresh();
return;
}
}
// After copying, refresh the list so the new packs show up
this.shaderPackList.refresh();
if (packs.isEmpty()) {
if (paths.size() == 1) {
displayNotification(I18n.format("options.iris.shaderPackSelection.failedAddSingle", paths.getFirst().getFileName().toString()));
} else {
displayNotification(I18n.format("options.iris.shaderPackSelection.failedAdd"));
}
} else if (packs.size() == 1) {
final String packName = packs.getFirst().getFileName().toString();
displayNotification(I18n.format("options.iris.shaderPackSelection.addedPack", packName));
// Select the freshly-added pack, since the user probably wants to use it
this.shaderPackList.select(packName);
} else {
displayNotification(I18n.format("options.iris.shaderPackSelection.addedPacks", packs.size()));
}
}
public void onOptionMenuFilesDrop(List<Path> paths) {
// Only one settings file should be imported at a time
if (paths.size() != 1) {
displayNotification(I18n.format("options.iris.shaderPackOptions.tooManyFiles"));
return;
}
importPackOptions(paths.getFirst());
}
public void importPackOptions(Path settingFile) {
try (InputStream in = Files.newInputStream(settingFile)) {
final Properties properties = new Properties();
properties.load(in);
Iris.queueShaderPackOptionsFromProperties(properties);
displayNotification(I18n.format("options.iris.shaderPackOptions.importedSettings", settingFile.getFileName().toString()));
if (this.navigation != null) {
this.navigation.refresh();
}
} catch (Exception e) {
Iris.logger.error("Error importing shader settings file \"" + settingFile + "\"", e);
displayNotification(I18n.format("options.iris.shaderPackOptions.failedImport", settingFile.getFileName().toString()));
}
}
public void onClose() {
if (!dropChanges) {
applyChanges();
@@ -377,6 +505,19 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
this.mc.displayGuiScreen(parent);
}
@Override
public void onGuiClosed() {
BackendManager.RENDER_BACKEND.stopFileDrop();
if (this.shaderPackList != null) {
try {
this.shaderPackList.close();
} catch (IOException e) {
Iris.logger.error("Failed to safely close shaderpack selection!", e);
}
}
}
private void dropChangesAndClose() {
dropChanges = true;
onClose();
@@ -419,8 +560,29 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
CompletableFuture.runAsync(() -> openUri(Iris.getShaderpacksDirectoryManager().getDirectoryUri()));
}
public void openLinkConfirm(String url) {
this.mc.displayGuiScreen(new GuiConfirmOpenLink((result, id) -> {
if (result) {
try {
openUri(new URI(url));
} catch (URISyntaxException e) {
Iris.logger.error("Invalid shader download URL: " + url, e);
}
}
this.mc.displayGuiScreen(this);
}, url, 0, true) {
@Override
public void initGui() {
super.initGui();
for (int i = 0; i < this.buttonList.size(); i++) {
this.buttonList.get(i).xPosition = this.width / 2 - 155 + i * 105;
}
}
});
}
private void openUri(URI uri) {
switch (net.minecraft.util.Util.getOSType()) {
switch (Util.getOSType()) {
case OSX -> {
try {
Runtime.getRuntime().exec(new String[] { "/usr/bin/open", uri.toString() });
@@ -452,7 +614,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
try {
final Class<?> aClass = Class.forName("java.awt.Desktop");
final Object getDesktop = aClass.getMethod("getDesktop").invoke((Object) null);
final Object getDesktop = aClass.getMethod("getDesktop").invoke(null);
aClass.getMethod("browse", URI.class).invoke(getDesktop, uri);
} catch (Exception e) {
e.printStackTrace();
@@ -475,17 +637,17 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
this.hoveredElementCommentTitle = ((CommentedElementWidget<?>) widget).getCommentTitle();
Optional<String> commentBody = ((CommentedElementWidget<?>) widget).getCommentBody();
if (!commentBody.isPresent()) {
if (commentBody.isEmpty()) {
this.hoveredElementCommentBody.clear();
} else {
String rawCommentBody = commentBody.get();
// Strip any trailing "."s
// Strip any trailing periods
if (rawCommentBody.endsWith(".")) {
rawCommentBody = rawCommentBody.substring(0, rawCommentBody.length() - 1);
}
// Split comment body into lines by separator ". "
List<String> splitByPeriods = Arrays.stream(rawCommentBody.split("\\. [ ]*")).map(String::new).collect(Collectors.toList());
List<String> splitByPeriods = Arrays.stream(rawCommentBody.split("\\. +")).toList();
// Line wrap
this.hoveredElementCommentBody = new ArrayList<>();
for (String text : splitByPeriods) {
@@ -1597,6 +1597,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
this.customUniforms.update();
// Make sure SSBO is bound
if (ssboHolder != null) {
ssboHolder.setupBuffers();
}
// Get ready for world rendering
prepareRenderTargets();
@@ -1,7 +1,7 @@
package net.coderbot.iris.rendertarget;
import com.gtnewhorizons.angelica.compat.mojang.NativeImage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.lwjgl.opengl.GL11;
@@ -10,6 +10,11 @@ import java.util.Random;
public class NativeImageBackedNoiseTexture extends DynamicTexture {
public NativeImageBackedNoiseTexture(int size) {
super(create(size));
RenderSystem.texParameteri(getGlTextureId(), GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
RenderSystem.texParameteri(getGlTextureId(), GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
RenderSystem.texParameteri(getGlTextureId(), GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
RenderSystem.texParameteri(getGlTextureId(), GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
}
private static NativeImage create(int size) {
@@ -27,11 +32,4 @@ public class NativeImageBackedNoiseTexture extends DynamicTexture {
return image;
}
@Override
public void updateDynamicTexture() {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
super.updateDynamicTexture();
}
}
@@ -0,0 +1,21 @@
package net.coderbot.iris.shaderpack;
import java.util.HashMap;
import java.util.Map;
public class DimensionFlatteningMap {
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
static {
MODERN_TO_LEGACY.put("minecraft:overworld", "Overworld");
MODERN_TO_LEGACY.put("minecraft:the_nether", "Nether");
MODERN_TO_LEGACY.put("minecraft:the_end", "The End");
}
/**
* Maps a modern dimension ID to its 1.7.10 string name.
*/
public static String toLegacyName(String dimensionName) {
return MODERN_TO_LEGACY.getOrDefault(dimensionName, dimensionName);
}
}
@@ -10,6 +10,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -33,7 +34,7 @@ public class LanguageMap {
stream.filter(path -> !Files.isDirectory(path)).forEach(path -> {
// Also note that OptiFine uses a property scheme for loading language entries to keep parity with other
// OptiFine features
final String currentFileName = path.getFileName().toString();
final String currentFileName = path.getFileName().toString().toLowerCase(Locale.ROOT);
if (!currentFileName.endsWith(".lang")) {
// This file lacks a .lang file extension and should be ignored.
@@ -23,14 +23,12 @@ import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuContainer;
import net.coderbot.iris.shaderpack.option.values.MutableOptionValues;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import net.coderbot.iris.shaderpack.parsing.BooleanParser;
import net.coderbot.iris.shaderpack.preprocessor.JcppProcessor;
import net.coderbot.iris.shaderpack.preprocessor.PropertiesPreprocessor;
import net.coderbot.iris.shaderpack.texture.CustomTextureData;
import net.coderbot.iris.shaderpack.texture.TextureFilteringData;
import net.coderbot.iris.shaderpack.texture.TextureStage;
import net.coderbot.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.api.v0.IrisApi;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
@@ -242,12 +240,8 @@ public class ShaderPack {
List<String> invalidFeatureFlags = invalidFlagList.stream().map(FeatureFlags::getHumanReadableName).collect(Collectors.toList());
if (!invalidFeatureFlags.isEmpty()) {
// TODO: GUI
// if (Minecraft.getMinecraft().screen instanceof ShaderPackScreen) {
// Minecraft.getMinecraft().setScreen(new FeatureMissingErrorScreen(Minecraft.getMinecraft().screen, I18n.format("iris.unsupported.pack"), I18n.format("iris.unsupported.pack.description", FeatureFlags.getInvalidStatus(invalidFlagList), invalidFeatureFlags.stream()
// .collect(Collectors.joining(", ", ": ", ".")))));
// }
IrisApi.getInstance().getConfig().setShadersEnabledAndApply(false);
// TODO: GUI?
Iris.logger.warn("Shader pack requires unsupported feature flags, loading anyway: {}", String.join(", ", invalidFeatureFlags));
}
ProfileSet profiles = ProfileSet.fromTree(shaderProperties.getProfiles(), this.shaderPackOptions.getOptionSet());
@@ -258,9 +252,7 @@ public class ShaderPack {
this.profile.current.ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms));
// Add programs that are disabled by shader options
shaderProperties.getConditionallyEnabledPrograms().forEach((program, shaderOption) -> {
if ("true".equals(shaderOption)) return;
if ("false".equals(shaderOption) || !this.shaderPackOptions.getOptionValues().getBooleanValueOrDefault(shaderOption)) {
if (!BooleanParser.parse(shaderOption, this.shaderPackOptions.getOptionValues())) {
disabledPrograms.add(program);
}
});
@@ -438,7 +430,8 @@ public class ShaderPack {
folderNames.add(folderName);
for (String dimensionName : dimensionNames) {
dimensionMap.put(dimensionName, folderName);
// Convert modern dimension IDs to 1.7.10 names
dimensionMap.put(DimensionFlatteningMap.toLegacyName(dimensionName), folderName);
}
});
@@ -668,6 +661,7 @@ public class ShaderPack {
*/
public ProgramSet getProgramSet(String dimensionName) {
int dimensionId = Iris.getCurrentDimensionId();
dimensionName = DimensionFlatteningMap.toLegacyName(dimensionName);
// First, try to find an exact match in the dimension map
String folderName = dimensionMap.get(dimensionName);
@@ -373,17 +373,21 @@ public class FlatteningMap {
colorVariants("carpet", "carpet");
// === Double plant (ID 175) ===
// Bottom half has type in meta 0-5; top half is always meta 8 (shared by all types).
meta("sunflower", "double_plant", 0);
meta("lilac", "double_plant", 1);
meta("tall_grass", "double_plant", 2);
meta("large_fern", "double_plant", 3);
meta("rose_bush", "double_plant", 4);
meta("peony", "double_plant", 5);
// Bottom half carries the variant in meta 0-5; the top half's real meta is 8-11.
// We use a synthetic key to tie the variant the bottom is with the top half.
doublePlant("sunflower", 0);
doublePlant("lilac", 1);
doublePlant("tall_grass", 2);
doublePlant("large_fern", 3);
doublePlant("rose_bush", 4);
doublePlant("peony", 5);
// === Water / Lava (IDs 8-11) ===
// In 1.13+, water/lava are single blocks. In 1.7.10, flowing variants are separate.
// Map modern names to both still and flowing for full coverage.
// In 1.13+, water/lava are single blocks. In 1.7.10, flowing variants are separate.
// Map modern names to both still and flowing for full coverage. See IrisExtendedChunkVertexEncoder.java
// for more info on this.
liquidLevels("water", "water", "flowing_water");
liquidLevels("lava", "lava", "flowing_lava");
multi("water",
entry("water"),
entry("flowing_water"));
@@ -861,21 +865,6 @@ public class FlatteningMap {
state(WOOD_TYPES[i] + "_sapling", "stage", "0", entryMetas("sapling", i));
state(WOOD_TYPES[i] + "_sapling", "stage", "1", entryMetas("sapling", i + 8));
}
// === Double plant half (ID 175) ===
// 1.7.10: bottom half has type in meta 0-5; top half is always meta 8
state("sunflower", "half", "lower", entryMetas("double_plant", 0));
state("sunflower", "half", "upper", entryMetas("double_plant", 8));
state("lilac", "half", "lower", entryMetas("double_plant", 1));
state("lilac", "half", "upper", entryMetas("double_plant", 8));
state("tall_grass", "half", "lower", entryMetas("double_plant", 2));
state("tall_grass", "half", "upper", entryMetas("double_plant", 8));
state("large_fern", "half", "lower", entryMetas("double_plant", 3));
state("large_fern", "half", "upper", entryMetas("double_plant", 8));
state("rose_bush", "half", "lower", entryMetas("double_plant", 4));
state("rose_bush", "half", "upper", entryMetas("double_plant", 8));
state("peony", "half", "lower", entryMetas("double_plant", 5));
state("peony", "half", "upper", entryMetas("double_plant", 8));
}
/**
@@ -986,6 +975,13 @@ public class FlatteningMap {
STATE_MAPPINGS.put(modern + "|" + property + "=" + value, List.of(entries));
}
/** Maps each modern liquid level state to that exact metadata on both the static and flowing IDs */
private static void liquidLevels(String modern, String legacyStatic, String legacyDynamic) {
for (int level = 0; level <= 15; level++) {
state(modern, "level", String.valueOf(level), entryMetas(legacyStatic, level), entryMetas(legacyDynamic, level));
}
}
/** Creates a BlockEntry with no specific metas. */
private static BlockEntry entry(String legacyName) {
return new BlockEntry(new NamespacedId("minecraft", legacyName), Collections.emptySet());
@@ -1053,6 +1049,17 @@ public class FlatteningMap {
}
}
/**
* Double plant: the bottom half stores the variant in meta 0-5; the top half stores facing
* direction, so Angelica's chunk mesher rebuilds a synthetic key 0x8|variant for top halves.
*/
private static void doublePlant(String modern, int variant) {
final int upper = 0x8 | variant;
metas(modern, "double_plant", variant, upper);
state(modern, "half", "lower", entryMetas("double_plant", variant));
state(modern, "half", "upper", entryMetas("double_plant", upper));
}
// ==========================================
// Blockstate property mapping helpers
// ==========================================
@@ -11,6 +11,9 @@ public interface OptionValues {
Optional<String> getStringValue(String name);
default boolean getBooleanValueOrDefault(String name) {
if ("0".equals(name)) return false;
if ("1".equals(name)) return true;
return getBooleanValue(name).orElseGet(() -> {
if (!getOptionSet().getBooleanOptions().containsKey(name)) {
Iris.logger.warn("Tried to get boolean value for unknown option: " + name + ", defaulting to true!");
@@ -0,0 +1,124 @@
package net.coderbot.iris.shaderpack.parsing;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.option.values.OptionValues;
import java.util.EmptyStackException;
import java.util.Stack;
public class BooleanParser {
public static boolean parse(String expression, OptionValues valueLookup) {
try {
StringBuilder option = new StringBuilder();
Stack<Operation> operationStack = new Stack<>();
Stack<Boolean> valueStack = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
switch (c) {
case '!' -> operationStack.push(Operation.NOT);
case '&' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (operationStack.isEmpty() || !operationStack.peek().equals(Operation.AND)) {
operationStack.push(Operation.OPEN);
}
i++;
operationStack.push(Operation.AND);
}
case '|' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) {
evaluate(operationStack, valueStack, true);
}
i++;
operationStack.push(Operation.OR);
}
case '(' -> operationStack.push(Operation.OPEN);
case ')' -> {
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
option = new StringBuilder();
}
if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) {
evaluate(operationStack, valueStack, true);
}
evaluate(operationStack, valueStack, true);
}
case ' ' -> {}
default -> option.append(c);
}
}
if (!option.isEmpty()) {
valueStack.push(processValue(option.toString(), valueLookup, operationStack));
}
evaluate(operationStack, valueStack, false);
boolean result = valueStack.pop();
if (!valueStack.isEmpty() || !operationStack.isEmpty()) {
Iris.logger.warn("Failed to parse boolean expression, stack isn't empty, defaulting to true: '{}'", expression);
return true;
}
return result;
} catch (EmptyStackException e) {
Iris.logger.warn("Failed to parse boolean expression, stack is empty unexpectedly, defaulting to true: '{}'", expression);
return true;
}
}
private static boolean processValue(String value, OptionValues valueLookup, Stack<Operation> operationStack) {
boolean booleanValue = switch (value) {
case "true", "1" -> true;
case "false", "0" -> false;
default -> valueLookup != null && valueLookup.getBooleanValueOrDefault(value);
};
if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) {
operationStack.pop();
return !booleanValue;
}
return booleanValue;
}
private static void evaluate(Stack<Operation> operationStack, Stack<Boolean> valueStack, boolean currentBracket) {
boolean value = valueStack.pop();
while (!operationStack.isEmpty() && (!currentBracket || operationStack.peek() != Operation.OPEN)) {
value = operationStack.pop().compute(value, valueStack);
}
if (!operationStack.isEmpty() && operationStack.peek() == Operation.OPEN) {
operationStack.pop();
if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) {
value = operationStack.pop().compute(value, valueStack);
}
}
valueStack.push(value);
}
private enum Operation {
AND {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return valueStack.pop() && value;
}
},
OR {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return valueStack.pop() || value;
}
},
NOT {
@Override
boolean compute(boolean value, Stack<Boolean> valueStack) {
return !value;
}
},
OPEN;
boolean compute(boolean value, Stack<Boolean> valueStack) {
return value;
}
}
}
@@ -116,6 +116,7 @@ public final class CommonUniforms {
final SmoothedVec2f eyeBrightnessSmooth = new SmoothedVec2f(directives.getEyeBrightnessHalfLife(), directives.getEyeBrightnessHalfLife(), CommonUniforms::getEyeBrightness, updateNotifier);
uniforms
.uniform1f(ONCE, "pi", () -> (float) Math.PI)
.uniform1f(ONCE, "darknessFactor", () -> 0.0F) // This is PER_FRAME in modern, it is an effect added by The Warden. We're just setting to 0 because 1.7.10 doesn't have it.
.uniform1f(ONCE, "darknessLightFactor", () -> 0.0F) // Warden darkness current light factor - 1.7.10 doesn't have it so hardcode to 0
.uniform1b(PER_FRAME, "hideGUI", () -> client.gameSettings.hideGUI)
@@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
@@ -96,7 +97,7 @@ public class ItemMaterialHelper {
if (blockMetaMatches != null) {
Int2IntMap metaMap = blockMetaMatches.get(block);
if (metaMap != null) {
int id = metaMap.get(itemBlock.getMetadata(metadata));
int id = BlockMaterialMapping.resolveId(metaMap, itemBlock.getMetadata(metadata));
if (id != -1) {
return id;
}
@@ -72,6 +72,11 @@ public class VanillaBiomeList {
biomes.add(new BiomeEntry(BiomeGenBase.sky, "END_HIGHLANDS"));
biomes.add(new BiomeEntry(BiomeGenBase.sky, "END_BARRENS"));
// Modern biome variants (1.20+)
biomes.add(new BiomeEntry(BiomeGenBase.jungle, "BIOME_SPARSE_JUNGLE"));
biomes.add(new BiomeEntry(BiomeGenBase.mushroomIsland, "BIOME_MUSHROOM_FIELDS"));
biomes.add(new BiomeEntry(BiomeGenBase.swampland, "BIOME_MANGROVE_SWAMP"));
return Collections.unmodifiableList(biomes);
}
@@ -214,6 +214,8 @@ options.angelica.threadedChunkBuilding.tooltip=If enabled, chunk meshing will be
pack.iris.select.title=Select
pack.iris.configure.title=Configure
pack.iris.list.label=+ Drag and Drop Shader Packs to add
pack.iris.list.label.lwjgl2=Install lwjgl3ify to drag and drop Shader Packs
label.iris.true=On
label.iris.false=Off
@@ -0,0 +1,257 @@
options.button.shader=シェーダー...
options.chunks=%s チャンク
options.entityShadows=エンティティの影
options.droppedItems=%s アイテム
options.droppedItems.max=無制限
sodium.option_impact.low=低
sodium.option_impact.medium=中
sodium.option_impact.high=高
sodium.option_impact.extreme=極高
sodium.option_impact.varies=場合による
sodium.options.pages.quality=品質
sodium.options.pages.performance=パフォーマンス
sodium.options.pages.advanced=詳細
sodium.options.pages.appearance=外観
sodium.options.pages.text=文字エフェクト
sodium.options.view_distance.tooltip=地形をどれだけ遠くまで描画するかを設定します。描画距離が短いほど描画される地形が少なくなり、FPSが向上します。
sodium.options.simulation_distance.tooltip=エンティティが読み込まれ、処理される距離を設定します。演算距離を短くすることで内部サーバーの負荷が減り、FPSが向上します。
sodium.options.brightness.tooltip=ゲーム画面の明るさ(ガンマ)を設定します。
sodium.options.clouds.name=雲
sodium.options.clouds.tooltip=雲をどのように表示するか、無効にするかを設定します。
sodium.options.gui_scale.tooltip=GUIの大きさを設定します。「自動」では最大倍率が適用されます。
sodium.options.fullscreen.tooltip=有効にすると、ゲーム画面は(対応している場合)フルスクリーンで表示されます。
sodium.options.v_sync.tooltip=有効にすると、ゲームのFPSがモニターのリフレッシュレートに同期され、入力遅延が増加する代わりに、より滑らかになります。処理速度が遅いと、パフォーマンスが低下する可能性があります。
sodium.options.fps_limit.tooltip=最大FPSを制限し、マルチタスク時の負荷を軽減できます。垂直同期が有効になっている場合、この設定はモニターのリフレッシュレートよりも低い値でない限り無視されます。
sodium.options.view_bobbing.tooltip=有効にすると、移動時に視点が揺れます。画面酔いの症状が出る場合は、無効にすると症状が軽減される可能性があります。
sodium.options.graphics_quality.tooltip=一部の古い形式の設定を制御します。これはMODとの互換性のために必要です。以降の設定を「デフォルト」にすると、この設定が適用されます。
sodium.options.clouds_quality.tooltip=雲の品質を設定します。
sodium.options.weather_quality.tooltip=雨や雪のエフェクトの品質を設定します。
sodium.options.leaves_quality.name=木の葉の品質
sodium.options.leaves_quality.tooltip=木の葉の品質を設定します。
sodium.options.grass_quality.name=草ブロックの品質
sodium.options.grass_quality.tooltip=草ブロックの側面の品質を設定します。
sodium.options.celeritas_smooth_lighting.name=Celeritasスムースライティング
sodium.options.celeritas_smooth_lighting.tooltip=有効にすると、Celeritasの照明パイプラインがスムースライティングに使用されます。スムースライティングが有効である必要があります。
sodium.options.particle_quality.name=パーティクルの品質
sodium.options.particle_quality.tooltip=画面上に同時に存在できるパーティクルの最大数を設定します。
sodium.options.smooth_lighting.tooltip=ブロックの照明と陰影を滑らかにします。チャンクロードが少し遅くなりますが、FPSには影響しません。
sodium.options.biome_blend.value=%s ブロック
sodium.options.biome_blend.tooltip=ブロックの色情報を収集するバイオームの範囲を設定します。値を大きくすると、チャンクロードが大幅に遅くなる上に、あまり品質が向上しなくなります。
sodium.options.entity_shadows.tooltip=有効にすると、エンティティの下に影が表示されます。
sodium.options.vignette.name=ビネット
sodium.options.vignette.tooltip=有効にすると、ゲーム画面にビネット効果が適用されます。フィルレートが制限されていない限り、FPSに影響が出る可能性は非常に低いでしょう。
sodium.options.mipmap_levels.tooltip=ブロックのテクスチャに使用するミップマップの数を設定します。値が大きいほど遠くのブロックの見た目が良くなりますが、アニメーションが多いとパフォーマンスに悪影響を与える可能性があります。
sodium.options.use_chunk_multidraw.name=チャンクの同時描画
sodium.options.use_chunk_multidraw.tooltip=複数のチャンクを少ない回数で描画できます。CPU負荷を大幅に削減し、GPUの利用効率も向上する可能性があります。一部のグラフィックドライバで問題を引き起こす可能性があるため、不具合が発生する場合は無効にしてください。
sodium.options.use_vertex_objects.name=頂点配列オブジェクト
sodium.options.use_vertex_objects.tooltip=頂点の描画方法に関する情報をグラフィックドライバに移動することでパフォーマンスが向上し、同じオブジェクトの反復描画をより最適化できるようになります。互換性の無いMODを使用している場合を除き、通常はこの機能を無効にする必要はありません。
sodium.options.use_block_face_culling.name=ブロック表面カリング
sodium.options.use_block_face_culling.tooltip=有効にすると、画面に映るブロック面のみが描画対象となります。描画処理の初期段階で多数のブロック面が除外され、メモリ帯域幅とGPUの処理時間を節約できます。一部のリソースパックでは問題が発生する可能性があるため、ブロック面に穴が開く場合は無効にしてください。
sodium.options.use_compact_vertex_format.name=小さな頂点フォーマット
sodium.options.use_compact_vertex_format.tooltip=有効にすると、チャンク描画に小さな頂点フォーマットが使用されます。特に内臓グラフィックスの場合、VRAMの使用量と帯域幅を大幅に削減できますが、位置とテクスチャ座標属性の精度が低下するため、一部のリソースパックでZ-Fightingが発生する可能性があります。
sodium.options.use_fog_occlusion.name=霧による遮蔽
sodium.options.use_fog_occlusion.tooltip=有効にすると、霧に完全に隠れているチャンクは描画されなくなり、パフォーマンスが向上します。霧が強い水中などでより効果が高くなりますが、空と霧の間で見た目の異常が出る可能性があります。
sodium.options.use_entity_culling.name=エンティティカリング
sodium.options.use_entity_culling.tooltip=有効にすると、見える範囲に存在しないと判断されたエンティティが描画されなくなります。地下や壁の背後にあるエンティティの描画をしないことで、パフォーマンスが向上します。
sodium.options.use_particle_culling.name=パーティクルカリング
sodium.options.use_particle_culling.tooltip=有効にすると、見えると判断されたパーティクルのみが描画されるようになります。近くに多数のパーティクルがある場合にFPSが大幅に向上する可能性があります。
sodium.options.animate_only_visible_textures.name=見えるアニメーションのみ描画
sodium.options.animate_only_visible_textures.tooltip=有効にすると、表示可能と判断されたアニメーションのみが描画されます。特にリソースパックの負荷が高い場合、一部のハードウェアでFPSが大幅に向上する可能性があります。一部のアニメーションが動かないなどの問題が発生した場合は、この設定を無効にしてください。
sodium.options.translucency_sorting.name=半透明ブロックの整理
sodium.options.translucency_sorting.tooltip=有効にすると、半透明ブロックは奥から手前へと正しく並べ替えられ、特に複数の半透明ブロックが重なっている場合の品質が向上しますが、代わりに負荷が上昇します。
sodium.options.cpu_render_ahead_limit.name=CPUの先読み処理制限
sodium.options.cpu_render_ahead_limit.tooltip=CPUがGPUの描画完了まで待機できる最大フレーム数を設定します。極端に低い値、または高い値を設定すると、FPSが不安定になる可能性があります。
sodium.options.cpu_render_ahead_limit.value=%s フレーム
sodium.options.allow_direct_memory_access.name=ダイレクトメモリアクセス
sodium.options.allow_direct_memory_access.tooltip=有効にすると、一部の重要なコードパスでのパフォーマンス向上のために、メモリへの直接アクセスが許可されます。チャンクやエンティティの描画でのCPUの余分な負荷が大幅に削減される可能性が高まりますが、一部のバグやクラッシュの診断が難しくなる場合があります。誰かに指示された場合か、設定内容を十分に理解している場合にのみ無効にしてください。
sodium.options.ignore_driver_blacklist.name=ドライバブラックリストの無視
sodium.options.ignore_driver_blacklist.tooltip=有効にすると、ハードウェア/ドライバー構成との既知の非互換性が無視され、問題を引き起こす可能性のある設定を有効にできるようになります。何をしているのかを正確に理解していない限り、このオプションを変更しないでください。変更後に設定画面を保存して閉じ、再度開く必要があります。
sodium.options.enable_memory_tracing.name=メモリトレース
sodium.options.enable_memory_tracing.tooltip=デバッグ機能です。有効にすると、メモリリークが検出された際の診断情報を改善するために、メモリ割り当てと処理の呼び出し履歴が収集されます。
sodium.options.performance_impact_string=パフォーマンスへの影響: %s
sodium.options.use_persistent_mapping.name=永続的なマッピング
sodium.options.use_persistent_mapping.tooltip=有効にすると、チャンクとエンティティの描画を高速化するために、少量のメモリが永続的にマッピングされます。\n\nOpenGL4.4かARB_buffer_storageが必要です。
sodium.options.chunk_memory_allocator.name=チャンクメモリ割り当て
sodium.options.chunk_memory_allocator.tooltip=チャンク描画に使用するメモリ割り当て方法を設定します。\n- 非同期: 最速で、ほとんどの最新のグラフィックドライバで正常に動作します。\n- スワップ: 古いグラフィックドライバ向けの代替法です。メモリ使用量が大幅に増加する可能性があります。
sodium.options.chunk_memory_allocator.async=非同期
sodium.options.chunk_memory_allocator.swap=スワップ
sodium.options.chunk_update_threads.name=チャンク更新スレッド
sodium.options.chunk_update_threads.tooltip=チャンクロードに使用するCPUスレッド数を設定します。スレッド数を増やすと、チャンクロード速度が向上しますが、フレームタイムに悪影響を与える可能性があります。
sodium.options.always_defer_chunk_updates.name=常にチャンク更新を遅らせる
sodium.options.always_defer_chunk_updates.tooltip=有効にすると、重要なチャンクの更新完了を待たずに描画が実行されます。状況によってはFPSが大幅に向上する可能性がありますが、地形描画に著しい視覚的な遅延が発生する場合があります。
sodium.options.use_no_error_context.name=エラーの無いコンテキスト
sodium.options.use_no_error_context.tooltip=有効にすると、エラーチェックが無効になった状態でOpenGLコンテキストが作成されます。パフォーマンスが若干向上する可能性がありますが、OpenGLエラーを適切に処理できずにゲームがクラッシュする可能性も高まります。原因不明の突然のクラッシュが発生する場合は、この設定を無効にしてください。
sodium.options.async_occlusion_mode.name=非同期オクルージョン
sodium.options.async_occlusion_mode.tooltip=有効にすると、レンダリングパスで非同期オクルージョンカリングを使用します。「Only Shadow(デフォルト)」はシャドウパスのみで非同期オクルージョンを実行し、メインの描画リストでは同期を維持します。「Everything」は全てのパスを非同期で実行しますが、カメラの高速移動時に1フレームの可視遅延が発生する可能性があります。「None」は非同期オクルージョンを完全に無効化します。
sodium.options.use_occlusion_culling.name=オクルージョンカリング
sodium.options.use_occlusion_culling.tooltip=有効にすると、他のチャンクの後ろに隠れているチャンクは描画されません。無効にするとパフォーマンスが大幅に低下しますが、デバッグには役立ちます。
sodium.options.threads.value=%s スレッド
sodium.options.default=デフォルト
sodium.options.buttons.undo=戻す
sodium.options.buttons.apply=適用
sodium.options.buttons.donate=JellySquidに一杯のコーヒーを!
options.dynamic_fov=臨場感ある視野角
options.dynamic_fov.tooltip=有効にすると、ゲーム画面の視野角はプレイヤーの速度に応じて変化します。
options.framerate=%s FPS
iris.shaders.toggled=シェーダーを %s に切り替えました!
iris.shaders.disabled=シェーダーを無効化しました!
options.iris.apply=適用
options.iris.refresh=更新
options.iris.openShaderPackFolder=パックフォルダーを開く...
options.iris.shaderPackSettings=シェーダーの設定...
options.iris.shaderPackList=シェーダー一覧...
options.iris.refreshShaderPacks=シェーダーの更新
options.iris.shaderPackSelection=シェーダーパック...
options.iris.shaderPackSelection.title=シェーダーパック
options.iris.shaderPackSelection.addedPack=シェーダー「%s」を追加しました
options.iris.shaderPackSelection.addedPacks=%s個のシェーダーを追加しました。
options.iris.shaderPackSelection.failedAdd=無効なファイルです
options.iris.shaderPackSelection.failedAddSingle=「%s」は無効なシェーダーです。
options.iris.shaderPackSelection.copyError=シェーダー「%s」を追加できませんでした
options.iris.shaderPackSelection.copyErrorAlreadyExists=「%s」は既にパックフォルダーに存在します!
options.iris.shaderPackOptions.tooManyFiles=シェーダー設定ファイルは複数個インポートできません!
options.iris.shaderPackOptions.failedImport=シェーダー設定ファイル「%s」のインポートに失敗しました!
options.iris.shaderPackOptions.importedSettings=シェーダー設定を「%s」からインポートしました。
options.iris.shaders.disabled=シェーダー: OFF
options.iris.shaders.enabled=シェーダー: ON
options.iris.shaders.nonePresent=シェーダー: 未選択
options.iris.back=戻る
options.iris.reset=リセット
options.iris.reset.tooltip=全ての設定をリセットしますか?
options.iris.reset.tooltip.holdShift=Shiftキーを押しながらクリックでリセット
options.iris.importSettings.tooltip=ファイルから設定をインポート
options.iris.exportSettings.tooltip=設定をファイルへエクスポート
options.iris.mustDisableFullscreen=フルスクリーンを解除してください!
options.iris.setToDefault=この設定をリセットしますか?
options.iris.profile=プロファイル
options.iris.profile.custom=カスタム
options.iris.shadowDistance=影の最大描画距離
options.iris.shadowDistance.sodium_tooltip=影の最大描画距離
options.iris.shadowDistance.enabled=影が描画される最大の距離を設定できます。 指定された値より遠くの地形やエンティティには影が描画されません。 最大描画距離を下げることで、パフォーマンスが向上することがあります。
options.iris.shadowDistance.disabled=お使いのシェーダーパックでは既に最大描画距離が設定されているため、ここでは変更できません。
options.iris.gui.hide=GUIを非表示
options.iris.gui.show=GUIを表示
options.dynamic_lights=ダイナミックライト
options.dynamic_lights.tooltip=ダイナミックライトを有効にします。モード:OFF - 無効、FASTEST - 500ミリ秒の遅延、FAST - 250ミリ秒の遅延、FANCY - 50ミリ秒の遅延、REALTIME - 無遅延。
options.dynamic_lights_shader_force=ダイナミックライト - シェーダーに強制適用
options.dynamic_lights_shader_force.tooltip=有効 - シェーダーがONのときでもダイナミックライトを使用します、無効 - シェーダーがONのときはダイナミックライトを使用しません。
options.dynamic_lights_frustum_culling=フラスタムカリング
options.dynamic_lights_frustum_culling.tooltip=画面上の光源があるチャンクのみを更新します。GPU負荷が軽減されます。
options.dynamic_lights_adaptive_ticking=適応型ティック処理
options.dynamic_lights_adaptive_ticking.tooltip=遠方および画面外の光源の更新頻度を下げます。グローバルモード設定と併用できます。
options.dynamic_lights_slow_dist=遅い光源更新の距離
options.dynamic_lights_slow_dist.tooltip=光源の更新頻度が1/5となる距離(単位:ブロック)。推奨値:32ブロック(約2チャンク)。
options.dynamic_lights_slower_dist=より遅い光源更新の距離
options.dynamic_lights_slower_dist.tooltip=光源の更新頻度が1/10となる距離(単位:ブロック)。推奨値:64ブロック(約4チャンク)。
options.dynamic_lights_background_dist=バックグラウンド更新距離
options.dynamic_lights_background_dist.tooltip=光源の更新頻度が1/20(1秒に1回)となる距離(単位:ブロック)。見えない光源にも適用されます。
options.dynamic_lights_cull_timeout=カリングタイムアウト(ティック)
options.dynamic_lights_cull_timeout.tooltip=見えないチャンクが更新を強制されるまでに待機できる最大ティック数。値が小さいほど応答性は向上しますが、GPUの負荷が増加します。
options.dynamiclights.page=ダイナミックライト
options.dynamiclights.entities=エンティティの設定...
options.dynamiclights.entities.title=ダイナミックライト- エンティティ
options.dynamiclights.entities.info=ダイナミックライトを適用するエンティティの種類を設定します。一部の項目はMODとの互換性のために設定されています。
options.dynamiclights.entities.search=検索 (@mod, $on, $off)
options.dynamiclights.entity.tooltip=%sのエンティティのダイナミックライトを設定します。そのエンティティタイプが不要な光を発する場合は、パフォーマンス向上のために無効にしてください。
options.angelica.search.no_results=該当する設定が見つかりません
options.angelica.fontconfig=フォント設定...
options.angelica.fontconfig.title=フォント設定
options.angelica.fontconfig.currentfonts=使用フォント: §e%s§r 代替フォント: §b%s
options.angelica.fontconfig.searchprompt=§7§oフォントを検索...
options.angelica.fontconfig.testareaprompt=§7§oテストエリア
options.angelica.fontconfig.testareainfo=ここに文章を入力して、どのように表示されるかを確認できます。書式設定には「&」を使用してください。
options.angelica.fontconfig.testareaexample=&d&lへロブライン&rは&n此処には&7&m&o居なかった&r
options.angelica.fontconfig.disable_custom_font=カスタムフォントを無効化
options.angelica.fontconfig.enable_custom_font=カスタムフォントを有効化
options.angelica.fontconfig.reset_values=設定をリセット
options.angelica.fontconfig.no_fonts_warning=お使いのシステムではカスタムフォントを利用できません。詳細についてはログをご覧ください。
options.angelica.fontconfig.font_quality=カスタムフォントの品質: %s
options.angelica.fontconfig.shadow_offset=陰影のオフセット: %s
options.angelica.fontconfig.shadow_copies=陰影の数: %s
options.angelica.fontconfig.bold_copies=太字のコピーの数:%s
options.angelica.fontconfig.glyph_scale=文字の大きさ:%s
options.angelica.fontconfig.glyph_aspect=文字の縦横比:%s
options.angelica.fontconfig.whitespace_scale=空白の長さ:%s
options.angelica.fontconfig.glyph_spacing=文字間の長さ:%s
options.angelica.fontconfig.font_aa_strength=アンチエイリアシングの強度:%s
options.angelica.fontconfig.aamode.aa_none=アンチエイリアシング:オフ
options.angelica.fontconfig.aamode.aa_4x=アンチエイリアシング:4x MSAA
options.angelica.fontconfig.aamode.aa_16x=アンチエイリアシング:16x MSAA
options.angelica.fontconfig.custom_font_scale=カスタムフォントの大きさ:%s
options.angelica.fontconfig.shadow_offset_unicode=ユニコード陰影オフセット:%s
options.angelica.fontconfig.font_quality.tooltip=カスタムフォントの描画品質を向上させ、パフォーマンスとメモリ使用量を多少犠牲にする代わりに、ギザギザを軽減します。
options.angelica.fontconfig.shadow_offset.tooltip=フォントの陰影のオフセット。§l太字§rにも適用されます。
options.angelica.fontconfig.shadow_copies.tooltip=文字の陰影の数。すべてをオフセットすることで、擬似的な3D効果を生み出します。
options.angelica.fontconfig.bold_copies.tooltip=太字のコピーの数。細めのカスタムフォントで、太字の見栄えが良くなる場合があります。
options.angelica.fontconfig.glyph_scale.tooltip=各文字の大きさ。極端な値を設定すると、プレイに支障が出る可能性があります。
options.angelica.fontconfig.glyph_aspect.tooltip=各文字の縦横比。
options.angelica.fontconfig.whitespace_scale.tooltip=スペースなどの空白の拡大率。
options.angelica.fontconfig.glyph_spacing.tooltip=各文字の間隔。
options.angelica.fontconfig.font_aa_strength.tooltip=フォントのアンチエイリアシングに使用されるサンプル間隔。値が大きいとフォントがぼやけ、値が小さいとギザギザが発生する可能性があります。お使いの環境に最適な値を見つけてください。
options.angelica.fontconfig.aamode.tooltip=フォントのアンチエイリアシングモード。パフォーマンスを多少犠牲にする代わりに、拡大縮小された文字や、斜体の外観と読みやすさを大幅に向上させることができます。
options.angelica.fontconfig.custom_font_scale.tooltip=カスタムフォントの大きさ変更の値です。カスタムフォントの文字が、通常フォントの文字と比較して小さすぎるか、大きすぎる場合はこの値を調整してください。
options.angelica.fontconfig.shadow_offset_unicode.tooltip=Unicodeフォントにのみ適用される陰影オフセットの値です。Unicodeフォントの一部をMinecraft風のフォントに置き換えるリソースパックを使用している場合は、この値を1.0倍に設定してください。
options.angelica.hudCaching=HUDキャッシング
options.angelica.hudCaching.tooltip=HUDをフレームバッファにキャッシュし、フレーム間で再利用することで描画時の余計な負荷を削減します。起動時にHUDキャッシングを有効にする必要があります(実験的機能)。
options.angelica.droppedItemLimit=ドロップアイテムの描画数制限
options.angelica.droppedItemLimit.tooltip=描画されるドロップアイテムの最大数。値を小さくするとパフォーマンスが向上する可能性があります。
options.angelica.mobSpawnerRenderDistance=モブスポナーの描画距離
options.angelica.mobSpawnerRenderDistance.tooltip=モブスポナー内の回転するモブの描画距離
options.angelica.itemdisplaylistcount=アイテム描画キャッシュの制限
options.angelica.itemdisplaylistcount.tooltip=最適化されたアイテム描画でキャッシュするVBOの最大値。値を大きくするとパフォーマンスが大幅に向上しますが、VRAMの使用量が増える可能性があります。
options.angelica.threadedChunkBuilding=並列チャンク構築
options.angelica.threadedChunkBuilding.tooltip=有効にすると、チャンクメッシュ生成が複数のスレッドで並列実行され、チャンクロード速度が大幅に向上します。カスタムブロックレンダラーを使用するMODでレンダリングの問題が発生する場合は、無効にしてください。有効化には描画の再読み込みが必要です。
pack.iris.select.title=シェーダー選択
pack.iris.configure.title=シェーダー設定
pack.iris.list.label=+ ドラッグアンドドロップでシェーダーを追加
pack.iris.list.label.lwjgl2=ドラッグアンドドロップでシェーダーを追加するにはlwjgl3ifyをインストールしてください
label.iris.true=ON
label.iris.false=OFF
sodium.options.use_render_pass_optimization.name=レンダーパスの最適化
sodium.options.use_render_pass_optimization.tooltip=スプライトの透明度を分析し、スプライトがより負荷の高いレンダリングパスを必要としない場合、レンダリングパスを自動的にダウングレードします(例:半透明から切り抜き、切り抜きから不透明)。これにより、描画呼び出しと余計な描画が削減されます。
options.angelica.aggressiveChunkLoading=積極的チャンクロード
options.angelica.aggressiveChunkLoading.tooltip=隣接するチャンクの準備がまだ整っていなくても、チャンクが読み込まれるとすぐに描画します。描画距離の端での体感的なチャンクロード速度が向上します。隣接するチャンクが読み込まれると、チャンクは自動的に再構築されます。描画距離の短いサーバーで役立ちます。
options.angelica.disablef3=F3画面追加情報の無効化
options.angelica.disablef3.tooltip=Angelicaに関するF3画面の情報の表示を無効にします。
options.angelica.texteffects.rgb_colors=RGBカラー
options.angelica.texteffects.rgb_colors.tooltip=「%s」のHTMLカラーコードを有効にします。
options.angelica.texteffects.gradients=グラデーション
options.angelica.texteffects.gradients.tooltip=滑らかな色の移り変わり
options.angelica.texteffects.rainbow=虹色
options.angelica.texteffects.rainbow.tooltip=虹色のアニメーション
options.angelica.texteffects.wave_text=波打つ文字
options.angelica.texteffects.wave_text.tooltip=波打つ文字のアニメーション
options.angelica.texteffects.wave_amplitude=波の振幅
options.angelica.texteffects.wave_amplitude.tooltip=波打つ文字が跳ね返る距離(デフォルト:2.0)
options.angelica.texteffects.dinnerbone_text=文字の上下反転
options.angelica.texteffects.dinnerbone_text.tooltip=文字の上下反転の設定。
options.angelica.texteffects.drop_shadow=陰影
options.angelica.texteffects.drop_shadow.tooltip=区切りごとの陰影は「%s」、色付きの陰影は
options.angelica.texteffects.ampersand_conversion=アンパサンドの変換
options.angelica.texteffects.ampersand_conversion.tooltip=描画時に「%s」プレフィックスコードをフォーマットコードに変換します。
sodium.options.multidraw_mode.name=同時描画モード
sodium.options.multidraw_mode.tooltip=地形描画の命令の割り当て方法を設定します。§l直接的§r は glMultiDrawElementsBaseVertex を使用します (デフォルト、OpenGL 3.2 以降)。§l間接的§r はGPU間接レンダリング (GL 4.3 以降) を使用し、一部のハードウェアではCPUの余分な負荷を削減できます。§l独立的§r は個別の描画呼び出しをします。
sodium.options.multidraw_mode.direct=直接的
sodium.options.multidraw_mode.indirect=間接的
sodium.options.multidraw_mode.individual=独立的
sodium.options.upload_method.name=ストリーミングアップロード方式
sodium.options.upload_method.tooltip=データがGPUにストリーミングされる方式を変更します。一部の設定は、特定のドライバーでパフォーマンスの向上につながる場合があります。
sodium.options.enable_deferred_batching.name=遅延パーティクル同時処理
sodium.options.enable_deferred_batching.tooltip=パーティクルの描画呼び出しを、レイヤーごとに統合された描画にまとめて実行します。パーティクルが正しくレンダリングされない場合は、この設定を無効にしてください。
@@ -1,19 +1,9 @@
#version 330 core
uniform mat4 u_MVP;
uniform vec3 u_Min;
uniform vec3 u_Max;
layout(location = 0) in vec3 a_Position;
const vec3 edges[24] = vec3[24](
vec3(0,0,0), vec3(1,0,0), vec3(1,0,0), vec3(1,0,1),
vec3(1,0,1), vec3(0,0,1), vec3(0,0,1), vec3(0,0,0),
vec3(0,1,0), vec3(1,1,0), vec3(1,1,0), vec3(1,1,1),
vec3(1,1,1), vec3(0,1,1), vec3(0,1,1), vec3(0,1,0),
vec3(0,0,0), vec3(0,1,0), vec3(1,0,0), vec3(1,1,0),
vec3(1,0,1), vec3(1,1,1), vec3(0,0,1), vec3(0,1,1)
);
uniform mat4 u_MVP;
void main() {
vec3 pos = mix(u_Min, u_Max, edges[gl_VertexID]);
gl_Position = u_MVP * vec4(pos, 1.0);
gl_Position = u_MVP * vec4(a_Position, 1.0);
}
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff. Show more