mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a4d48964e | ||
|
|
231a2ab216 | ||
|
|
61bb779dea | ||
|
|
7cd97d1216 | ||
|
|
fe82cd320a | ||
|
|
c7a0f3a208 | ||
|
|
338d94197c | ||
|
|
75a0278138 |
No files matched your search
@@ -21,6 +21,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
@@ -106,6 +107,7 @@ public class GLContextState {
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public RetainedState retainedOwner;
|
||||
public int attribDepth = 0;
|
||||
|
||||
public boolean savesState(int id) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.TexImage2DCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.TexSubImage2DCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
@@ -49,6 +50,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
@@ -307,12 +309,19 @@ public class GLStateManager {
|
||||
return Thread.currentThread() == MainThread;
|
||||
}
|
||||
|
||||
public static void requireContext(String call) {
|
||||
if (Thread.currentThread() == MainThread || RENDER_BACKEND.hasContextOnThread()) return;
|
||||
throw new IllegalStateException(call + " on thread " + Thread.currentThread().getName() + ", which has no GL context");
|
||||
}
|
||||
|
||||
@Getter private static GLSMInitConfig initConfig;
|
||||
|
||||
public static ContextCapabilities capabilities;
|
||||
|
||||
// Software stack depths for FFP emulation
|
||||
public static final int MAX_ATTRIB_STACK_DEPTH = 32;
|
||||
public static final int RETAINED_SLOT = MAX_ATTRIB_STACK_DEPTH;
|
||||
public static final int STATE_SLOTS = MAX_ATTRIB_STACK_DEPTH + 1;
|
||||
public static final int MAX_MODELVIEW_STACK_DEPTH = 32 + 2;
|
||||
public static final int MAX_PROJECTION_STACK_DEPTH = 4;
|
||||
public static final int MAX_TEXTURE_STACK_DEPTH = 4;
|
||||
@@ -542,6 +551,7 @@ public class GLStateManager {
|
||||
final GLContextState glCtx = ctx();
|
||||
DisplayListManager.abortCompilation();
|
||||
glCtx.attribDepth = 0;
|
||||
glCtx.retainedOwner = null;
|
||||
for (int i = 0; i < glCtx.attribSets.length; i++) {
|
||||
glCtx.attribSets[i] = null;
|
||||
}
|
||||
@@ -1604,58 +1614,25 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glBlendFunc(int srcFactor, int dstFactor) {
|
||||
final GLContextState glCtx = ctx();
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final boolean snapshotted = snapshotVanillaBlendFunc();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isBlendLocked()) {
|
||||
mod(glCtx.blendState);
|
||||
bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
}
|
||||
final boolean caching = isCachingEnabled();
|
||||
tryBlendFuncSeparate(srcFactor, dstFactor, srcFactor, dstFactor);
|
||||
}
|
||||
|
||||
private static void issueBlendFunc(BlendStateStack blend) {
|
||||
if (GLSMConfig.hudCacheOverride) {
|
||||
mod(glCtx.blendState);
|
||||
glCtx.blendState.setAll(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
glCtx.blendState.clearFuncUnknownState();
|
||||
RENDER_BACKEND.blendFuncSeparate(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
|
||||
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
|
||||
GLSMHooks.blendFuncChangeEvent.dstRgb = dstFactor;
|
||||
GLSMHooks.blendFuncChangeEvent.srcAlpha = GL11.GL_ONE;
|
||||
GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
|
||||
}
|
||||
final boolean bypass = !caching;
|
||||
if (bypass
|
||||
|| glCtx.blendState.getSrcRgb() != srcFactor
|
||||
|| glCtx.blendState.getDstRgb() != dstFactor
|
||||
|| glCtx.blendState.getSrcAlpha() != srcFactor
|
||||
|| glCtx.blendState.getDstAlpha() != dstFactor
|
||||
|| glCtx.blendState.isFuncUnknown()) {
|
||||
mod(glCtx.blendState);
|
||||
glCtx.blendState.setAll(srcFactor, dstFactor, srcFactor, dstFactor);
|
||||
glCtx.blendState.clearFuncUnknownState();
|
||||
RENDER_BACKEND.blendFunc(srcFactor, dstFactor);
|
||||
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
|
||||
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
|
||||
GLSMHooks.blendFuncChangeEvent.dstRgb = dstFactor;
|
||||
GLSMHooks.blendFuncChangeEvent.srcAlpha = srcFactor;
|
||||
GLSMHooks.blendFuncChangeEvent.dstAlpha = dstFactor;
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
}
|
||||
|
||||
public static void setHudCacheOverride(boolean enabled) {
|
||||
if (GLSMConfig.hudCacheOverride == enabled) return;
|
||||
GLSMConfig.hudCacheOverride = enabled;
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isOverrideHeld()) return;
|
||||
final BlendStateStack blend = ctx().blendState;
|
||||
issueBlendFunc(blend);
|
||||
blend.clearFuncUnknownState();
|
||||
}
|
||||
|
||||
public static void glBlendEquation(int mode) {
|
||||
@@ -1707,22 +1684,19 @@ public class GLStateManager {
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
}
|
||||
if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) {
|
||||
srcAlpha = GL11.GL_ONE;
|
||||
dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
final boolean caching = isCachingEnabled();
|
||||
final boolean bypass = !caching;
|
||||
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha || glCtx.blendState.isFuncUnknown()) {
|
||||
mod(glCtx.blendState);
|
||||
glCtx.blendState.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
glCtx.blendState.clearFuncUnknownState();
|
||||
RENDER_BACKEND.blendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
issueBlendFunc(glCtx.blendState);
|
||||
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
|
||||
final boolean hudCache = GLSMConfig.hudCacheOverride;
|
||||
GLSMHooks.blendFuncChangeEvent.srcRgb = srcRgb;
|
||||
GLSMHooks.blendFuncChangeEvent.dstRgb = dstRgb;
|
||||
GLSMHooks.blendFuncChangeEvent.srcAlpha = srcAlpha;
|
||||
GLSMHooks.blendFuncChangeEvent.dstAlpha = dstAlpha;
|
||||
GLSMHooks.blendFuncChangeEvent.srcAlpha = hudCache ? GL11.GL_ONE : srcAlpha;
|
||||
GLSMHooks.blendFuncChangeEvent.dstAlpha = hudCache ? GL11.GL_ONE_MINUS_SRC_ALPHA : dstAlpha;
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
}
|
||||
@@ -2557,11 +2531,13 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glGenTextures() {
|
||||
requireContext("glGenTextures");
|
||||
flushDeferredTextureDeletes();
|
||||
return RENDER_BACKEND.genTextures();
|
||||
}
|
||||
|
||||
public static void glGenTextures(IntBuffer textures) {
|
||||
requireContext("glGenTextures");
|
||||
flushDeferredTextureDeletes();
|
||||
RENDER_BACKEND.genTextures(textures);
|
||||
}
|
||||
@@ -4177,6 +4153,89 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void retainModifiedState(int depth, RetainedState out) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if (depth < 0 || depth != glCtx.attribDepth - 1) {
|
||||
throw new IllegalStateException("retainModifiedState: depth " + depth + " is not the open push (attrib depth " + glCtx.attribDepth + ")");
|
||||
}
|
||||
glCtx.retainedOwner = out;
|
||||
final int count = glCtx.modifiedCount[depth];
|
||||
final int[] ids = glCtx.modifiedIds[depth];
|
||||
final CowStateStack<?>[] stackById = glCtx.stackById;
|
||||
out.ensureCapacity(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int id = ids[i];
|
||||
stackById[id].captureSlot(RETAINED_SLOT);
|
||||
out.ids[i] = id;
|
||||
}
|
||||
out.count = count;
|
||||
out.program = glCtx.activeProgram;
|
||||
out.programGen = programLifetimeGeneration;
|
||||
}
|
||||
|
||||
public static void applyRetainedState(RetainedState in) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if (glCtx.retainedOwner != in) return;
|
||||
final CowStateStack<?>[] stackById = glCtx.stackById;
|
||||
final byte[] kindById = glCtx.kindById;
|
||||
final int[] ids = in.ids;
|
||||
final int count = in.count;
|
||||
int first = 0;
|
||||
while (first < count && !CowDispatch.slotChanged(kindById[ids[first]], stackById[ids[first]], RETAINED_SLOT)) first++;
|
||||
if (first < count) applyRetainedChanges(glCtx, in, first);
|
||||
if (in.program != glCtx.activeProgram && (in.program == 0 || in.programGen == programLifetimeGeneration)) glUseProgram(in.program);
|
||||
}
|
||||
|
||||
private static void applyRetainedChanges(GLContextState glCtx, RetainedState in, int first) {
|
||||
beforeUncapturedStateChange();
|
||||
final boolean blendSnapshotted = snapshotVanillaBlendFunc();
|
||||
final int alphaFuncId = glCtx.alphaState.stackId();
|
||||
final int alphaTestId = glCtx.alphaTest.stackId();
|
||||
boolean alphaChanged = false;
|
||||
final boolean prevPoppingAttributes = glCtx.poppingAttributes;
|
||||
glCtx.poppingAttributes = true;
|
||||
try {
|
||||
final CowStateStack<?>[] stackById = glCtx.stackById;
|
||||
final int[] restoreBitById = glCtx.restoreBitById;
|
||||
final int[] restoreUnitById = glCtx.restoreUnitById;
|
||||
final byte[] kindById = glCtx.kindById;
|
||||
int changed = 0;
|
||||
long unitsChanged = 0L;
|
||||
for (int i = first; i < in.count; i++) {
|
||||
final int id = in.ids[i];
|
||||
final CowStateStack<?> cow = stackById[id];
|
||||
final byte kind = kindById[id];
|
||||
if (!CowDispatch.slotChanged(kind, cow, RETAINED_SLOT)) continue;
|
||||
cow.beforeModify();
|
||||
final int bit = restoreBitById[id];
|
||||
if (bit != 0) {
|
||||
changed |= bit;
|
||||
final int unit = restoreUnitById[id];
|
||||
if (unit >= 0) unitsChanged |= 1L << Math.min(unit, 63);
|
||||
}
|
||||
CowDispatch.restoreSlot(kind, cow, RETAINED_SLOT);
|
||||
if (id == alphaFuncId || id == alphaTestId) alphaChanged = true;
|
||||
}
|
||||
glCtx.restoreChangedMask = changed;
|
||||
glCtx.restoreUnitChangedMask = unitsChanged;
|
||||
issueRestoredBackendState(glCtx, -1);
|
||||
glCtx.lightingGeneration++;
|
||||
glCtx.fragmentGeneration++;
|
||||
glCtx.colorGeneration++;
|
||||
glCtx.ffp.normalGeneration++;
|
||||
glCtx.ffp.texCoordGeneration++;
|
||||
glCtx.dirtyColorAttrib = true;
|
||||
glCtx.dirtyNormalAttrib = true;
|
||||
glCtx.dirtyTexCoordAttrib = true;
|
||||
postVanillaBlendChangeIfMoved(blendSnapshotted);
|
||||
} finally {
|
||||
glCtx.poppingAttributes = prevPoppingAttributes;
|
||||
}
|
||||
if (alphaChanged && GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDepthColorOverridden() {
|
||||
final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler;
|
||||
return dch != null && dch.isOverrideHeld();
|
||||
@@ -4195,7 +4254,7 @@ public class GLStateManager {
|
||||
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
|
||||
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
|
||||
|
||||
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
|
||||
issueBlendFunc(glCtx.blendState);
|
||||
glCtx.blendState.clearFuncUnknownState();
|
||||
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
|
||||
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
|
||||
@@ -4256,12 +4315,7 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After popping GLSM stacks, apply restored state to the GL driver.
|
||||
*/
|
||||
private static void applyRestoredState(StateSet set, boolean blendSnapshotted) {
|
||||
final GLContextState glCtx = ctx();
|
||||
final int restore = set.restore;
|
||||
private static void issueRestoredBackendState(GLContextState glCtx, int restore) {
|
||||
final int changed = glCtx.restoreChangedMask;
|
||||
if ((restore & StateSet.R_DEPTH) != 0 && (changed & StateSet.R_DEPTH) != 0) {
|
||||
attribValueRestores++;
|
||||
@@ -4280,7 +4334,7 @@ public class GLStateManager {
|
||||
attribValueRestores++;
|
||||
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
|
||||
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
|
||||
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
|
||||
issueBlendFunc(blend);
|
||||
attribBackendCalls++;
|
||||
blend.clearFuncUnknownState();
|
||||
}
|
||||
@@ -4377,6 +4431,16 @@ public class GLStateManager {
|
||||
attribBackendCalls++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After popping GLSM stacks, apply restored state to the GL driver.
|
||||
*/
|
||||
private static void applyRestoredState(StateSet set, boolean blendSnapshotted) {
|
||||
final GLContextState glCtx = ctx();
|
||||
final int restore = set.restore;
|
||||
final int changed = glCtx.restoreChangedMask;
|
||||
issueRestoredBackendState(glCtx, restore);
|
||||
|
||||
final int bump = set.bump;
|
||||
final int depth = glCtx.attribDepth;
|
||||
@@ -5269,13 +5333,18 @@ public class GLStateManager {
|
||||
}
|
||||
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
|
||||
|
||||
public static int glGenSamplers() { return RENDER_BACKEND.genSamplers(); }
|
||||
public static int glGenSamplers() {
|
||||
requireContext("glGenSamplers");
|
||||
return RENDER_BACKEND.genSamplers();
|
||||
}
|
||||
|
||||
public static void glGenSamplers(IntBuffer samplers) {
|
||||
requireContext("glGenSamplers");
|
||||
for (int i = samplers.position(); i < samplers.limit(); i++) samplers.put(i, RENDER_BACKEND.genSamplers());
|
||||
}
|
||||
|
||||
public static void glGenSamplers(int[] samplers) {
|
||||
requireContext("glGenSamplers");
|
||||
for (int i = 0; i < samplers.length; i++) samplers[i] = RENDER_BACKEND.genSamplers();
|
||||
}
|
||||
|
||||
@@ -6692,26 +6761,32 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glGenBuffers() {
|
||||
requireContext("glGenBuffers");
|
||||
return RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
public static void glGenBuffers(IntBuffer buffers) {
|
||||
requireContext("glGenBuffers");
|
||||
for (int i = buffers.position(); i < buffers.limit(); i++) buffers.put(i, RENDER_BACKEND.genBuffers());
|
||||
}
|
||||
|
||||
public static void glGenBuffers(int[] buffers) {
|
||||
requireContext("glGenBuffers");
|
||||
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
public static int glCreateBuffers() {
|
||||
requireContext("glCreateBuffers");
|
||||
return RENDER_BACKEND.createBuffers();
|
||||
}
|
||||
|
||||
public static void glCreateBuffers(IntBuffer buffers) {
|
||||
requireContext("glCreateBuffers");
|
||||
for (int i = buffers.position(); i < buffers.limit(); i++) buffers.put(i, RENDER_BACKEND.createBuffers());
|
||||
}
|
||||
|
||||
public static void glCreateBuffers(int[] buffers) {
|
||||
requireContext("glCreateBuffers");
|
||||
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.createBuffers();
|
||||
}
|
||||
|
||||
@@ -7102,6 +7177,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glGenVertexArrays() {
|
||||
requireContext("glGenVertexArrays");
|
||||
return RENDER_BACKEND.genVertexArrays();
|
||||
}
|
||||
|
||||
@@ -7155,6 +7231,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glGenFramebuffers() {
|
||||
requireContext("glGenFramebuffers");
|
||||
return RENDER_BACKEND.genFramebuffers();
|
||||
}
|
||||
|
||||
@@ -7172,15 +7249,24 @@ public class GLStateManager {
|
||||
|
||||
public static void glFramebufferTexture(int target, int attachment, int texture, int level) { RENDER_BACKEND.framebufferTexture(target, attachment, texture, level); }
|
||||
|
||||
public static int glGenRenderbuffers() { return RENDER_BACKEND.genRenderbuffers(); }
|
||||
public static int glGenRenderbuffers() {
|
||||
requireContext("glGenRenderbuffers");
|
||||
return RENDER_BACKEND.genRenderbuffers();
|
||||
}
|
||||
public static void glDeleteRenderbuffers(int renderbuffer) { RENDER_BACKEND.deleteRenderbuffers(renderbuffer); }
|
||||
public static void glBindRenderbuffer(int target, int renderbuffer) { RENDER_BACKEND.bindRenderbuffer(target, renderbuffer); }
|
||||
public static void glRenderbufferStorage(int target, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorage(target, internalformat, width, height); }
|
||||
public static void glRenderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorageMultisample(target, samples, internalformat, width, height); }
|
||||
public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) { RENDER_BACKEND.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); }
|
||||
|
||||
public static void glGenQueries(IntBuffer ids) { RENDER_BACKEND.genQueries(ids); }
|
||||
public static int glGenQueries() { return RENDER_BACKEND.genQueries(); }
|
||||
public static void glGenQueries(IntBuffer ids) {
|
||||
requireContext("glGenQueries");
|
||||
RENDER_BACKEND.genQueries(ids);
|
||||
}
|
||||
public static int glGenQueries() {
|
||||
requireContext("glGenQueries");
|
||||
return RENDER_BACKEND.genQueries();
|
||||
}
|
||||
public static void glDeleteQueries(int id) { RENDER_BACKEND.deleteQueries(id); }
|
||||
public static void glBeginQuery(int target, int id) { RENDER_BACKEND.beginQuery(target, id); }
|
||||
public static void glEndQuery(int target) { RENDER_BACKEND.endQuery(target); }
|
||||
@@ -7779,6 +7865,9 @@ public class GLStateManager {
|
||||
if (ShaderManager.isEnabled()) {
|
||||
generateVertexShaderIfNeeded(program);
|
||||
}
|
||||
if (ProgramBinaryCache.isEnabled()) {
|
||||
ProgramBinaryCache.markRetrievable(program);
|
||||
}
|
||||
RENDER_BACKEND.linkProgram(program);
|
||||
if (ShaderManager.isEnabled() && RENDER_BACKEND.getProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
|
||||
LOGGER.warn("Program {} failed to link: {}", program, RENDER_BACKEND.getProgramInfoLog(program));
|
||||
@@ -7786,6 +7875,21 @@ public class GLStateManager {
|
||||
CompatUniformManager.onLinkProgram(program);
|
||||
}
|
||||
|
||||
public static void glProgramParameteri(int program, int pname, int value) {
|
||||
RENDER_BACKEND.programParameteri(program, pname, value);
|
||||
}
|
||||
|
||||
public static void glGetProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
|
||||
RENDER_BACKEND.getProgramBinary(program, length, binaryFormat, binary);
|
||||
}
|
||||
|
||||
public static void glProgramBinary(int program, int binaryFormat, ByteBuffer binary) {
|
||||
RENDER_BACKEND.programBinary(program, binaryFormat, binary);
|
||||
if (RENDER_BACKEND.getProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_TRUE) {
|
||||
CompatUniformManager.onLinkProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a program has a fragment shader but no vertex shader, generate a passthrough vertex shader from the fragment's in declarations and attach it.
|
||||
*/
|
||||
@@ -7860,6 +7964,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glCreateShader(int type) {
|
||||
requireContext("glCreateShader");
|
||||
return RENDER_BACKEND.createShader(type);
|
||||
}
|
||||
|
||||
@@ -7868,6 +7973,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glCreateProgram() {
|
||||
requireContext("glCreateProgram");
|
||||
final int program = RENDER_BACKEND.createProgram();
|
||||
programsPendingDeletion.remove(program);
|
||||
programLifetimeGeneration++;
|
||||
|
||||
@@ -48,6 +48,7 @@ public class RenderSystem {
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static boolean supportsProgramBinary;
|
||||
private static int maxImageUnits;
|
||||
@Getter private static int maxCombinedTextureImageUnits;
|
||||
private static int maxSSBOBindings;
|
||||
@@ -159,6 +160,9 @@ public class RenderSystem {
|
||||
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
||||
}
|
||||
|
||||
supportsProgramBinary = RENDER_BACKEND.supportsProgramBinary();
|
||||
GLStateManager.LOGGER.info("Program Binary: {}", supportsProgramBinary);
|
||||
|
||||
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
|
||||
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
|
||||
|
||||
@@ -611,6 +615,10 @@ public class RenderSystem {
|
||||
return supportsSamplerObjects;
|
||||
}
|
||||
|
||||
public static boolean supportsProgramBinary() {
|
||||
return supportsProgramBinary;
|
||||
}
|
||||
|
||||
public static int genSampler() {
|
||||
if (!supportsSamplerObjects) return 0;
|
||||
return GLStateManager.glGenSamplers();
|
||||
|
||||
@@ -107,9 +107,11 @@ public final class StateSet {
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0)
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
@@ -121,9 +123,11 @@ public final class StateSet {
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0)
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.AMDDebugOutput;
|
||||
import org.lwjgl.opengl.AMDDebugOutputCallback;
|
||||
@@ -23,6 +24,7 @@ import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL41;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
@@ -807,6 +809,28 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
return GL20.glIsProgram(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsProgramBinary() {
|
||||
final ContextCapabilities caps = GLStateManager.capabilities;
|
||||
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
|
||||
&& GL11.glGetInteger(GL41.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programParameteri(int program, int pname, int value) {
|
||||
GL41.glProgramParameteri(program, pname, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
|
||||
GL41.glGetProgramBinary(program, length, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
|
||||
GL41.glProgramBinary(program, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateProgram(int program) {
|
||||
GL20.glValidateProgram(program);
|
||||
|
||||
@@ -36,6 +36,9 @@ public abstract class RenderBackend {
|
||||
/** Returns true if the current thread has a valid render context. */
|
||||
public abstract boolean hasContext();
|
||||
|
||||
/** Returns true if the calling thread may issue render commands. */
|
||||
public boolean hasContextOnThread() { return hasContext(); }
|
||||
|
||||
/** Returns true if the calling thread owns the "GL Context" */
|
||||
public abstract boolean isCurrent();
|
||||
|
||||
@@ -504,6 +507,10 @@ public abstract class RenderBackend {
|
||||
public abstract int getUniformLocation(int program, ByteBuffer name);
|
||||
public boolean isShader(int obj) { return false; }
|
||||
public boolean isProgram(int obj) { return false; }
|
||||
public boolean supportsProgramBinary() { return false; }
|
||||
public void programParameteri(int program, int pname, int value) {}
|
||||
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {}
|
||||
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {}
|
||||
public void validateProgram(int program) {}
|
||||
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
|
||||
if (count != null) count.put(0, 0);
|
||||
|
||||
@@ -133,11 +133,13 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public int createFramebuffer() {
|
||||
GLStateManager.requireContext("glCreateFramebuffers");
|
||||
return RENDER_BACKEND.createFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
GLStateManager.requireContext("glCreateTextures");
|
||||
return RENDER_BACKEND.createTextures(target);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
final int texture = RENDER_BACKEND.genTextures();
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
+25
-13
@@ -54,9 +54,17 @@ public final class GeometryShaderGenerator {
|
||||
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
|
||||
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
|
||||
|
||||
sb.append(" float d0 = c0.z + c0.w;\n");
|
||||
sb.append(" float d1 = c1.z + c1.w;\n");
|
||||
sb.append(" if (d0 < 0.0 && d1 < 0.0) return;\n");
|
||||
sb.append(" float t0 = d0 < 0.0 ? d0 / (d0 - d1) : 0.0;\n");
|
||||
sb.append(" float t1 = d1 < 0.0 ? d0 / (d0 - d1) : 1.0;\n");
|
||||
sb.append(" vec4 p0 = mix(c0, c1, t0);\n");
|
||||
sb.append(" vec4 p1 = mix(c0, c1, t1);\n\n");
|
||||
|
||||
sb.append(" // NDC positions\n");
|
||||
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
|
||||
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
|
||||
sb.append(" vec2 n0 = p0.xy / p0.w;\n");
|
||||
sb.append(" vec2 n1 = p1.xy / p1.w;\n\n");
|
||||
|
||||
sb.append(" // Screen-space direction and perpendicular\n");
|
||||
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
|
||||
@@ -74,22 +82,22 @@ public final class GeometryShaderGenerator {
|
||||
}
|
||||
|
||||
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
|
||||
final String ci = "c" + endpointIdx;
|
||||
final String pi = "p" + endpointIdx;
|
||||
final String ni = "n" + endpointIdx;
|
||||
final String idx = "[" + endpointIdx + "]";
|
||||
final String ti = "t" + endpointIdx;
|
||||
|
||||
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
|
||||
|
||||
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
|
||||
sb.append(" v_Color = ").append(lerp("v_Color_gs", ti)).append(";\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
|
||||
sb.append(" v_SpecularColor = ").append(lerp("v_SpecularColor_gs", ti)).append(";\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
|
||||
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = ").append(lerp("v_TexCoord0_gs", ti)).append(";\n");
|
||||
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = ").append(lerp("v_TexCoord1_gs", ti)).append(";\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = ").append(lerp("v_TexCoord2_gs", ti)).append(";\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = ").append(lerp("v_TexCoord3_gs", ti)).append(";\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
|
||||
sb.append(" v_FogCoord = ").append(lerp("v_FogCoord_gs", ti)).append(";\n");
|
||||
}
|
||||
if (key.lineStipple()) {
|
||||
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
|
||||
@@ -97,11 +105,15 @@ public final class GeometryShaderGenerator {
|
||||
|
||||
if (key.clipPlanesEnabled()) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = mix(gl_in[0].gl_ClipDistance[").append(i).append("], gl_in[1].gl_ClipDistance[").append(i).append("], ").append(ti).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
|
||||
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(pi).append(".w, ").append(pi).append(".z, ").append(pi).append(".w);\n");
|
||||
sb.append(" EmitVertex();\n\n");
|
||||
}
|
||||
|
||||
private static String lerp(String input, String t) {
|
||||
return "mix(" + input + "[0], " + input + "[1], " + t + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.gtnewhorizons.angelica.glsm.shader;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL41;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Saves linked GL programs to disk and loads them back with glProgramBinary, skipping compile and link.
|
||||
*/
|
||||
public final class ProgramBinaryCache {
|
||||
private static final Logger LOGGER = LogManager.getLogger("ProgramBinaryCache");
|
||||
static final String LAYER = "gl-program";
|
||||
private static final int PACKS_KEPT = 3;
|
||||
|
||||
private static volatile boolean enabled;
|
||||
private static volatile String packLayer;
|
||||
private static String driverId;
|
||||
private static String fullCompileLayer;
|
||||
private static Set<String> keysInUse;
|
||||
private static List<CompletableFuture<Boolean>> fullCompileWrites;
|
||||
private static Object fullCompileGeneration;
|
||||
private static final AtomicBoolean rejectWarned = new AtomicBoolean();
|
||||
private static final Object WRITE_LOCK = new Object();
|
||||
private static final Map<String, Object> compileGenerations = new HashMap<>();
|
||||
private static int deleteCount;
|
||||
|
||||
private ProgramBinaryCache() {}
|
||||
|
||||
public static void setEnabled(boolean value) {
|
||||
enabled = value;
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return enabled && packLayer != null && RenderSystem.supportsProgramBinary() && ShaderDiskCache.isEnabled();
|
||||
}
|
||||
|
||||
public static void usePack(String packName) {
|
||||
if (packName == null) {
|
||||
packLayer = null;
|
||||
return;
|
||||
}
|
||||
final String layer = LAYER + '/' + folderName(packName);
|
||||
packLayer = layer;
|
||||
if (!enabled) return;
|
||||
ShaderDiskCache.touchLayer(layer);
|
||||
AngelicaWorkers.run(() -> ShaderDiskCache.keepNewestSubLayers(LAYER, PACKS_KEPT));
|
||||
}
|
||||
|
||||
public static Key key() {
|
||||
return new Key(ShaderDiskCache.key(packLayer).str(driverId()).b(ShaderManager.isEnabled()));
|
||||
}
|
||||
|
||||
public static void markRetrievable(int program) {
|
||||
GLStateManager.glProgramParameteri(program, GL41.GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL11.GL_TRUE);
|
||||
}
|
||||
|
||||
public static int load(Key key) {
|
||||
final ShaderDiskCache.Key diskKey = key.diskKey;
|
||||
final ShaderDiskCache.Blob blob = ShaderDiskCache.getBlob(diskKey);
|
||||
if (blob == null) return 0;
|
||||
final int format;
|
||||
try {
|
||||
format = Integer.parseInt(blob.tag());
|
||||
} catch (NumberFormatException e) {
|
||||
ShaderDiskCache.remove(diskKey);
|
||||
return 0;
|
||||
}
|
||||
final int program = GLStateManager.glCreateProgram();
|
||||
final ByteBuffer data = MemoryUtilities.memAlloc(blob.data().length);
|
||||
try {
|
||||
data.put(blob.data()).flip();
|
||||
GLStateManager.glProgramBinary(program, format, data);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(data);
|
||||
}
|
||||
if (GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
ShaderDiskCache.remove(diskKey);
|
||||
if (rejectWarned.compareAndSet(false, true)) LOGGER.error("Driver rejected a saved shader program!! Recompiling rejected programs from source");
|
||||
return 0;
|
||||
}
|
||||
keep(diskKey);
|
||||
return program;
|
||||
}
|
||||
|
||||
public static void save(Key key, int program) {
|
||||
final int length = GLStateManager.glGetProgrami(program, GL41.GL_PROGRAM_BINARY_LENGTH);
|
||||
if (length <= 0) return;
|
||||
final ByteBuffer data = MemoryUtilities.memAlloc(length);
|
||||
final IntBuffer written = MemoryUtilities.memAllocInt(1);
|
||||
final IntBuffer format = MemoryUtilities.memAllocInt(1);
|
||||
try {
|
||||
GLStateManager.glGetProgramBinary(program, written, format, data);
|
||||
if (written.get(0) <= 0) return;
|
||||
data.limit(written.get(0));
|
||||
final byte[] binary = ShaderCacheIO.toHeap(data);
|
||||
final String formatTag = Integer.toString(format.get(0));
|
||||
final ShaderDiskCache.Key diskKey = key.diskKey;
|
||||
keep(diskKey);
|
||||
final int deletesBefore;
|
||||
synchronized (WRITE_LOCK) {
|
||||
deletesBefore = deleteCount;
|
||||
}
|
||||
final CompletableFuture<Boolean> write = AngelicaWorkers.submit(() -> {
|
||||
synchronized (WRITE_LOCK) {
|
||||
return deletesBefore == deleteCount && ShaderDiskCache.putBlob(diskKey, formatTag, binary);
|
||||
}
|
||||
});
|
||||
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
|
||||
if (writes != null) writes.add(write);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(data);
|
||||
MemoryUtilities.memFree(written);
|
||||
MemoryUtilities.memFree(format);
|
||||
}
|
||||
}
|
||||
|
||||
public static void beginFullCompile() {
|
||||
fullCompileLayer = packLayer;
|
||||
keysInUse = new HashSet<>();
|
||||
fullCompileWrites = new ArrayList<>();
|
||||
synchronized (WRITE_LOCK) {
|
||||
fullCompileGeneration = new Object();
|
||||
if (fullCompileLayer != null) compileGenerations.put(fullCompileLayer, fullCompileGeneration);
|
||||
}
|
||||
}
|
||||
public static void finishFullCompile(@Nullable String completedSettings) {
|
||||
final String layer = fullCompileLayer;
|
||||
final Set<String> used = keysInUse;
|
||||
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
|
||||
final Object generation = fullCompileGeneration;
|
||||
fullCompileLayer = null;
|
||||
keysInUse = null;
|
||||
fullCompileWrites = null;
|
||||
fullCompileGeneration = null;
|
||||
if (layer == null || used == null || writes == null) return;
|
||||
|
||||
final ShaderDiskCache.Key marker = completedSettings != null ? compiledSettingsKey(layer, completedSettings) : null;
|
||||
final String markerHex = marker != null ? marker.hex() : null;
|
||||
CompletableFuture.allOf(writes.toArray(new CompletableFuture<?>[0])).whenComplete((ignored, failure) -> {
|
||||
synchronized (WRITE_LOCK) {
|
||||
if (compileGenerations.get(layer) != generation) return;
|
||||
if (marker == null || failure != null) return;
|
||||
for (CompletableFuture<Boolean> write : writes) {
|
||||
if (!write.join()) return;
|
||||
}
|
||||
ShaderDiskCache.retainLayer(layer, used);
|
||||
ShaderDiskCache.retainLayer(layer + "/settings", Collections.singleton(markerHex));
|
||||
ShaderDiskCache.putString(marker, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void deleteAll() {
|
||||
synchronized (WRITE_LOCK) {
|
||||
deleteCount++;
|
||||
compileGenerations.clear();
|
||||
ShaderDiskCache.deleteLayer(LAYER);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean wasCompiledFor(String settings) {
|
||||
final String layer = packLayer;
|
||||
return layer != null && ShaderDiskCache.getString(compiledSettingsKey(layer, settings)) != null;
|
||||
}
|
||||
|
||||
private static ShaderDiskCache.Key compiledSettingsKey(String layer, String settings) {
|
||||
return ShaderDiskCache.key(layer + "/settings").str(driverId()).b(ShaderManager.isEnabled()).str(settings);
|
||||
}
|
||||
|
||||
private static String folderName(String packName) {
|
||||
final StringBuilder name = new StringBuilder(Math.min(packName.length(), 64));
|
||||
for (int i = 0; i < packName.length() && name.length() < 64; i++) {
|
||||
final char c = packName.charAt(i);
|
||||
name.append(c < 128 && (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_') ? c : '_');
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
private static void keep(ShaderDiskCache.Key diskKey) {
|
||||
final String hex = diskKey.hex();
|
||||
final Set<String> used = keysInUse;
|
||||
if (used != null) used.add(hex);
|
||||
}
|
||||
|
||||
private static String driverId() {
|
||||
String id = driverId;
|
||||
if (id == null) {
|
||||
id = RENDER_BACKEND.getString(GL11.GL_VENDOR) + '|' + RENDER_BACKEND.getString(GL11.GL_RENDERER) + '|'
|
||||
+ RENDER_BACKEND.getString(GL11.GL_VERSION) + '|' + RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION) + '|'
|
||||
+ RenderSystem.isGLES() + '|' + RENDER_BACKEND.getMinGLSLVersion();
|
||||
driverId = id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static final class Key {
|
||||
private final ShaderDiskCache.Key diskKey;
|
||||
|
||||
private Key(ShaderDiskCache.Key diskKey) {
|
||||
this.diskKey = diskKey;
|
||||
}
|
||||
|
||||
public Key stage(int shaderType, String source) {
|
||||
diskKey.str("stage").i(shaderType).str(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Key attribute(String name, int index) {
|
||||
diskKey.str("attribute").str(name).i(index);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,18 @@ import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
@@ -116,9 +120,9 @@ public final class ShaderDiskCache {
|
||||
}
|
||||
}
|
||||
|
||||
public static void put(Key key, byte[] payload) {
|
||||
public static boolean put(Key key, byte[] payload) {
|
||||
final Path r = root;
|
||||
if (r == null) return;
|
||||
if (r == null) return false;
|
||||
final Path target = key.path(r);
|
||||
final Path tmp = target.resolveSibling(key.hex() + '.' + TMP_SEQ.getAndIncrement() + ".tmp");
|
||||
try {
|
||||
@@ -129,11 +133,13 @@ public final class ShaderDiskCache {
|
||||
write(tmp, payload);
|
||||
}
|
||||
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
Files.deleteIfExists(tmp);
|
||||
} catch (IOException ignored) {}
|
||||
if (writeWarned.compareAndSet(false, true)) LOGGER.warn("Shader disk cache write failed for {}", target, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +208,7 @@ public final class ShaderDiskCache {
|
||||
}
|
||||
}
|
||||
|
||||
public static void putBlob(Key key, String tag, byte[] data) {
|
||||
public static boolean putBlob(Key key, String tag, byte[] data) {
|
||||
final ByteArrayOutputStream bytes = new ByteArrayOutputStream(data.length + 64);
|
||||
try (DataOutputStream out = new DataOutputStream(bytes)) {
|
||||
writeStr(out, tag);
|
||||
@@ -210,11 +216,101 @@ public final class ShaderDiskCache {
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
put(key, bytes.toByteArray());
|
||||
return put(key, bytes.toByteArray());
|
||||
}
|
||||
|
||||
public record Blob(String tag, byte[] data) {}
|
||||
|
||||
public static void remove(Key key) {
|
||||
final Path r = root;
|
||||
if (r == null) return;
|
||||
try {
|
||||
Files.deleteIfExists(key.path(r));
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public static void retainLayer(String layer, Set<String> keep) {
|
||||
final Path r = root;
|
||||
if (r == null) return;
|
||||
final Path dir = r.resolve(layer);
|
||||
if (!Files.isDirectory(dir)) return;
|
||||
final List<Path> files = new ArrayList<>();
|
||||
try (Stream<Path> list = Files.list(dir)) {
|
||||
list.forEach(files::add);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Shader disk cache could not list {}", dir, e);
|
||||
return;
|
||||
}
|
||||
for (Path p : files) {
|
||||
final String name = p.getFileName().toString();
|
||||
if (!name.endsWith(".bin")) continue; // in-flight writes are still .tmp
|
||||
if (keep != null && keep.contains(name.substring(0, name.length() - 4))) continue;
|
||||
try {
|
||||
Files.deleteIfExists(p);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static void touchLayer(String layer) {
|
||||
final Path r = root;
|
||||
if (r == null) return;
|
||||
final Path dir = r.resolve(layer);
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
Files.setLastModifiedTime(dir, FileTime.fromMillis(System.currentTimeMillis()));
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Shader disk cache could not mark {} as used", dir, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void keepNewestSubLayers(String layer, int keep) {
|
||||
final Path r = root;
|
||||
if (r == null) return;
|
||||
final Path dir = r.resolve(layer);
|
||||
if (!Files.isDirectory(dir)) return;
|
||||
final List<Path> subLayers = new ArrayList<>();
|
||||
try (Stream<Path> list = Files.list(dir)) {
|
||||
list.filter(Files::isDirectory).forEach(subLayers::add);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Shader disk cache could not list {}", dir, e);
|
||||
return;
|
||||
}
|
||||
subLayers.sort(Comparator.comparingLong(ShaderDiskCache::lastModifiedMillis).reversed());
|
||||
for (int i = keep; i < subLayers.size(); i++) {
|
||||
deleteRecursively(subLayers.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteLayer(String layer) {
|
||||
final Path r = root;
|
||||
if (r != null) deleteRecursively(r.resolve(layer));
|
||||
}
|
||||
|
||||
private static long lastModifiedMillis(Path path) {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path).toMillis();
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteRecursively(Path dir) {
|
||||
if (!Files.exists(dir)) return;
|
||||
final List<Path> paths = new ArrayList<>();
|
||||
try (Stream<Path> walk = Files.walk(dir)) {
|
||||
walk.forEach(paths::add);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Shader disk cache could not list {}", dir, e);
|
||||
return;
|
||||
}
|
||||
Collections.reverse(paths);
|
||||
for (Path p : paths) {
|
||||
try {
|
||||
Files.deleteIfExists(p);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
static void enforceCap(Path dir, long maxBytes) {
|
||||
try {
|
||||
final long[] total = {0};
|
||||
|
||||
@@ -14,8 +14,8 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
|
||||
|
||||
public AlphaStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new AlphaState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new AlphaState();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,11 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
|
||||
@@ -17,8 +17,8 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
|
||||
|
||||
public BlendStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new BlendState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new BlendState();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return funcUnknown || VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
|
||||
@@ -44,7 +44,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
|
||||
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
|
||||
super(glCap, ffpStateOnly);
|
||||
this.enabled = initialState;
|
||||
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
stack = new boolean[GLStateManager.STATE_SLOTS];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,6 +52,11 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return stateUnknown || (vanillaLayer != null && vanillaLayer.isOverrideHeld()) || enabled != stack[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int slot) {
|
||||
stack[slot] = vanillaValue();
|
||||
|
||||
@@ -10,8 +10,8 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
|
||||
|
||||
public Color4Stack(int id) {
|
||||
cow.id = id;
|
||||
stack = new Color4[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new Color4[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new Color4();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,11 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -15,8 +15,8 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
|
||||
|
||||
public ColorMaskStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new ColorMask[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new ColorMask();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,11 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
|
||||
@@ -101,6 +101,36 @@ public final class CowDispatch {
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean slotChanged(byte kind, CowStateStack<?> s, int slot) {
|
||||
return switch (kind) {
|
||||
case BLEND -> ((BlendStateStack) s).slotChanged(slot);
|
||||
case BOOLEAN -> ((BooleanStateStack) s).slotChanged(slot);
|
||||
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).slotChanged(slot);
|
||||
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).slotChanged(slot);
|
||||
case COLOR_MASK -> ((ColorMaskStack) s).slotChanged(slot);
|
||||
case LIGHT_STATE -> ((LightStateStack) s).slotChanged(slot);
|
||||
case MATERIAL -> ((MaterialStateStack) s).slotChanged(slot);
|
||||
case STENCIL -> ((StencilStateStack) s).slotChanged(slot);
|
||||
case MATRIX_MODE -> ((MatrixModeStack) s).slotChanged(slot);
|
||||
case LIGHT_MODEL -> ((LightModelStateStack) s).slotChanged(slot);
|
||||
case ALPHA -> ((AlphaStateStack) s).slotChanged(slot);
|
||||
case POINT -> ((PointStateStack) s).slotChanged(slot);
|
||||
case FOG -> ((FogStateStack) s).slotChanged(slot);
|
||||
case VEC4F -> ((Vec4fStack) s).slotChanged(slot);
|
||||
case VEC3F -> ((Vec3fStack) s).slotChanged(slot);
|
||||
case INTEGER -> ((IntegerStateStack) s).slotChanged(slot);
|
||||
case DEPTH -> ((DepthStateStack) s).slotChanged(slot);
|
||||
case VIEWPORT -> ((ViewPortStateStack) s).slotChanged(slot);
|
||||
case POLYGON -> ((PolygonStateStack) s).slotChanged(slot);
|
||||
case COLOR4 -> ((Color4Stack) s).slotChanged(slot);
|
||||
case LINE -> ((LineStateStack) s).slotChanged(slot);
|
||||
case TEXTURE_BINDING -> ((TextureBindingStack) s).slotChanged(slot);
|
||||
case TEX_ENV -> ((TexEnvState) s).slotChanged(slot);
|
||||
case SCISSOR -> ((ScissorStateStack) s).slotChanged(slot);
|
||||
default -> throw new IllegalStateException("Unknown kind: " + kind);
|
||||
};
|
||||
}
|
||||
|
||||
public static void restoreSlot(byte kind, CowStateStack<?> s, int slot) {
|
||||
switch (kind) {
|
||||
case BLEND -> ((BlendStateStack) s).restoreSlot(slot);
|
||||
|
||||
@@ -20,6 +20,8 @@ public interface CowStateStack<T> extends IStateStack<T> {
|
||||
|
||||
void restoreSlot(int slot);
|
||||
|
||||
boolean slotChanged(int slot);
|
||||
|
||||
default int stackId() {
|
||||
return cowDepths().id;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
|
||||
|
||||
public DepthStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new DepthState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new DepthState();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
|
||||
@@ -10,8 +10,8 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
|
||||
|
||||
public FogStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new FogState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new FogState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new FogState();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
|
||||
public IntegerStateStack(int val, int id) {
|
||||
cow.id = id;
|
||||
setValue(val);
|
||||
stack = new IntegerState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new IntegerState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new IntegerState();
|
||||
stack[i].setValue(val);
|
||||
}
|
||||
@@ -30,6 +30,11 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class LightModelStateStack extends LightModelState implements CowSt
|
||||
public LightModelStateStack(int id) {
|
||||
super();
|
||||
cow.id = id;
|
||||
stack = new LightModelState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new LightModelState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new LightModelState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class LightModelStateStack extends LightModelState implements CowSt
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class LightStateStack extends LightState implements CowStateStack<L
|
||||
public LightStateStack(int light, int id) {
|
||||
super(light);
|
||||
cow.id = id;
|
||||
stack = new LightState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new LightState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new LightState(light);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class LightStateStack extends LightState implements CowStateStack<L
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
|
||||
|
||||
public LineStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new LineState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new LineState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
|
||||
public MaterialStateStack(int face, int id) {
|
||||
super(face);
|
||||
cow.id = id;
|
||||
stack = new MaterialState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new MaterialState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new MaterialState(face);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -10,8 +10,8 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
|
||||
|
||||
public MatrixModeStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new MatrixMode[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new MatrixMode[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new MatrixMode();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class PointStateStack extends PointState implements CowStateStack<P
|
||||
|
||||
public PointStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new PointState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new PointState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class PointStateStack extends PointState implements CowStateStack<P
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
|
||||
|
||||
public PolygonStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new PolygonState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new PolygonState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
public final class RetainedState {
|
||||
public int[] ids = new int[64];
|
||||
public int count;
|
||||
public int program;
|
||||
public long programGen;
|
||||
|
||||
public void ensureCapacity(int capacity) {
|
||||
if (ids.length < capacity) ids = new int[capacity];
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
|
||||
|
||||
public ScissorStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new ScissorState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new ScissorState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class StencilStateStack extends StencilState implements CowStateSta
|
||||
|
||||
public StencilStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new StencilState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new StencilState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class StencilStateStack extends StencilState implements CowStateSta
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -12,8 +12,8 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
|
||||
public TextureBindingStack(int id, int unit) {
|
||||
cow = new CowDepths(StateSet.R_TEXTURE, unit);
|
||||
cow.id = id;
|
||||
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new TextureBinding[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new TextureBinding();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,11 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
|
||||
public Vec3fStack(Vector3f value, int id) {
|
||||
this.value = value;
|
||||
cow.id = id;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
|
||||
stack = new float[GLStateManager.STATE_SLOTS][3];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,6 +23,11 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s][0] = value.x;
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
|
||||
public Vec4fStack(Vector4f value, int id) {
|
||||
this.value = value;
|
||||
cow.id = id;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
|
||||
stack = new float[GLStateManager.STATE_SLOTS][4];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,6 +23,11 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2] || value.w != stack[slot][3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s][0] = value.x;
|
||||
|
||||
@@ -11,8 +11,8 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
|
||||
|
||||
public ViewPortStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new ViewportState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new ViewportState();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
|
||||
*/
|
||||
@@ -45,8 +47,8 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
|
||||
stack = null;
|
||||
cow = null;
|
||||
} else {
|
||||
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack = new TexEnvState[GLStateManager.STATE_SLOTS];
|
||||
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
|
||||
stack[i] = new TexEnvState(true);
|
||||
}
|
||||
cow = new CowDepths();
|
||||
@@ -70,6 +72,14 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
|
||||
this.envColorA = other.envColorA;
|
||||
}
|
||||
|
||||
private boolean sameAs(TexEnvState other) {
|
||||
return mode == other.mode && combineRgb == other.combineRgb && combineAlpha == other.combineAlpha
|
||||
&& Arrays.equals(sourceRgb, other.sourceRgb) && Arrays.equals(sourceAlpha, other.sourceAlpha)
|
||||
&& Arrays.equals(operandRgb, other.operandRgb) && Arrays.equals(operandAlpha, other.operandAlpha)
|
||||
&& scaleRgb == other.scaleRgb && scaleAlpha == other.scaleAlpha
|
||||
&& envColorR == other.envColorR && envColorG == other.envColorG && envColorB == other.envColorB && envColorA == other.envColorA;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mode = GL11.GL_MODULATE;
|
||||
combineRgb = GL11.GL_MODULATE;
|
||||
@@ -89,6 +99,11 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean slotChanged(int slot) {
|
||||
return !sameAs(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].copyFrom(this);
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_HudCacheBlendOverride_GLTest {
|
||||
|
||||
private static void assertDriverFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
assertEquals(srcRgb, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
|
||||
assertEquals(dstRgb, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
|
||||
assertEquals(srcAlpha, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
|
||||
assertEquals(dstAlpha, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
|
||||
}
|
||||
|
||||
private static void assertCachedFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
assertEquals(srcRgb, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB));
|
||||
assertEquals(dstRgb, GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB));
|
||||
assertEquals(srcAlpha, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
|
||||
assertEquals(dstAlpha, GLStateManager.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideChangesTheDriverFuncButNotTheCache() {
|
||||
try {
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
GLStateManager.setHudCacheOverride(true);
|
||||
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
|
||||
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_SRC_ALPHA, GL11.GL_ONE);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
|
||||
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
|
||||
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
|
||||
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
|
||||
} finally {
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popUnderOverrideKeepsTheOverriddenAlphaOnTheDriver() {
|
||||
try {
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.setHudCacheOverride(true);
|
||||
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
|
||||
GLStateManager.popStateTo(d);
|
||||
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
} finally {
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void separateAlphaIsForcedEvenWhenDstAlphaAlreadyMatches() {
|
||||
try {
|
||||
GLStateManager.setHudCacheOverride(true);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
} finally {
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.SharedDrawable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_OffContextCreation_GLTest {
|
||||
|
||||
private SharedDrawable sharedDrawable;
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
if (sharedDrawable != null) {
|
||||
DrawableContexts.destroy(sharedDrawable);
|
||||
sharedDrawable = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Executable> creationCalls() {
|
||||
final Map<String, Executable> calls = new LinkedHashMap<>();
|
||||
calls.put("glGenTextures()", GLStateManager::glGenTextures);
|
||||
calls.put("glGenTextures(IntBuffer)", () -> GLStateManager.glGenTextures(BufferUtils.createIntBuffer(1)));
|
||||
calls.put("glGenSamplers()", GLStateManager::glGenSamplers);
|
||||
calls.put("glGenSamplers(IntBuffer)", () -> GLStateManager.glGenSamplers(BufferUtils.createIntBuffer(1)));
|
||||
calls.put("glGenSamplers(int[])", () -> GLStateManager.glGenSamplers(new int[1]));
|
||||
calls.put("glGenBuffers()", GLStateManager::glGenBuffers);
|
||||
calls.put("glGenBuffers(IntBuffer)", () -> GLStateManager.glGenBuffers(BufferUtils.createIntBuffer(1)));
|
||||
calls.put("glGenBuffers(int[])", () -> GLStateManager.glGenBuffers(new int[1]));
|
||||
calls.put("glCreateBuffers()", GLStateManager::glCreateBuffers);
|
||||
calls.put("glCreateBuffers(IntBuffer)", () -> GLStateManager.glCreateBuffers(BufferUtils.createIntBuffer(1)));
|
||||
calls.put("glCreateBuffers(int[])", () -> GLStateManager.glCreateBuffers(new int[1]));
|
||||
calls.put("glGenVertexArrays()", GLStateManager::glGenVertexArrays);
|
||||
calls.put("glGenFramebuffers()", GLStateManager::glGenFramebuffers);
|
||||
calls.put("glGenRenderbuffers()", GLStateManager::glGenRenderbuffers);
|
||||
calls.put("glGenQueries()", GLStateManager::glGenQueries);
|
||||
calls.put("glGenQueries(IntBuffer)", () -> GLStateManager.glGenQueries(BufferUtils.createIntBuffer(1)));
|
||||
calls.put("glCreateShader", () -> GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER));
|
||||
calls.put("glCreateProgram", GLStateManager::glCreateProgram);
|
||||
calls.put("RenderSystem.createTexture", () -> RenderSystem.createTexture(GL11.GL_TEXTURE_2D));
|
||||
calls.put("RenderSystem.createFramebuffer", RenderSystem::createFramebuffer);
|
||||
return calls;
|
||||
}
|
||||
|
||||
@Test
|
||||
void creationWithoutContextThrows() throws InterruptedException {
|
||||
TestThreads.run("glsm-no-context", () -> {
|
||||
for (Map.Entry<String, Executable> call : creationCalls().entrySet()) {
|
||||
assertThrows(IllegalStateException.class, call.getValue(), call.getKey() + " did not throw off-context");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void creationOnSharedContextThreadPasses() throws Exception {
|
||||
sharedDrawable = new SharedDrawable(Display.getDrawable());
|
||||
DrawableContexts.run(sharedDrawable, "glsm-shared-context", () -> {
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
assertNotEquals(0, texture, "no texture name on a thread that owns a shared context");
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_RetainedState_GLTest {
|
||||
|
||||
private static final StateSet ALL = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
|
||||
|
||||
private static int driverTextureBinding(int unit) {
|
||||
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
GL13.glActiveTexture(saved);
|
||||
return binding;
|
||||
}
|
||||
|
||||
private static void baseline() {
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
private static void mutate(int texture) {
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE);
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GEQUAL, 0.5F);
|
||||
GLStateManager.glColor4f(0.25F, 0.5F, 0.75F, 0.5F);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
private static void assertBaseline() {
|
||||
assertTrue(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND));
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
|
||||
assertEquals(GL11.GL_ZERO, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
|
||||
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
|
||||
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
|
||||
assertEquals(GL11.GL_GREATER, GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC));
|
||||
assertEquals(1.0F, GLStateManager.getColor().getRed(), 0.0F);
|
||||
assertEquals(0, driverTextureBinding(0));
|
||||
}
|
||||
|
||||
private static void assertMutated(int texture) {
|
||||
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
assertTrue(GLStateManager.glIsEnabled(GL11.GL_BLEND));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
|
||||
assertEquals(GL11.GL_ZERO, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
|
||||
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
|
||||
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
|
||||
assertEquals(GL11.GL_DST_COLOR, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB));
|
||||
assertFalse(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
|
||||
assertEquals(GL11.GL_ALWAYS, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
|
||||
assertEquals(GL11.GL_GEQUAL, GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC));
|
||||
assertEquals(0.25F, GLStateManager.getColor().getRed(), 0.0F);
|
||||
assertEquals(0.5F, GLStateManager.getColor().getAlpha(), 0.0F);
|
||||
assertEquals(texture, driverTextureBinding(0));
|
||||
assertEquals(texture, GLStateManager.getBoundTextureForServerState(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retainedDeltaReappliesAfterPop() {
|
||||
final int texture = GL11.glGenTextures();
|
||||
final RetainedState retained = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
final int d = GLStateManager.pushState(ALL);
|
||||
mutate(texture);
|
||||
GLStateManager.retainModifiedState(d, retained);
|
||||
GLStateManager.popStateTo(d);
|
||||
assertBaseline();
|
||||
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertMutated(texture);
|
||||
|
||||
baseline();
|
||||
assertBaseline();
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertMutated(texture);
|
||||
} finally {
|
||||
baseline();
|
||||
GL11.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliedDeltaIsRestoredByAnEnclosingPush() {
|
||||
final int texture = GL11.glGenTextures();
|
||||
final RetainedState retained = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
final int d = GLStateManager.pushState(ALL);
|
||||
mutate(texture);
|
||||
GLStateManager.retainModifiedState(d, retained);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
final int outer = GLStateManager.pushState(ALL);
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertMutated(texture);
|
||||
GLStateManager.popStateTo(outer);
|
||||
assertBaseline();
|
||||
} finally {
|
||||
baseline();
|
||||
GL11.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void statesRestoredByANestedPushAreNotRetained() {
|
||||
final RetainedState retained = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
final int d = GLStateManager.pushState(ALL);
|
||||
GLStateManager.enableBlend();
|
||||
final int nested = GLStateManager.pushState(ALL);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.popStateTo(nested);
|
||||
GLStateManager.retainModifiedState(d, retained);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
assertTrue(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
} finally {
|
||||
baseline();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyDifferingStateIsReissued() {
|
||||
final int texture = GL11.glGenTextures();
|
||||
final RetainedState retained = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
final int d = GLStateManager.pushState(ALL);
|
||||
mutate(texture);
|
||||
GLStateManager.retainModifiedState(d, retained);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final long backendCalls = GLStateManager.attribBackendCalls;
|
||||
final int colorGen = glCtx.colorGeneration;
|
||||
final int fragmentGen = glCtx.fragmentGeneration;
|
||||
final int batchGen = glCtx.batchStateGeneration;
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertEquals(backendCalls, GLStateManager.attribBackendCalls);
|
||||
assertEquals(colorGen, glCtx.colorGeneration);
|
||||
assertEquals(fragmentGen, glCtx.fragmentGeneration);
|
||||
assertEquals(batchGen, glCtx.batchStateGeneration);
|
||||
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertEquals(backendCalls + 3, GLStateManager.attribBackendCalls);
|
||||
assertMutated(texture);
|
||||
} finally {
|
||||
baseline();
|
||||
GL11.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void aSecondCaptureInvalidatesTheFirst() {
|
||||
final RetainedState first = new RetainedState();
|
||||
final RetainedState second = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
int d = GLStateManager.pushState(ALL);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.retainModifiedState(d, first);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
d = GLStateManager.pushState(ALL);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.retainModifiedState(d, second);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
GLStateManager.applyRetainedState(first);
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
GLStateManager.applyRetainedState(second);
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
|
||||
} finally {
|
||||
baseline();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesInAnOuterPushAreNotRetained() {
|
||||
final RetainedState retained = new RetainedState();
|
||||
try {
|
||||
baseline();
|
||||
GLStateManager.glClearColor(0.25F, 0.5F, 0.75F, 1.0F);
|
||||
final int outer = GLStateManager.pushState(ALL);
|
||||
GLStateManager.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
final int inner = GLStateManager.pushState(ALL);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.retainModifiedState(inner, retained);
|
||||
GLStateManager.popStateTo(outer);
|
||||
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
|
||||
assertEquals(0.25F, GLStateManager.getClearColor().getRed(), 0.0F);
|
||||
assertEquals(1.0F, GLStateManager.getClearColor().getAlpha(), 0.0F);
|
||||
} finally {
|
||||
baseline();
|
||||
GLStateManager.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDeletedProgramIsNotRebound() {
|
||||
final RetainedState retained = new RetainedState();
|
||||
final int program = GLSM_StateSet_GLTest.linkTrivialProgram();
|
||||
try {
|
||||
baseline();
|
||||
final int d = GLStateManager.pushState(ALL);
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.retainModifiedState(d, retained);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
GLStateManager.applyRetainedState(retained);
|
||||
assertEquals(0, GLStateManager.getActiveProgram());
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
baseline();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class GLSM_StateSet_GLTest {
|
||||
private static final String TRIVIAL_VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
|
||||
private static final String TRIVIAL_FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
|
||||
|
||||
private static int linkTrivialProgram() {
|
||||
static int linkTrivialProgram() {
|
||||
final int v = compileShader(GL20.GL_VERTEX_SHADER, TRIVIAL_VS);
|
||||
final int f = compileShader(GL20.GL_FRAGMENT_SHADER, TRIVIAL_FS);
|
||||
final int p = GL20.glCreateProgram();
|
||||
@@ -89,33 +89,41 @@ public class GLSM_StateSet_GLTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void fontRestoresDepthAxisAndShadeModelButNotCull() {
|
||||
void fontRestoresDepthAxisShadeModelAndPolygonOffsetButNotStencil() {
|
||||
try {
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.FONT);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(-10.0f, -10.0f);
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - restored by FONT pop");
|
||||
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
|
||||
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
|
||||
assertEquals(GL11.GL_SMOOTH, GLStateManager.getShadeModelState().getValue(), "cache shade model");
|
||||
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a FONT member, must stay toggled");
|
||||
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a FONT member, must stay toggled");
|
||||
verifyIsEnabled(GL11.GL_POLYGON_OFFSET_FILL, false, "Polygon offset fill - restored by FONT pop");
|
||||
assertEquals(0.0f, GLStateManager.getPolygonState().getOffsetFactor(), "cache polygon offset factor");
|
||||
assertEquals(0.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_FACTOR), "driver polygon offset factor");
|
||||
assertEquals(0.0f, GLStateManager.getPolygonState().getOffsetUnits(), "cache polygon offset units");
|
||||
assertEquals(0.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_UNITS), "driver polygon offset units");
|
||||
verifyIsEnabled(GL11.GL_STENCIL_TEST, false, "Stencil test - not a FONT member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LESS);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class WideLineClipGLTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final double NEAR = 0.05;
|
||||
private static final float LINE_WIDTH = 3.0f;
|
||||
private static final int BACKGROUND = 0xFF000000;
|
||||
|
||||
private static final float[] IN_FRONT = { 0f, -1f, -2f };
|
||||
private static final float[] BEHIND = { 0f, 0.8f, 2f };
|
||||
|
||||
private boolean savedEmulation;
|
||||
private int vao;
|
||||
private int vbo;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
assumeTrue(GLStateManager.supportsGeometryShaders());
|
||||
savedEmulation = GLStateManager.wideLineEmulationEnabled;
|
||||
GLStateManager.wideLineEmulationEnabled = true;
|
||||
|
||||
FfpFixture.resetFfpState();
|
||||
ShaderManager.enable();
|
||||
ShaderManager.getInstance().activate();
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glFrustum(-NEAR, NEAR, -NEAR, NEAR, NEAR, 100.0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glLineWidth(LINE_WIDTH);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLStateManager.wideLineEmulationEnabled = savedEmulation;
|
||||
GLStateManager.glLineWidth(1.0f);
|
||||
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
|
||||
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
FfpFixture.resetFfpState();
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lineEndingBehindTheCameraKeepsItsWidth() {
|
||||
assertConstantWidth(IN_FRONT, BEHIND);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lineStartingBehindTheCameraKeepsItsWidth() {
|
||||
assertConstantWidth(BEHIND, IN_FRONT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lineFullyBehindTheCameraDrawsNothing() {
|
||||
final int[] pixels = drawLine(new float[] { 0f, -1f, 2f }, BEHIND);
|
||||
for (int px : pixels) {
|
||||
assertEquals(BACKGROUND, px);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConstantWidth(float[] start, float[] end) {
|
||||
final int[] pixels = drawLine(start, end);
|
||||
for (int row = 0; row < SIZE / 4; row++) {
|
||||
int lit = 0;
|
||||
for (int x = 0; x < SIZE; x++) {
|
||||
if (pixels[row * SIZE + x] != BACKGROUND) lit++;
|
||||
}
|
||||
final int y = row, count = lit;
|
||||
assertTrue(count > 0 && count <= LINE_WIDTH + 1, () -> "row " + y + " is " + count + " pixels wide");
|
||||
}
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
private int[] drawLine(float[] start, float[] end) {
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
final FloatBuffer positions = BufferUtils.createFloatBuffer(6);
|
||||
positions.put(start).put(end).flip();
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(0);
|
||||
|
||||
FfpFixture.clear();
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 2);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
return FfpFixture.readRegion(SIZE);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
@@ -122,4 +123,27 @@ class ShaderDiskCacheTest {
|
||||
ShaderDiskCache.put(ShaderDiskCache.key("t").i(9), new byte[] {5});
|
||||
assertArrayEquals(new byte[] {5}, ShaderDiskCache.get(ShaderDiskCache.key("t").i(9)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retainLayerKeepsListedEntriesOtherLayersAndInFlightWrites() throws IOException {
|
||||
ShaderDiskCache.configure(dir, "a");
|
||||
final ShaderDiskCache.Key kept = ShaderDiskCache.key("p").str("kept");
|
||||
final ShaderDiskCache.Key dropped = ShaderDiskCache.key("p").str("dropped");
|
||||
final ShaderDiskCache.Key otherLayer = ShaderDiskCache.key("q").str("dropped");
|
||||
ShaderDiskCache.put(kept, new byte[] {1});
|
||||
ShaderDiskCache.put(dropped, new byte[] {2});
|
||||
ShaderDiskCache.put(otherLayer, new byte[] {3});
|
||||
final Path inFlight = dir.resolve("p").resolve(dropped.hex() + ".7.tmp");
|
||||
Files.write(inFlight, new byte[] {4});
|
||||
|
||||
ShaderDiskCache.retainLayer("p", Set.of(kept.hex()));
|
||||
assertArrayEquals(new byte[] {1}, ShaderDiskCache.get(kept));
|
||||
assertNull(ShaderDiskCache.get(dropped));
|
||||
assertArrayEquals(new byte[] {3}, ShaderDiskCache.get(otherLayer));
|
||||
assertTrue(Files.exists(inFlight));
|
||||
|
||||
ShaderDiskCache.retainLayer("p", null);
|
||||
assertNull(ShaderDiskCache.get(kept));
|
||||
assertArrayEquals(new byte[] {3}, ShaderDiskCache.get(otherLayer));
|
||||
}
|
||||
}
|
||||
+22
@@ -28,6 +28,7 @@ import org.lwjgl.opengl.GL31C;
|
||||
import org.lwjgl.opengl.GL32C;
|
||||
import org.lwjgl.opengl.GL33C;
|
||||
import org.lwjgl.opengl.GL40C;
|
||||
import org.lwjgl.opengl.GL41C;
|
||||
import org.lwjgl.opengl.GL42C;
|
||||
import org.lwjgl.opengl.GL43C;
|
||||
import org.lwjgl.opengl.GL44C;
|
||||
@@ -965,6 +966,27 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
return GL20C.glIsProgram(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsProgramBinary() {
|
||||
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
|
||||
&& GL11C.glGetInteger(GL41C.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programParameteri(int program, int pname, int value) {
|
||||
GL41C.glProgramParameteri(program, pname, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
|
||||
GL41C.glGetProgramBinary(program, length, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
|
||||
GL41C.glProgramBinary(program, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateProgram(int program) {
|
||||
GL20C.glValidateProgram(program);
|
||||
|
||||
@@ -543,6 +543,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return device.getDevice() != 0;
|
||||
}
|
||||
|
||||
@Override public boolean hasContextOnThread() {
|
||||
return hasContext() && (Thread.currentThread() == GLStateManager.getMainThread() || tlState.get() != null);
|
||||
}
|
||||
|
||||
@Override public boolean isCurrent() {
|
||||
return Thread.currentThread() == GLStateManager.getMainThread();
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlOffContextCreationTest {
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
@Test
|
||||
void creationWithoutContextThrows() throws InterruptedException {
|
||||
TestThreads.run("sdl-no-context", () -> assertThrows(IllegalStateException.class, GLStateManager::glGenTextures));
|
||||
}
|
||||
|
||||
@Test
|
||||
void creationOnRenderThreadPasses() {
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
assertNotEquals(0, texture);
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
@@ -28,7 +27,6 @@ import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.PipelineManager;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.MathHelper;
|
||||
@@ -61,6 +59,8 @@ import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_C
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_PAYLOAD;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
|
||||
import static com.gtnewhorizons.angelica.client.font.FontStrategist.INVALID_TEXTURE;
|
||||
import static com.gtnewhorizons.angelica.client.font.FontStrategist.bindIntTexture;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
@@ -192,6 +192,9 @@ public class BatchingFontRenderer {
|
||||
private int lightmapTextureId = 0;
|
||||
private int lightmapPackedLight = -1;
|
||||
|
||||
private final TextDrawState drawState = new TextDrawState();
|
||||
private final TextDrawState liveDrawState = new TextDrawState();
|
||||
|
||||
private final Vector3f lightingFactor = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||
private boolean lightingFactorActive = false;
|
||||
|
||||
@@ -243,6 +246,13 @@ public class BatchingFontRenderer {
|
||||
lightmapPackedLight = packedLight;
|
||||
}
|
||||
|
||||
private void captureDrawState() {
|
||||
liveDrawState.captureLive();
|
||||
if (liveDrawState.sameAs(drawState)) return;
|
||||
sealBatchSegment();
|
||||
drawState.set(liveDrawState);
|
||||
}
|
||||
|
||||
private static final class TextSegment {
|
||||
final Matrix4f mvp = new Matrix4f();
|
||||
final Matrix4f modelView = new Matrix4f();
|
||||
@@ -256,8 +266,7 @@ public class BatchingFontRenderer {
|
||||
int packedLight;
|
||||
float normalX, normalY, normalZ;
|
||||
int blockEntityId;
|
||||
boolean depthTest;
|
||||
boolean depthMask;
|
||||
final TextDrawState drawState = new TextDrawState();
|
||||
}
|
||||
|
||||
// 16-bit EBO index range
|
||||
@@ -403,18 +412,6 @@ public class BatchingFontRenderer {
|
||||
private static final int LAYER_BACKGROUND = -1;
|
||||
private static final int LAYER_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* FML's splash font renderer draws before the texture manager exists and binds its own
|
||||
* font texture, so let it bind and read the id back.
|
||||
*/
|
||||
private int textureFor(FontProvider fontProvider, char chr) {
|
||||
if (fontProvider instanceof FontProviderCustom || Minecraft.getMinecraft().getTextureManager() != null) {
|
||||
return fontProvider.getTexture(chr);
|
||||
}
|
||||
((FontRendererAccessor) underlying).angelica$bindTexture(locationFontTexture);
|
||||
return GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, int texture, boolean isUnicode) {
|
||||
pushDrawCmd(startIdx, idxCount, texture, isUnicode, LAYER_DEFAULT);
|
||||
}
|
||||
@@ -555,8 +552,7 @@ public class BatchingFontRenderer {
|
||||
segment.normalY = normal.y;
|
||||
segment.normalZ = normal.z;
|
||||
segment.blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
segment.depthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
segment.depthMask = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
segment.drawState.set(drawState);
|
||||
batchSegments.add(segment);
|
||||
batchSealedEnd = end;
|
||||
}
|
||||
@@ -588,7 +584,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
private static boolean shouldDeferNow() {
|
||||
if (!GLStateManager.isMainThread()) return false;
|
||||
return TesrBatchRenderer.INSTANCE.hasPendingGeometry() || ModelPartBatcher.INSTANCE.isActive();
|
||||
return (TesrBatchRenderer.INSTANCE.hasPendingGeometry() || ModelPartBatcher.INSTANCE.isActive())
|
||||
&& BatchDrawDefaults.drawsToPassFramebuffer();
|
||||
}
|
||||
|
||||
public void beginDeferredBatch() {
|
||||
@@ -676,23 +673,13 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
final BatchingFontRenderer owner = segment.owner;
|
||||
if (segment.cmdStart == segment.cmdEnd) continue;
|
||||
|
||||
GLStateManager.setModelViewMatrix(segment.modelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
|
||||
|
||||
if (segment.depthTest != curDepthTest) {
|
||||
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
curDepthTest = segment.depthTest;
|
||||
}
|
||||
if (segment.depthMask != curDepthMask) {
|
||||
GLStateManager.glDepthMask(segment.depthMask);
|
||||
curDepthMask = segment.depthMask;
|
||||
}
|
||||
segment.drawState.apply();
|
||||
|
||||
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
|
||||
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
@@ -718,24 +705,15 @@ public class BatchingFontRenderer {
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
deferredSegments.get(0).owner.setupFontDrawState();
|
||||
flushLastTexture = -1;
|
||||
flushLastTexture = INVALID_TEXTURE;
|
||||
resetFlushLightmap();
|
||||
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
FLUSH_MVP.clear();
|
||||
segment.mvp.get(FLUSH_MVP);
|
||||
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, FLUSH_MVP);
|
||||
uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU,
|
||||
segment.lightmapV, segment.lightmapTexture);
|
||||
if (segment.depthTest != curDepthTest) {
|
||||
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
curDepthTest = segment.depthTest;
|
||||
}
|
||||
if (segment.depthMask != curDepthMask) {
|
||||
GLStateManager.glDepthMask(segment.depthMask);
|
||||
curDepthMask = segment.depthMask;
|
||||
}
|
||||
segment.owner.applyDrawState(segment.drawState);
|
||||
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
|
||||
}
|
||||
} finally {
|
||||
@@ -746,6 +724,11 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDrawState(TextDrawState state) {
|
||||
state.apply();
|
||||
GLStateManager.glUniform1f(alphaTestRefLocation, state.alphaRef);
|
||||
}
|
||||
|
||||
public static void discardDeferredText() {
|
||||
if (deferredSegments.isEmpty()) return;
|
||||
for (int i = 0, n = deferredSegments.size(); i < n; i++) {
|
||||
@@ -844,7 +827,7 @@ public class BatchingFontRenderer {
|
||||
final int d = GLStateManager.pushState(StateSet.FONT);
|
||||
try {
|
||||
setupFontDrawState();
|
||||
flushLastTexture = -1;
|
||||
flushLastTexture = INVALID_TEXTURE;
|
||||
resetFlushLightmap();
|
||||
try {
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
@@ -853,6 +836,7 @@ public class BatchingFontRenderer {
|
||||
segment.mvp.get(FLUSH_MVP);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, FLUSH_MVP);
|
||||
uploadLightmap(lightmapLocation, segment.lightmapActive, segment.lightmapU, segment.lightmapV, segment.lightmapTexture);
|
||||
applyDrawState(segment.drawState);
|
||||
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
|
||||
}
|
||||
if (batchSealedEnd < cmdCount) {
|
||||
@@ -861,6 +845,7 @@ public class BatchingFontRenderer {
|
||||
scratchMvp.get(FLUSH_MVP);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, FLUSH_MVP);
|
||||
uploadLightmap(lightmapLocation, lightmapActive, lightmapU, lightmapV, lightmapTextureId);
|
||||
applyDrawState(drawState);
|
||||
drawCommands(cmds, batchSealedEnd, cmdCount, this);
|
||||
}
|
||||
} finally {
|
||||
@@ -894,7 +879,6 @@ public class BatchingFontRenderer {
|
||||
fontBrightnessLast = FontConfig.fontBrightness;
|
||||
GLStateManager.glUniform1f(fontBrightness, 0.625f * FontConfig.fontBrightness + 0.1875f);
|
||||
}
|
||||
GLStateManager.glUniform1f(alphaTestRefLocation, GLStateManager.getAlphaState().getReference());
|
||||
if (lightmapSamplerProgram != fontShaderId) {
|
||||
GLStateManager.glUniform1i(lightmapSamplerLocation, LIGHTMAP_TEX_UNIT);
|
||||
lightmapSamplerProgram = fontShaderId;
|
||||
@@ -977,7 +961,7 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
if (cmd.texture != 0) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cmd.texture);
|
||||
bindIntTexture(owner, cmd.texture);
|
||||
}
|
||||
flushLastTexture = cmd.texture;
|
||||
}
|
||||
@@ -1023,12 +1007,14 @@ public class BatchingFontRenderer {
|
||||
if (segment.cmdStart == segment.cmdEnd) continue;
|
||||
GLStateManager.setModelViewMatrix(segment.modelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
|
||||
segment.drawState.apply();
|
||||
emitRangeThroughPipeline(this, cmdsData, segment.cmdStart, segment.cmdEnd,
|
||||
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
|
||||
}
|
||||
if (batchSealedEnd < cmdCount) {
|
||||
GLStateManager.setModelViewMatrix(scratchModelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
|
||||
drawState.apply();
|
||||
final Vector3f normal = ShaderManager.getCurrentNormal();
|
||||
emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount,
|
||||
lightmapPackedLight, normal.x, normal.y, normal.z);
|
||||
@@ -1048,7 +1034,7 @@ public class BatchingFontRenderer {
|
||||
private static void emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to,
|
||||
int packedLight, float normalX, float normalY, float normalZ) {
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
int lastTexture = -1;
|
||||
int lastTexture = INVALID_TEXTURE;
|
||||
boolean drawing = false;
|
||||
try {
|
||||
for (int i = from; i < to; i++) {
|
||||
@@ -1062,7 +1048,7 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.disableTexture();
|
||||
} else {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cmd.texture);
|
||||
bindIntTexture(owner, cmd.texture);
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
@@ -1253,6 +1239,7 @@ public class BatchingFontRenderer {
|
||||
|
||||
this.beginBatch();
|
||||
this.captureLightmapState();
|
||||
this.captureDrawState();
|
||||
this.lightingFactorActive = isWorldSpaceText() && FFPVertexLighting.modulatesVertexColor(this.lightingFactor);
|
||||
float curX = anchorX;
|
||||
try {
|
||||
@@ -1501,7 +1488,7 @@ public class BatchingFontRenderer {
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final int texture = textureFor(fontProvider, chr);
|
||||
final int texture = fontProvider.getTexture(chr);
|
||||
|
||||
// Wave: Y offset via sine wave
|
||||
float renderY = heightNorth;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -20,10 +16,9 @@ import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.Raster;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.FontStrategist.getFontName;
|
||||
@@ -33,222 +28,241 @@ public final class FontProviderCustom implements FontProvider {
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
static final int ATLAS_SIZE = 128;
|
||||
static final int ATLAS_COUNT = 512;
|
||||
private final byte id; // 0 - primary font, 1 - fallback font
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
private float currentFontQuality = FontConfig.customFontQuality;
|
||||
@Setter
|
||||
private final FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
private final int[] atlasTextures = new int[ATLAS_COUNT];
|
||||
private Font font;
|
||||
|
||||
private FontProviderCustom(byte id) {
|
||||
this.id = id;
|
||||
Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
String myFontName = switch (this.id) {
|
||||
case 0 -> FontConfig.customFontNamePrimary;
|
||||
case 1 -> FontConfig.customFontNameFallback;
|
||||
default -> null;
|
||||
};
|
||||
if (Objects.equals(myFontName, "(none)")) {
|
||||
this.font = null;
|
||||
return;
|
||||
FontProviderCustom(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
private static Font configured(String fontName) {
|
||||
if (Objects.equals(fontName, "(none)")) {
|
||||
return null;
|
||||
}
|
||||
int fontPos = -1;
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(myFontName, getFontName(availableFonts[i]))) {
|
||||
fontPos = i;
|
||||
break;
|
||||
for (Font available : FontStrategist.getAvailableFonts()) {
|
||||
if (Objects.equals(fontName, getFontName(available))) {
|
||||
return available.deriveFont((float) FontConfig.customFontQuality);
|
||||
}
|
||||
}
|
||||
if (fontPos == -1) {
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
this.font = availableFonts[fontPos].deriveFont(this.currentFontQuality);
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", fontName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class InstLoader {
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
|
||||
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom(configured(FontConfig.customFontNamePrimary));
|
||||
static final FontProviderCustom instance1 = new FontProviderCustom(configured(FontConfig.customFontNameFallback));
|
||||
}
|
||||
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
|
||||
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
|
||||
|
||||
public void setFont(Font font) {
|
||||
synchronized (this) {
|
||||
this.font = font;
|
||||
Arrays.fill(this.fontAtlases, null);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ATLAS_COUNT; i++) {
|
||||
if (this.atlasTextures[i] != 0) {
|
||||
GLStateManager.glDeleteTextures(this.atlasTextures[i]);
|
||||
this.atlasTextures[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
this.currentFontQuality = FontConfig.customFontQuality;
|
||||
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
|
||||
|
||||
for (FontAtlas atlas : fontAtlases) {
|
||||
if (atlas != null && atlas.texture != 0) {
|
||||
GLStateManager.glDeleteTextures(atlas.texture);
|
||||
}
|
||||
}
|
||||
|
||||
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
setFont(FontStrategist.getAvailableFonts()[fontID].deriveFont((float) FontConfig.customFontQuality));
|
||||
}
|
||||
|
||||
@Value
|
||||
private class GlyphData {
|
||||
float uStart;
|
||||
float vStart;
|
||||
float xAdvance;
|
||||
float glyphW;
|
||||
float uSz;
|
||||
float vSz;
|
||||
}
|
||||
private static final class FontAtlas {
|
||||
static final int STRIDE = 6;
|
||||
static final int U_START = 0;
|
||||
static final int V_START = 1;
|
||||
static final int X_ADVANCE = 2;
|
||||
static final int GLYPH_W = 3;
|
||||
static final int U_SIZE = 4;
|
||||
static final int V_SIZE = 5;
|
||||
|
||||
private class FontAtlas {
|
||||
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
|
||||
private int texture;
|
||||
private final int id;
|
||||
final float[] metrics;
|
||||
final long availLo;
|
||||
final long availHi;
|
||||
|
||||
FontAtlas(int id) {
|
||||
this.id = id;
|
||||
FontAtlas(float[] metrics, long availLo, long availHi) {
|
||||
this.metrics = metrics;
|
||||
this.availLo = availLo;
|
||||
this.availHi = availHi;
|
||||
}
|
||||
|
||||
void construct(Font font) {
|
||||
int atlasChars = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) { atlasChars++; }
|
||||
}
|
||||
if (atlasChars == 0) { return; }
|
||||
boolean has(int slot) {
|
||||
return bit(this.availLo, this.availHi, slot);
|
||||
}
|
||||
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setFont(font);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
static boolean bit(long lo, long hi, int slot) {
|
||||
return ((slot < 64 ? lo >>> slot : hi >>> (slot - 64)) & 1L) != 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private static final FontAtlas EMPTY = new FontAtlas(new float[ATLAS_SIZE * FontAtlas.STRIDE], 0L, 0L);
|
||||
|
||||
private static void setHints(Graphics2D g2d) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
private static BufferedImage layout(Font font, int id, long availLo, long availHi, float[] metricsOut) {
|
||||
final float quality = font.getSize2D();
|
||||
int atlasChars = Long.bitCount(availLo) + Long.bitCount(availHi);
|
||||
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setFont(font);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
|
||||
final float charSeparator = quality / 3f;
|
||||
int rowWidth = 0;
|
||||
int maxRowWidth = 0;
|
||||
atlasChars = 0;
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (atlasChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
rowWidth = 0;
|
||||
}
|
||||
if (FontAtlas.bit(availLo, availHi, i)) {
|
||||
rowWidth += (int) (charSeparator + fm.charWidth((char) (i + ATLAS_SIZE * id)));
|
||||
atlasChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + charSeparator);
|
||||
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
|
||||
|
||||
final boolean rasterize = metricsOut == null;
|
||||
if (rasterize) {
|
||||
g2d.dispose();
|
||||
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
|
||||
final float charSeparator = currentFontQuality / 3f;
|
||||
int rowWidth = 0;
|
||||
int maxRowWidth = 0;
|
||||
atlasChars = 0;
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (atlasChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
rowWidth = 0;
|
||||
}
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
rowWidth += (int) (charSeparator + fm.charWidth(ch));
|
||||
atlasChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + charSeparator);
|
||||
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
|
||||
|
||||
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = image.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
setHints(g2d);
|
||||
g2d.setFont(font);
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) charSeparator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (!font.canDisplay(ch)) { continue; }
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) charSeparator;
|
||||
tileY++;
|
||||
}
|
||||
|
||||
final int charWidth = fm.charWidth(ch);
|
||||
final float charAspectRatio = (float) charWidth / lineHeight;
|
||||
final float inset = currentFontQuality / 16;
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
|
||||
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
final float vStart = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
|
||||
final float glyphW = charAspectRatio * 8 + 1;
|
||||
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
|
||||
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
|
||||
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
|
||||
imgX += (int) (charWidth + charSeparator);
|
||||
tileX++;
|
||||
}
|
||||
g2d.dispose();
|
||||
|
||||
int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
|
||||
|
||||
final int width = image.getWidth();
|
||||
final int height = image.getHeight();
|
||||
final IntBuffer pixelBuffer = memAllocInt(width * height);
|
||||
long ptr = memAddress0(pixelBuffer);
|
||||
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferInt dataBufferInt) {
|
||||
final int[] pixelValues = dataBufferInt.getData();
|
||||
|
||||
for (int i : pixelValues) {
|
||||
memPutInt(ptr, i);
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
pixelBuffer.limit(pixelValues.length);
|
||||
} else {
|
||||
Raster raster = image.getRaster();
|
||||
ColorModel colorModel = image.getColorModel();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
Object pixel = raster.getDataElements(x, y, null);
|
||||
memPutInt(ptr, colorModel.getRGB(pixel));
|
||||
ptr += 4;
|
||||
}
|
||||
}
|
||||
pixelBuffer.limit(image.getHeight() * image.getWidth());
|
||||
}
|
||||
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, image.getWidth(), image.getHeight(), 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
memFree(pixelBuffer);
|
||||
|
||||
this.texture = id;
|
||||
} else {
|
||||
setHints(g2d);
|
||||
}
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) charSeparator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (!FontAtlas.bit(availLo, availHi, i)) { continue; }
|
||||
final char ch = (char) (i + ATLAS_SIZE * id);
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) charSeparator;
|
||||
tileY++;
|
||||
}
|
||||
|
||||
final int charWidth = fm.charWidth(ch);
|
||||
if (rasterize) {
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
|
||||
} else {
|
||||
final float charAspectRatio = (float) charWidth / lineHeight;
|
||||
final float inset = quality / 16;
|
||||
final int o = i * FontAtlas.STRIDE;
|
||||
metricsOut[o + FontAtlas.U_START] = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
metricsOut[o + FontAtlas.V_START] = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
metricsOut[o + FontAtlas.X_ADVANCE] = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
|
||||
metricsOut[o + FontAtlas.GLYPH_W] = charAspectRatio * 8 + 1;
|
||||
metricsOut[o + FontAtlas.U_SIZE] = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
|
||||
metricsOut[o + FontAtlas.V_SIZE] = (float) (lineHeight + 2 * inset) / imageHeight;
|
||||
}
|
||||
imgX += (int) (charWidth + charSeparator);
|
||||
tileX++;
|
||||
}
|
||||
g2d.dispose();
|
||||
|
||||
return rasterize ? image : null;
|
||||
}
|
||||
|
||||
private FontAtlas getAtlas(char chr) {
|
||||
int id = chr / ATLAS_SIZE;
|
||||
final FontAtlas fa = this.fontAtlases[chr / ATLAS_SIZE];
|
||||
return fa != null ? fa : buildAtlas(chr / ATLAS_SIZE);
|
||||
}
|
||||
|
||||
private synchronized FontAtlas buildAtlas(int id) {
|
||||
FontAtlas fa = this.fontAtlases[id];
|
||||
if (fa == null) {
|
||||
fa = new FontAtlas(id);
|
||||
fa.construct(this.font);
|
||||
this.fontAtlases[id] = fa;
|
||||
if (fa != null) { return fa; }
|
||||
final Font f = this.font;
|
||||
if (f == null) { return EMPTY; }
|
||||
long availLo = 0L;
|
||||
long availHi = 0L;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (f.canDisplay((char) (i + ATLAS_SIZE * id))) {
|
||||
if (i < 64) { availLo |= 1L << i; } else { availHi |= 1L << (i - 64); }
|
||||
}
|
||||
}
|
||||
if ((availLo | availHi) == 0L) {
|
||||
fa = EMPTY;
|
||||
} else {
|
||||
final float[] metrics = new float[ATLAS_SIZE * FontAtlas.STRIDE];
|
||||
layout(f, id, availLo, availHi, metrics);
|
||||
fa = new FontAtlas(metrics, availLo, availHi);
|
||||
}
|
||||
this.fontAtlases[id] = fa;
|
||||
return fa;
|
||||
}
|
||||
|
||||
private synchronized BufferedImage rasterize(int id) {
|
||||
final Font f = this.font;
|
||||
final FontAtlas fa = this.fontAtlases[id];
|
||||
if (f == null || fa == null || fa == EMPTY) { return null; }
|
||||
return layout(f, id, fa.availLo, fa.availHi, null);
|
||||
}
|
||||
|
||||
private int uploadAtlas(int id) {
|
||||
final BufferedImage image = rasterize(id);
|
||||
if (image == null) { return 0; }
|
||||
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
|
||||
|
||||
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
final IntBuffer pixelBuffer = memAllocInt(pixels.length);
|
||||
pixelBuffer.put(pixels);
|
||||
pixelBuffer.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, image.getWidth(), image.getHeight(), 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
memFree(pixelBuffer);
|
||||
|
||||
this.atlasTextures[id] = texture;
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
if (this.font == null) { return false; }
|
||||
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
|
||||
return getAtlas(chr).has(chr % ATLAS_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* A random glyph of the same width, for {@code §k}. Kept inside the character's own
|
||||
* atlas: atlases are rasterized on first use, so reaching outside one would build a
|
||||
* new atlas mid-frame. Slots the font has no glyph for are skipped.
|
||||
* atlas: reaching outside one would measure and rasterize another atlas mid-frame.
|
||||
* Slots the font has no glyph for are skipped.
|
||||
*/
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
@@ -256,49 +270,52 @@ public final class FontProviderCustom implements FontProvider {
|
||||
return chr;
|
||||
}
|
||||
final FontAtlas atlas = getAtlas(chr);
|
||||
final GlyphData target = atlas.glyphData[chr % ATLAS_SIZE];
|
||||
if (target == null) {
|
||||
if (!atlas.has(chr % ATLAS_SIZE)) {
|
||||
return chr;
|
||||
}
|
||||
final float targetAdvance = atlas.metrics[(chr % ATLAS_SIZE) * FontAtlas.STRIDE + FontAtlas.X_ADVANCE];
|
||||
final int atlasStart = (chr / ATLAS_SIZE) * ATLAS_SIZE;
|
||||
for (int attempt = 0; attempt < RANDOM_GLYPH_TRIES; attempt++) {
|
||||
final int slot = fontRandom.nextInt(ATLAS_SIZE);
|
||||
final GlyphData candidate = atlas.glyphData[slot];
|
||||
if (candidate != null && candidate.xAdvance == target.xAdvance) {
|
||||
if (atlas.has(slot) && atlas.metrics[slot * FontAtlas.STRIDE + FontAtlas.X_ADVANCE] == targetAdvance) {
|
||||
return (char) (atlasStart + slot);
|
||||
}
|
||||
}
|
||||
return chr;
|
||||
}
|
||||
|
||||
private float metric(char chr, int field) {
|
||||
return getAtlas(chr).metrics[(chr % ATLAS_SIZE) * FontAtlas.STRIDE + field];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
|
||||
return metric(chr, FontAtlas.U_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
|
||||
return metric(chr, FontAtlas.V_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
|
||||
return metric(chr, FontAtlas.X_ADVANCE) * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
|
||||
return metric(chr, FontAtlas.GLYPH_W) * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
|
||||
return metric(chr, FontAtlas.U_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
|
||||
return metric(chr, FontAtlas.V_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -308,7 +325,9 @@ public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
return getAtlas(chr).texture;
|
||||
final int id = chr / ATLAS_SIZE;
|
||||
final int texture = this.atlasTextures[id];
|
||||
return texture != 0 ? texture : uploadAtlas(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,10 +3,6 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public final class FontProviderMC implements FontProvider {
|
||||
@@ -114,14 +110,7 @@ public final class FontProviderMC implements FontProvider {
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
final TextureManager tm = Minecraft.getMinecraft().getTextureManager();
|
||||
ITextureObject tex = tm.getTexture(locationFontTexture);
|
||||
if (tex == null) {
|
||||
tex = new SimpleTexture(locationFontTexture);
|
||||
tm.loadTexture(locationFontTexture, tex);
|
||||
tex = tm.getTexture(locationFontTexture);
|
||||
}
|
||||
return tex.getGlTextureId();
|
||||
return FontStrategist.getIntFromResourceLocation(locationFontTexture);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,9 +3,6 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||
@@ -133,14 +130,7 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
|
||||
public int getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
ResourceLocation rl = getUnicodePageLocation(uniPage);
|
||||
final TextureManager tm = Minecraft.getMinecraft().getTextureManager();
|
||||
ITextureObject tex = tm.getTexture(rl);
|
||||
if (tex == null) {
|
||||
tex = new SimpleTexture(rl);
|
||||
tm.loadTexture(rl, tex);
|
||||
tex = tm.getTexture(rl);
|
||||
}
|
||||
return tex.getGlTextureId();
|
||||
return FontStrategist.getIntFromResourceLocation(rl);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,13 +2,17 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
@@ -19,9 +23,11 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -196,4 +202,34 @@ public class FontStrategist {
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to refer to either a texture or a resource location using a single integer. Int space allocation:
|
||||
* Integer.MIN_VALUE - reserved invalid texture
|
||||
* (Integer.MIN_VALUE, -1] - ResourceLocations, values -1, -2, -3 map to indices 0, 1, 2 etc. in the below structures
|
||||
* 0 - untextured (underline, strikethrough...)
|
||||
* [1, Integer.MAX_VALUE] - regular OpenGL texture IDs
|
||||
*/
|
||||
static final int INVALID_TEXTURE = Integer.MIN_VALUE;
|
||||
private static int remap(int n) { return -1 - n; } // involution
|
||||
|
||||
private static final List<ResourceLocation> intToResourceLocation = new ArrayList<>();
|
||||
private static final Map<ResourceLocation, Integer> resourceLocationToInt = new HashMap<>();
|
||||
|
||||
static int getIntFromResourceLocation(ResourceLocation rl) {
|
||||
Integer i = resourceLocationToInt.get(rl);
|
||||
if (i != null) { return i; }
|
||||
int idx = remap(intToResourceLocation.size());
|
||||
resourceLocationToInt.put(rl, idx);
|
||||
intToResourceLocation.add(rl);
|
||||
return idx;
|
||||
}
|
||||
|
||||
static void bindIntTexture(BatchingFontRenderer owner, int texture) {
|
||||
if (texture > 0) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
} else if (texture < 0) {
|
||||
((FontRendererAccessor) owner.underlying).angelica$bindTexture(intToResourceLocation.get(remap(texture)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Drawing text picks up settings from whoever asked for it, such as depth testing, culling, polygon offset and
|
||||
* alpha cutoff. Text is sometimes drawn later than requested, so these are saved with it to make the late draw
|
||||
* look the same as an immediate one.
|
||||
*/
|
||||
final class TextDrawState {
|
||||
private final ColorMask colorMaskScratch = new ColorMask();
|
||||
|
||||
boolean depthTest;
|
||||
int depthFunc;
|
||||
boolean depthMask;
|
||||
boolean colorRed;
|
||||
boolean colorGreen;
|
||||
boolean colorBlue;
|
||||
boolean colorAlpha;
|
||||
boolean cull;
|
||||
int cullFace;
|
||||
int frontFace;
|
||||
boolean polygonOffset;
|
||||
float offsetFactor;
|
||||
float offsetUnits;
|
||||
float alphaRef;
|
||||
|
||||
void captureLive() {
|
||||
depthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
depthFunc = GLStateManager.getDepthState().getFunc();
|
||||
depthMask = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
final ColorMask colorMask = GLStateManager.getEffectiveColorMask(colorMaskScratch);
|
||||
colorRed = colorMask.red;
|
||||
colorGreen = colorMask.green;
|
||||
colorBlue = colorMask.blue;
|
||||
colorAlpha = colorMask.alpha;
|
||||
final PolygonState polygon = GLStateManager.getPolygonState();
|
||||
cull = GLStateManager.getCullState().isEnabled();
|
||||
cullFace = polygon.getCullFaceMode();
|
||||
frontFace = polygon.getFrontFace();
|
||||
polygonOffset = GLStateManager.getPolygonOffsetFillState().isEnabled();
|
||||
offsetFactor = polygon.getOffsetFactor();
|
||||
offsetUnits = polygon.getOffsetUnits();
|
||||
alphaRef = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
|
||||
boolean sameAs(TextDrawState other) {
|
||||
return depthTest == other.depthTest
|
||||
&& depthFunc == other.depthFunc
|
||||
&& depthMask == other.depthMask
|
||||
&& colorRed == other.colorRed
|
||||
&& colorGreen == other.colorGreen
|
||||
&& colorBlue == other.colorBlue
|
||||
&& colorAlpha == other.colorAlpha
|
||||
&& cull == other.cull
|
||||
&& cullFace == other.cullFace
|
||||
&& frontFace == other.frontFace
|
||||
&& polygonOffset == other.polygonOffset
|
||||
&& offsetFactor == other.offsetFactor
|
||||
&& offsetUnits == other.offsetUnits
|
||||
&& alphaRef == other.alphaRef;
|
||||
}
|
||||
|
||||
void set(TextDrawState other) {
|
||||
depthTest = other.depthTest;
|
||||
depthFunc = other.depthFunc;
|
||||
depthMask = other.depthMask;
|
||||
colorRed = other.colorRed;
|
||||
colorGreen = other.colorGreen;
|
||||
colorBlue = other.colorBlue;
|
||||
colorAlpha = other.colorAlpha;
|
||||
cull = other.cull;
|
||||
cullFace = other.cullFace;
|
||||
frontFace = other.frontFace;
|
||||
polygonOffset = other.polygonOffset;
|
||||
offsetFactor = other.offsetFactor;
|
||||
offsetUnits = other.offsetUnits;
|
||||
alphaRef = other.alphaRef;
|
||||
}
|
||||
|
||||
void apply() {
|
||||
if (depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(depthFunc);
|
||||
GLStateManager.glDepthMask(depthMask);
|
||||
GLStateManager.glColorMask(colorRed, colorGreen, colorBlue, colorAlpha);
|
||||
if (cull) GLStateManager.enableCull(); else GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(cullFace);
|
||||
GLStateManager.glFrontFace(frontFace);
|
||||
if (polygonOffset) GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL); else GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(offsetFactor, offsetUnits);
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.helpers.ItemBlockLight;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.PrimedEntityAccessor;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import mods.battlegear2.api.core.IBattlePlayer;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
@@ -586,10 +586,7 @@ public class DynamicLights {
|
||||
if (item instanceof IDynamicLightProducer lightProducer) {
|
||||
return lightProducer.getLuminance(stack);
|
||||
} else if (item instanceof ItemBlock itemBlock) {
|
||||
final Block block = itemBlock.field_150939_a;
|
||||
if (block != null) {
|
||||
return block.getLightValue();
|
||||
}
|
||||
return ItemBlockLight.getLightValue(itemBlock, stack);
|
||||
}
|
||||
|
||||
if (item == Items.lava_bucket) return Blocks.lava.getLightValue();
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.gtnewhorizons.angelica.helpers;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
/**
|
||||
* Light value of the block an ItemBlock stack would place.
|
||||
*/
|
||||
public final class ItemBlockLight {
|
||||
|
||||
private static final SingleBlockAccess ACCESS = new SingleBlockAccess();
|
||||
|
||||
private ItemBlockLight() {}
|
||||
|
||||
public static int getLightValue(ItemBlock item, ItemStack stack) {
|
||||
final Block block = item.field_150939_a;
|
||||
if (block == null) return 0;
|
||||
ACCESS.set(block, item.getMetadata(stack.getItemDamage()));
|
||||
try {
|
||||
return block.getLightValue(ACCESS, 0, 0, 0);
|
||||
} catch (RuntimeException e) {
|
||||
return block.getLightValue();
|
||||
} finally {
|
||||
ACCESS.set(Blocks.air, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SingleBlockAccess implements IBlockAccess {
|
||||
private Block block = Blocks.air;
|
||||
private int metadata;
|
||||
|
||||
void set(Block block, int metadata) {
|
||||
this.block = block;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
private static boolean isOrigin(int x, int y, int z) {
|
||||
return x == 0 && y == 0 && z == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock(int x, int y, int z) {
|
||||
return isOrigin(x, y, z) ? block : Blocks.air;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockMetadata(int x, int y, int z) {
|
||||
return isOrigin(x, y, z) ? metadata : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity getTileEntity(int x, int y, int z) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightBrightnessForSkyBlocks(int x, int y, int z, int min) {
|
||||
return min << 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isBlockProvidingPowerTo(int x, int y, int z, int direction) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAirBlock(int x, int y, int z) {
|
||||
return !isOrigin(x, y, z) || block == Blocks.air;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeGenBase getBiomeGenForCoords(int x, int z) {
|
||||
return BiomeGenBase.plains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean extendedLevelsInChunkCache() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSideSolid(int x, int y, int z, ForgeDirection side, boolean _default) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,30 @@
|
||||
package com.gtnewhorizons.angelica.hudcaching;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.CustomFramebuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.SharedDepthFramebuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.holoinventory.HoloInventoryReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.event.ClientEvent;
|
||||
import com.gtnewhorizons.angelica.event.ClientEventType;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.GuiIngameAccessor;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.GuiIngameForgeAccessor;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.RenderGameOverlayEventAccessor;
|
||||
import com.gtnewhorizons.angelica.render.PanoramaRenderer;
|
||||
import com.kentington.thaumichorizons.common.ThaumicHorizons;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Gui;
|
||||
import net.minecraft.client.gui.GuiIngame;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraftforge.client.GuiIngameForge;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import thaumcraft.common.Thaumcraft;
|
||||
import xaero.common.core.XaeroMinimapCore;
|
||||
|
||||
@@ -35,13 +33,20 @@ import xaero.common.core.XaeroMinimapCore;
|
||||
public class HUDCaching {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||
public static SharedDepthFramebuffer framebuffer;
|
||||
public static CustomFramebuffer framebuffer;
|
||||
private static boolean dirty = true;
|
||||
private static long nextHudRefresh;
|
||||
|
||||
private static IVertexArrayObject quadVAO;
|
||||
private static float quadWidth;
|
||||
private static float quadHeight;
|
||||
private static final RetainedState hudExitState = new RetainedState();
|
||||
private static StateSet allState;
|
||||
private static GlProgram<Object> compositeProgram;
|
||||
private static int compositeVao;
|
||||
|
||||
private static final Tracy.ZoneId Z_CACHE_WRAP = Tracy.zoneId("hudCacheWrap", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_DRAW = Tracy.zoneId("hudDraw", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_CAPTURED_BITS = Tracy.zoneId("hudCapturedBits", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_COMPOSITE = Tracy.zoneId("hudComposite", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_APPLY_RETAINED = Tracy.zoneId("hudApplyRetained", Tracy.COLOR_CLIENT);
|
||||
|
||||
public static boolean renderingCacheOverride;
|
||||
|
||||
@@ -61,16 +66,79 @@ public class HUDCaching {
|
||||
private final static RenderGameOverlayEvent fakeTextEvent = new RenderGameOverlayEvent.Text(new RenderGameOverlayEvent(0, null, 0, 0), null, null);
|
||||
private final static RenderGameOverlayEvent.Post fakePostEvent = new RenderGameOverlayEvent.Post(new RenderGameOverlayEvent(0, null, 0, 0), RenderGameOverlayEvent.ElementType.HELMET);
|
||||
|
||||
private static ScaledResolution resolution;
|
||||
private static int resolutionWidth, resolutionHeight, resolutionGuiScale;
|
||||
private static boolean resolutionUnicode;
|
||||
|
||||
private static ScaledResolution resolution() {
|
||||
final int width = mc.displayWidth, height = mc.displayHeight, guiScale = mc.gameSettings.guiScale;
|
||||
final boolean unicode = mc.func_152349_b();
|
||||
if (resolution == null || width != resolutionWidth || height != resolutionHeight || guiScale != resolutionGuiScale || unicode != resolutionUnicode) {
|
||||
resolutionWidth = width;
|
||||
resolutionHeight = height;
|
||||
resolutionGuiScale = guiScale;
|
||||
resolutionUnicode = unicode;
|
||||
resolution = new ScaledResolution(mc, width, height);
|
||||
}
|
||||
return resolution;
|
||||
}
|
||||
|
||||
private static void setupOverlay(ScaledResolution scaled) {
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glOrtho(0.0D, scaled.getScaledWidth_double(), scaled.getScaledHeight_double(), 0.0D, 1000.0D, 3000.0D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glTranslatef(0.0F, 0.0F, -2000.0F);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
framebuffer = new SharedDepthFramebuffer(CustomFramebuffer.STENCIL_BUFFER);
|
||||
framebuffer = new CustomFramebuffer(CustomFramebuffer.DEPTH_TEXTURE | CustomFramebuffer.STENCIL_BUFFER);
|
||||
}
|
||||
|
||||
public static void resetAfterCrash() {
|
||||
renderingCacheOverride = false;
|
||||
GLSMConfig.hudCacheOverride = false;
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
private static void initComposite() {
|
||||
compositeProgram = PanoramaRenderer.loadProgram("angelica:hud_composite", "angelica:panorama_blur", "angelica:hud_composite", ctx -> ctx);
|
||||
compositeProgram.bind();
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(compositeProgram.handle(), "u_Depth"), 1);
|
||||
compositeProgram.unbind();
|
||||
compositeVao = GLStateManager.glGenVertexArrays();
|
||||
}
|
||||
|
||||
private static void composite() {
|
||||
if (compositeProgram == null) initComposite();
|
||||
final int program = GLStateManager.getActiveProgram();
|
||||
final int vao = GLStateManager.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
|
||||
final int stateDepth = GLStateManager.pushState(allState);
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.disableScissorTest();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, framebuffer.depthAttachment);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, framebuffer.framebufferTexture);
|
||||
compositeProgram.bind();
|
||||
GLStateManager.glBindVertexArray(compositeVao);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
} finally {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.popStateTo(stateDepth);
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
|
||||
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
|
||||
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
|
||||
@@ -94,31 +162,45 @@ public class HUDCaching {
|
||||
if (System.currentTimeMillis() > nextHudRefresh) {
|
||||
dirty = true;
|
||||
}
|
||||
if (allState == null) allState = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
|
||||
|
||||
if (dirty) {
|
||||
dirty = false;
|
||||
nextHudRefresh = System.currentTimeMillis() + (1000 / AngelicaConfig.hudCachingFPS);
|
||||
if (framebuffer.framebufferWidth != mc.displayWidth || framebuffer.framebufferHeight != mc.displayHeight) {
|
||||
framebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
|
||||
} else {
|
||||
framebuffer.clearBindFramebuffer();
|
||||
}
|
||||
renderingCacheOverride = true;
|
||||
GLSMConfig.hudCacheOverride = true;
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CACHE_WRAP);
|
||||
final int outerDepth = GLStateManager.pushState(allState);
|
||||
try {
|
||||
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
|
||||
if (framebuffer.framebufferWidth != mc.displayWidth || framebuffer.framebufferHeight != mc.displayHeight) {
|
||||
framebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
|
||||
} else {
|
||||
framebuffer.clearBindFramebuffer();
|
||||
}
|
||||
final int hudDepth = GLStateManager.pushState(allState);
|
||||
renderingCacheOverride = true;
|
||||
GLStateManager.setHudCacheOverride(true);
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_DRAW);
|
||||
try {
|
||||
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
GLStateManager.popStateTo(hudDepth + 1);
|
||||
GLStateManager.retainModifiedState(hudDepth, hudExitState);
|
||||
} finally {
|
||||
renderingCacheOverride = false;
|
||||
GLSMConfig.hudCacheOverride = false;
|
||||
GLStateManager.setHudCacheOverride(false);
|
||||
GLStateManager.popStateTo(outerDepth);
|
||||
mc.getFramebuffer().bindFramebuffer(false);
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
} else {
|
||||
renderer.setupOverlayRendering();
|
||||
}
|
||||
|
||||
ScaledResolution resolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int width = resolution.getScaledWidth();
|
||||
int height = resolution.getScaledHeight();
|
||||
final ScaledResolution scaled = resolution();
|
||||
setupOverlay(scaled);
|
||||
int width = scaled.getScaledWidth();
|
||||
int height = scaled.getScaledHeight();
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CAPTURED_BITS);
|
||||
GLStateManager.enableBlend();
|
||||
|
||||
// reset the color that may be applied by some items
|
||||
@@ -135,7 +217,7 @@ public class HUDCaching {
|
||||
if (ingame instanceof GuiIngameForge) {
|
||||
GuiIngameForgeAccessor guiForge = ((GuiIngameForgeAccessor) ingame);
|
||||
if (renderHelmetCaptured) {
|
||||
guiForge.callRenderHelmet(resolution, partialTicks, hasScreen, mouseX, mouseY);
|
||||
guiForge.callRenderHelmet(scaled, partialTicks, hasScreen, mouseX, mouseY);
|
||||
if (ModStatus.isHoloInventoryLoaded) {
|
||||
HoloInventoryReflectionCompat.setAngelicaOverride(false);
|
||||
// only settings the partial ticks as mouseX and mouseY are not used in renderEvent
|
||||
@@ -155,7 +237,7 @@ public class HUDCaching {
|
||||
}
|
||||
if (ModStatus.isThaumcraftLoaded || ModStatus.isThaumicHorizonsLoaded) {
|
||||
((RenderGameOverlayEventAccessor) fakeTextEvent).setPartialTicks(partialTicks);
|
||||
((RenderGameOverlayEventAccessor) fakeTextEvent).setResolution(resolution);
|
||||
((RenderGameOverlayEventAccessor) fakeTextEvent).setResolution(scaled);
|
||||
((RenderGameOverlayEventAccessor) fakeTextEvent).setMouseX(mouseX);
|
||||
((RenderGameOverlayEventAccessor) fakeTextEvent).setMouseY(mouseY);
|
||||
if (ModStatus.isThaumcraftLoaded) {
|
||||
@@ -174,15 +256,14 @@ public class HUDCaching {
|
||||
}
|
||||
}
|
||||
|
||||
// render cached frame
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
framebuffer.bindFramebufferTexture();
|
||||
drawTexturedRect((float) resolution.getScaledWidth_double(), (float) resolution.getScaledHeight_double());
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
mc.getTextureManager().bindTexture(Gui.icons);
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_COMPOSITE);
|
||||
composite();
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_APPLY_RETAINED);
|
||||
GLStateManager.applyRetainedState(hudExitState);
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
|
||||
ClientEvent.post(ClientEventType.POST_RENDER_HUD, renderer, partialTicks);
|
||||
}
|
||||
@@ -200,35 +281,6 @@ public class HUDCaching {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private static void drawTexturedRect(float width, float height) {
|
||||
if (quadWidth != width || quadHeight != height) {
|
||||
quadWidth = width;
|
||||
quadHeight = height;
|
||||
rebuildVAO(width, height);
|
||||
}
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableTexture();
|
||||
quadVAO.render();
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
|
||||
private static void rebuildVAO(float width, float height) {
|
||||
final DirectTessellator tessellator = TessellatorManager.startCapturingDirect(DefaultVertexFormat.POSITION_TEXTURE);
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.addVertexWithUV(0, height, 0.0, 0, 0);
|
||||
tessellator.addVertexWithUV(width, height, 0.0, 1, 0);
|
||||
tessellator.addVertexWithUV(width, 0, 0.0, 1, 1);
|
||||
tessellator.addVertexWithUV(0, 0, 0.0, 0, 1);
|
||||
tessellator.draw();
|
||||
if (quadVAO == null) {
|
||||
quadVAO = tessellator.uploadToVBO(VertexBufferType.MUTABLE);
|
||||
} else {
|
||||
tessellator.updateToVBO(quadVAO.getVBO());
|
||||
}
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
}
|
||||
|
||||
public static void disableHoloInventory() {
|
||||
if (ModStatus.isHoloInventoryLoaded) {
|
||||
HoloInventoryReflectionCompat.setAngelicaOverride(true);
|
||||
|
||||
+5
-1
@@ -254,9 +254,13 @@ public class DarkModeFontTransform {
|
||||
),
|
||||
//endregion
|
||||
//region Exclude Methods
|
||||
// Applied Energistics 2
|
||||
// Applied Energistics 2
|
||||
RecolorTarget.excludeMethod(
|
||||
new MethodInfo("appeng.client.render.StackSizeRenderer drawStackSize(IILjava/lang/String;Lnet/minecraft/client/gui/FontRenderer;Lappeng/api/config/TerminalFontSize;)V")
|
||||
),
|
||||
// AE2 FluidCraft-Rework
|
||||
RecolorTarget.excludeMethod(
|
||||
new MethodInfo("com.glodblock.github.client.gui.GuiLevelTerminal drawReadableAmount(Lnet/minecraft/client/gui/FontRenderer;JZI)V")
|
||||
),
|
||||
// GregTech
|
||||
RecolorTarget.excludeMethodCall(
|
||||
|
||||
@@ -94,7 +94,7 @@ public class PanoramaRenderer {
|
||||
blurProgram.unbind();
|
||||
}
|
||||
|
||||
private static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, Function<ShaderBindingContext, T> factory) {
|
||||
public static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, Function<ShaderBindingContext, T> factory) {
|
||||
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertBase + ".vert", ShaderConstants.EMPTY);
|
||||
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", ShaderConstants.EMPTY);
|
||||
try {
|
||||
|
||||
@@ -73,9 +73,6 @@ public final class SelectionBoxRenderer {
|
||||
public static void draw(AxisAlignedBB aabb, int color) {
|
||||
if (program == null || vao == null) return;
|
||||
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
|
||||
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);
|
||||
|
||||
@@ -63,6 +63,7 @@ final class PacerCore {
|
||||
private boolean locked;
|
||||
private boolean probing;
|
||||
private boolean probeExhausted;
|
||||
private boolean gateAbandoned;
|
||||
private long probeLeadNanos;
|
||||
private boolean haveCandidate;
|
||||
private long candidateEndNanos;
|
||||
@@ -336,7 +337,7 @@ final class PacerCore {
|
||||
if (locked && framesSinceCandidate >= LOCK_FRAMES) dropLock();
|
||||
return;
|
||||
}
|
||||
if (haveCandidate && framesSinceCandidate <= LOCK_WINDOW) {
|
||||
if (!gateAbandoned && haveCandidate && framesSinceCandidate <= LOCK_WINDOW) {
|
||||
final long delta = sampleEnd - candidateEndNanos;
|
||||
final long k = (delta + refreshPeriodNanos / 2) / refreshPeriodNanos;
|
||||
if (k >= 1L && Math.abs(delta - k * refreshPeriodNanos) <= refreshPeriodNanos / LOCK_TOL_DIVISOR) {
|
||||
@@ -347,6 +348,7 @@ final class PacerCore {
|
||||
if (++inconsistentPairs >= LOCK_WINDOW) {
|
||||
dropLock();
|
||||
probeExhausted = true;
|
||||
gateAbandoned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,6 +360,7 @@ final class PacerCore {
|
||||
private void armProbe() {
|
||||
probeLeadNanos = 0L;
|
||||
probeExhausted = false;
|
||||
gateAbandoned = false;
|
||||
}
|
||||
|
||||
private void dropLock() {
|
||||
|
||||
+4
@@ -119,6 +119,10 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
return this.renderSectionManager;
|
||||
}
|
||||
|
||||
public WorldClient getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
private long asyncEwmaNanos;
|
||||
private long uploadEwmaNanos;
|
||||
|
||||
|
||||
@@ -43,6 +43,16 @@ public final class BakedTransformCapture extends DirectTessellator {
|
||||
|
||||
@Override
|
||||
protected int interceptDraw(Tessellator tessellator) {
|
||||
transform = delta.deltaOrNull(deltaMatrix);
|
||||
try {
|
||||
return super.interceptDraw(tessellator);
|
||||
} finally {
|
||||
transform = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int draw() {
|
||||
final Color4 color = GLStateManager.getColor();
|
||||
final int packed = ColorABGR.pack(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
if (!sawRun) {
|
||||
@@ -51,12 +61,7 @@ public final class BakedTransformCapture extends DirectTessellator {
|
||||
} else if (packed != firstColor) {
|
||||
colorUniform = false;
|
||||
}
|
||||
transform = delta.deltaOrNull(deltaMatrix);
|
||||
try {
|
||||
return super.interceptDraw(tessellator);
|
||||
} finally {
|
||||
transform = null;
|
||||
}
|
||||
return super.draw();
|
||||
}
|
||||
|
||||
// While capturing, Tessellator.instance is this tessellator, so vanilla code (e.g. RenderBlocks.renderBlockAsItem)
|
||||
|
||||
@@ -25,6 +25,11 @@ public final class BatchDrawDefaults {
|
||||
passFramebuffer = GLStateManager.getDrawFramebuffer();
|
||||
}
|
||||
|
||||
public static boolean drawsToPassFramebuffer() {
|
||||
return passFramebuffer < 0 || TesrBatchRenderer.deferredPipeline() != null
|
||||
|| GLStateManager.getDrawFramebuffer() == passFramebuffer;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
if (passFramebuffer >= 0 && TesrBatchRenderer.deferredPipeline() == null) {
|
||||
final int framebuffer = GLStateManager.getDrawFramebuffer();
|
||||
|
||||
@@ -3,7 +3,6 @@ package jss.notfine.gui;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiListExtended;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
@@ -19,7 +18,7 @@ public class GuiCustomMenu extends GuiScreen {
|
||||
private final OptionPage[] subPages;
|
||||
protected String screenTitle;
|
||||
|
||||
private GuiListExtended optionsRowList;
|
||||
private GuiCustomOptionsRowList optionsRowList;
|
||||
|
||||
public GuiCustomMenu(GuiScreen parentGuiScreen, OptionPage optionPage, OptionPage... subPages) {
|
||||
this.parentGuiScreen = parentGuiScreen;
|
||||
@@ -30,6 +29,7 @@ public class GuiCustomMenu extends GuiScreen {
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
buttonList.clear();
|
||||
buttonList.add(new GuiButton(200, width / 2 - 110, height - 27, I18n.format("gui.done")));
|
||||
optionsRowList = new GuiCustomOptionsRowList(mc, width, height, 32, height - 32, 25, optionPage, subPages);
|
||||
@@ -88,9 +88,15 @@ public class GuiCustomMenu extends GuiScreen {
|
||||
if(keyCode == Keyboard.KEY_ESCAPE && !(parentGuiScreen instanceof GuiCustomMenu)) {
|
||||
saveChanges();
|
||||
}
|
||||
optionsRowList.keyTyped(keyCode);
|
||||
super.keyTyped(typedChar, keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
Keyboard.enableRepeatEvents(false);
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
for(OptionStorage<?> storage : dirtyStorages) {
|
||||
storage.save();
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import jss.notfine.gui.options.control.element.NotFineControlElementFactory;
|
||||
import jss.notfine.gui.options.control.element.SliderControlElement;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
|
||||
@@ -20,6 +21,7 @@ import java.util.List;
|
||||
public class GuiCustomOptionsRowList extends GuiListExtended {
|
||||
private static final NotFineControlElementFactory factory = new NotFineControlElementFactory();
|
||||
private final List<Row> settingsList = Lists.newArrayList();
|
||||
private GuiButton focusedButton;
|
||||
|
||||
public GuiCustomOptionsRowList(Minecraft mc, int width, int height, int top, int bottom, int slotHeight, OptionPage optionPage, OptionPage... subPages) {
|
||||
super(mc, width, height, top, bottom, slotHeight);
|
||||
@@ -64,8 +66,14 @@ public class GuiCustomOptionsRowList extends GuiListExtended {
|
||||
return super.getScrollBarX() + 32;
|
||||
}
|
||||
|
||||
public void keyTyped(int keyCode) {
|
||||
if(focusedButton instanceof SliderControlElement slider) {
|
||||
slider.keyTyped(keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static class Row implements GuiListExtended.IGuiListEntry {
|
||||
public class Row implements GuiListExtended.IGuiListEntry {
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final GuiButton buttonOne, buttonTwo;
|
||||
|
||||
@@ -93,9 +101,11 @@ public class GuiCustomOptionsRowList extends GuiListExtended {
|
||||
@Override
|
||||
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) {
|
||||
if(buttonOne.mousePressed(mc, x, y)) {
|
||||
focusedButton = buttonOne;
|
||||
return true;
|
||||
}
|
||||
if(buttonTwo != null && buttonTwo.mousePressed(mc, x, y)) {
|
||||
focusedButton = buttonTwo;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.jellysquid.mods.sodium.client.util.Dim2i;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public class SliderControlElement extends NotFineControlElement<Integer> {
|
||||
private final ControlValueFormatter formatter;
|
||||
@@ -68,6 +69,29 @@ public class SliderControlElement extends NotFineControlElement<Integer> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean keyTyped(int keyCode) {
|
||||
if(!option.isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int step;
|
||||
if(keyCode == Keyboard.KEY_LEFT) {
|
||||
step = -interval;
|
||||
} else if(keyCode == Keyboard.KEY_RIGHT) {
|
||||
step = interval;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int stepped = MathHelper.clamp_int(option.getValue() + step, min, max);
|
||||
if(stepped != option.getValue()) {
|
||||
option.setValue(stepped);
|
||||
value = MathHelper.clamp_float((float)(stepped - min) / (max - min), 0f, 1f);
|
||||
onOptionValueChanged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateSlider(int mouseX) {
|
||||
value = (float)(mouseX - (xPosition + 4)) / (float)(width - 8);
|
||||
//Clamp normalized value
|
||||
|
||||
+13
@@ -78,6 +78,7 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
this.frame = this.parentFrameBuilder().build();
|
||||
this.children.add(this.frame);
|
||||
|
||||
@@ -287,6 +288,18 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
final boolean onSearchField = this.searchTextField.isMouseOver(mouseX, mouseY);
|
||||
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
|
||||
if (!onSearchField) {
|
||||
this.searchTextField.setFocused(false);
|
||||
this.setFocused(this.frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldCloseOnEsc() {
|
||||
return !this.hasPendingChanges;
|
||||
|
||||
+5
@@ -154,6 +154,11 @@ public class SearchTextFieldComponent extends AbstractWidget {
|
||||
return this.isFocused();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMouseOver(double mouseX, double mouseY) {
|
||||
return this.dim.containsCursor(mouseX, mouseY);
|
||||
}
|
||||
|
||||
private void drawSelectionHighlight(int x1, int y1, int x2, int y2) {
|
||||
int i;
|
||||
if (x1 < x2) {
|
||||
|
||||
@@ -54,6 +54,7 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
|
||||
protected boolean hasPendingChanges;
|
||||
protected ControlElement<?> hoveredElement;
|
||||
private Element focusedElement;
|
||||
|
||||
protected OptionPage shaderPacks;
|
||||
|
||||
@@ -97,13 +98,20 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
this.rebuildGUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
Keyboard.enableRepeatEvents(false);
|
||||
}
|
||||
|
||||
protected void rebuildGUI() {
|
||||
this.controls.clear();
|
||||
this.children.clear();
|
||||
this.drawable.clear();
|
||||
this.focusedElement = null;
|
||||
|
||||
if (this.currentPage == null) {
|
||||
if (this.pages.isEmpty()) {
|
||||
@@ -298,6 +306,10 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
SodiumGameOptionPages.quality(), SodiumGameOptionPages.advanced(), SodiumGameOptionPages.performance(),
|
||||
SodiumGameOptionPages.appearance()));
|
||||
}
|
||||
|
||||
if (this.focusedElement != null) {
|
||||
this.focusedElement.keyTyped(typedChar, keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldCloseOnEsc() {
|
||||
@@ -314,7 +326,11 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
|
||||
this.children.forEach(element -> element.mouseClicked(mouseX, mouseY, mouseButton));
|
||||
this.children.forEach(element -> {
|
||||
if (element.mouseClicked(mouseX, mouseY, mouseButton)) {
|
||||
this.focusedElement = element;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+25
@@ -7,6 +7,7 @@ import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatte
|
||||
import me.jellysquid.mods.sodium.client.gui.utils.Rect2i;
|
||||
import me.jellysquid.mods.sodium.client.util.Dim2i;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public class SodiumControlElementFactory implements ControlElementFactory {
|
||||
@Override
|
||||
@@ -194,6 +195,30 @@ public class SodiumControlElementFactory implements ControlElementFactory {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyTyped(char typedChar, int keyCode) {
|
||||
if (!this.option.isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int step;
|
||||
if (keyCode == Keyboard.KEY_LEFT) {
|
||||
step = -this.interval;
|
||||
} else if (keyCode == Keyboard.KEY_RIGHT) {
|
||||
step = this.interval;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int value = MathHelper.clamp_int(this.option.getValue() + step, this.min, this.min + this.range);
|
||||
|
||||
if (this.option.getValue() != value) {
|
||||
this.option.setValue(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TickBoxControlElement extends SodiumControlElement<Boolean> {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
|
||||
import com.gtnewhorizons.angelica.iris.ImmediateExtendedAttribs;
|
||||
@@ -39,6 +40,7 @@ import net.coderbot.iris.pipeline.transform.TransformPatcher;
|
||||
import net.coderbot.iris.shaderpack.OptionalBoolean;
|
||||
import net.coderbot.iris.shaderpack.ProgramSet;
|
||||
import net.coderbot.iris.shaderpack.ShaderPack;
|
||||
import net.coderbot.iris.shaderpack.StringPair;
|
||||
import net.coderbot.iris.shaderpack.discovery.ShaderpackDirectoryManager;
|
||||
import net.coderbot.iris.shaderpack.option.OptionSet;
|
||||
import net.coderbot.iris.shaderpack.option.Profile;
|
||||
@@ -48,6 +50,7 @@ import net.coderbot.iris.texture.pbr.PBRTextureManager;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.coderbot.iris.uniforms.PerFrameUniformBlockHarvester;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.LoadingScreenRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
@@ -63,6 +66,7 @@ import org.lwjgl.input.Keyboard;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.FileSystems;
|
||||
@@ -74,6 +78,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
@@ -101,6 +106,7 @@ public class Iris {
|
||||
private static CompletableFuture<Void> programSetWarmup;
|
||||
@Getter
|
||||
private static String currentPackName;
|
||||
private static String currentPackSettings;
|
||||
@Getter
|
||||
private static boolean initialized;
|
||||
@Getter
|
||||
@@ -319,9 +325,8 @@ public class Iris {
|
||||
// the player is in the overworld.
|
||||
// See: https://github.com/IrisShaders/Iris/issues/323
|
||||
lastDimensionName = "Overworld";
|
||||
compileEveryShaderFolder();
|
||||
Iris.getPipelineManager().preparePipeline("Overworld");
|
||||
|
||||
BlockRenderingSettings.INSTANCE.reloadRendererIfRequired();
|
||||
}
|
||||
|
||||
public static void toggleShaders(Minecraft minecraft, boolean enabled) throws IOException {
|
||||
@@ -493,7 +498,8 @@ public class Iris {
|
||||
resetShaderPackOptions = false;
|
||||
|
||||
try {
|
||||
currentPack = new ShaderPack(shaderPackPath, changedConfigs, StandardMacros.createStandardEnvironmentDefines());
|
||||
final Iterable<StringPair> environmentDefines = StandardMacros.createStandardEnvironmentDefines();
|
||||
currentPack = new ShaderPack(shaderPackPath, changedConfigs, environmentDefines);
|
||||
|
||||
final MutableOptionValues changedConfigsValues = currentPack.getShaderPackOptions().getOptionValues().mutableCopy();
|
||||
|
||||
@@ -503,6 +509,7 @@ public class Iris {
|
||||
changedConfigsValues.getStringValues().forEach(configsToSave::setProperty);
|
||||
|
||||
tryUpdateConfigPropertiesFile(shaderPackConfigTxt, configsToSave);
|
||||
currentPackSettings = describeSettings(name, shaderPackRoot, configsToSave, environmentDefines);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load the shaderpack \"{}\"!", name);
|
||||
logger.error("", e);
|
||||
@@ -512,6 +519,7 @@ public class Iris {
|
||||
|
||||
fallback = false;
|
||||
currentPackName = name;
|
||||
ProgramBinaryCache.usePack(name);
|
||||
|
||||
logger.info("Using shaderpack: " + name);
|
||||
|
||||
@@ -544,6 +552,8 @@ public class Iris {
|
||||
|
||||
private static void setShadersDisabled() {
|
||||
currentPack = null;
|
||||
currentPackSettings = null;
|
||||
ProgramBinaryCache.usePack(null);
|
||||
fallback = false;
|
||||
currentPackName = "(off)";
|
||||
|
||||
@@ -680,12 +690,12 @@ public class Iris {
|
||||
// Drop the stale program probe and cached item geometry
|
||||
ImmediateExtendedAttribs.onShaderPackChanged();
|
||||
|
||||
compileEveryShaderFolder();
|
||||
|
||||
// Very important - we need to re-create the pipeline straight away.
|
||||
// https://github.com/IrisShaders/Iris/issues/1330
|
||||
if (Minecraft.getMinecraft().theWorld != null) {
|
||||
Iris.getPipelineManager().preparePipeline(Iris.getCurrentDimensionName());
|
||||
|
||||
BlockRenderingSettings.INSTANCE.reloadRendererIfRequired();
|
||||
}
|
||||
|
||||
if (loadedIncompatiblePack() && Minecraft.getMinecraft().thePlayer != null) {
|
||||
@@ -701,6 +711,8 @@ public class Iris {
|
||||
private static void destroyEverything() {
|
||||
awaitProgramSetWarmup();
|
||||
currentPack = null;
|
||||
currentPackSettings = null;
|
||||
ProgramBinaryCache.usePack(null);
|
||||
|
||||
getPipelineManager().destroyPipeline();
|
||||
PBRTextureManager.INSTANCE.clear();
|
||||
@@ -759,6 +771,86 @@ public class Iris {
|
||||
}
|
||||
|
||||
|
||||
private static void compileEveryShaderFolder() {
|
||||
if (currentPack == null || !irisConfig.shouldSaveCompiledShaders()) return;
|
||||
if (currentPackSettings != null && ProgramBinaryCache.wasCompiledFor(currentPackSettings)) return;
|
||||
awaitProgramSetWarmup();
|
||||
|
||||
final Map<String, ProgramSet> programSets = currentPack.getEveryProgramSet();
|
||||
final LoadingScreenRenderer progress = Minecraft.getMinecraft().loadingScreen;
|
||||
if (progress != null) progress.resetProgressAndMessage(I18n.format("options.iris.saveCompiledShaders.progress"));
|
||||
boolean everyFolderCompiled = true;
|
||||
boolean loopFinished = false;
|
||||
ProgramBinaryCache.beginFullCompile();
|
||||
try {
|
||||
int i = 0;
|
||||
for (Map.Entry<String, ProgramSet> entry : programSets.entrySet()) {
|
||||
final String folder = entry.getKey() != null ? entry.getKey() : I18n.format("options.iris.saveCompiledShaders.baseFolder");
|
||||
if (progress != null) {
|
||||
progress.resetProgresAndWorkingMessage(I18n.format("options.iris.saveCompiledShaders.progressStep", i + 1, programSets.size(), folder));
|
||||
progress.setLoadingProgress(i * 100 / programSets.size());
|
||||
}
|
||||
i++;
|
||||
final ProgramSet programs = entry.getValue();
|
||||
try {
|
||||
shaderPackLoadId++;
|
||||
ShaderTransformExecutor.prepare();
|
||||
PerFrameUniformBlockHarvester.harvest(programs);
|
||||
final DeferredWorldRenderingPipeline pipeline = new DeferredWorldRenderingPipeline(programs);
|
||||
try {
|
||||
// Both are otherwise built lazily on first draw, after this pipeline is gone
|
||||
pipeline.compileInstancedVariants();
|
||||
if (IrisShaderProviderHolder.getProvider() instanceof IrisCeleritasShaderProvider terrain) {
|
||||
terrain.compileTerrainPrograms(pipeline.getCeleritasTerrainPipeline());
|
||||
}
|
||||
} finally {
|
||||
getPipelineManager().discardPipeline(pipeline);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
everyFolderCompiled = false;
|
||||
logger.error("Failed to compile shader folder '{}' for saving", folder, e);
|
||||
}
|
||||
}
|
||||
loopFinished = true;
|
||||
} finally {
|
||||
ProgramBinaryCache.finishFullCompile(loopFinished && everyFolderCompiled ? currentPackSettings : null);
|
||||
PerFrameUniformBlockHarvester.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String describeSettings(String packName, Path packRoot, Properties options, Iterable<StringPair> environmentDefines) {
|
||||
final StringBuilder settings = new StringBuilder(packName).append('\n');
|
||||
if (Files.isRegularFile(packRoot)) {
|
||||
try {
|
||||
settings.append(Files.size(packRoot)).append('|').append(Files.getLastModifiedTime(packRoot).toMillis());
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
} else if (Files.isDirectory(packRoot)) {
|
||||
try (Stream<Path> files = Files.walk(packRoot)) {
|
||||
files.filter(Files::isRegularFile).sorted().forEach(file -> {
|
||||
try {
|
||||
settings.append(packRoot.relativize(file)).append('|').append(Files.size(file)).append('|')
|
||||
.append(Files.getLastModifiedTime(file).toMillis()).append('\n');
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
} catch (IOException | UncheckedIOException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
settings.append('\n');
|
||||
new TreeMap<>(options).forEach((key, value) -> settings.append(key).append('=').append(value).append('\n'));
|
||||
for (StringPair define : environmentDefines) {
|
||||
settings.append('#').append(define.getKey()).append(' ').append(define.getValue()).append('\n');
|
||||
}
|
||||
return settings.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pipeline for a dimension using the dimension name from WorldProvider.getDimensionName().
|
||||
* Supports dimension.properties mappings with wildcard fallback.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.coderbot.iris.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.blending.BlendModeOverride;
|
||||
import net.coderbot.iris.gl.blending.BufferBlendOverride;
|
||||
@@ -13,12 +15,16 @@ import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderInterface;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class IrisCeleritasChunkProgramOverrides {
|
||||
private final EnumMap<IrisTerrainPass, GlProgram<IrisCeleritasChunkShaderInterface>> programs = new EnumMap<>(IrisTerrainPass.class);
|
||||
@@ -56,7 +62,48 @@ public class IrisCeleritasChunkProgramOverrides {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GlProgram<IrisCeleritasChunkShaderInterface> createShader(IrisTerrainPass pass, CeleritasTerrainPipeline pipeline, RenderPassConfiguration<?> configuration) {
|
||||
private static ProgramBinaryCache.Key cacheKey(CeleritasTerrainPipeline.PassInfo passInfo, ChunkVertexType vertexType) {
|
||||
if (!ProgramBinaryCache.isEnabled()) return null;
|
||||
final String vertex = passInfo.sources().get(PatchShaderType.VERTEX).orElse(null);
|
||||
final String geometry = passInfo.sources().get(PatchShaderType.GEOMETRY).orElse(null);
|
||||
final String fragment = passInfo.sources().get(PatchShaderType.FRAGMENT).orElse(null);
|
||||
if (vertex == null || fragment == null) return null;
|
||||
|
||||
final ProgramBinaryCache.Key key = ProgramBinaryCache.key();
|
||||
int attrIndex = 0;
|
||||
for (var attr : vertexType.getVertexFormat().getAttributes()) {
|
||||
key.attribute(attr.getName(), attrIndex++);
|
||||
}
|
||||
key.stage(ShaderType.VERTEX.id, vertex);
|
||||
if (geometry != null) key.stage(ShaderType.GEOM.id, geometry);
|
||||
key.stage(ShaderType.FRAGMENT.id, fragment);
|
||||
return key;
|
||||
}
|
||||
|
||||
private static final class SavedProgram extends GlProgram<IrisCeleritasChunkShaderInterface> {
|
||||
SavedProgram(int program, Function<ShaderBindingContext, IrisCeleritasChunkShaderInterface> factory) {
|
||||
super(program, factory);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GlProgram<IrisCeleritasChunkShaderInterface> createShader(IrisTerrainPass pass, CeleritasTerrainPipeline pipeline, ChunkVertexType vertexType) {
|
||||
final CeleritasTerrainPipeline.PassInfo passInfo = pipeline.getPassInfo(pass);
|
||||
final String name = "iris:celeritas-chunk-" + pass.getName();
|
||||
final BlendModeOverride blendOverride = passInfo.blendModeOverride();
|
||||
final List<BufferBlendOverride> bufferOverrides = passInfo.bufferBlendOverrides();
|
||||
final Function<ShaderBindingContext, IrisCeleritasChunkShaderInterface> factory = context -> new IrisCeleritasChunkShaderInterface(
|
||||
((GlObject) context).handle(), context, pipeline, pass.isShadow(), blendOverride, bufferOverrides, pipeline.getCustomUniforms());
|
||||
|
||||
final ProgramBinaryCache.Key cacheKey = cacheKey(passInfo, vertexType);
|
||||
if (cacheKey != null) {
|
||||
final int saved = ProgramBinaryCache.load(cacheKey);
|
||||
if (saved != 0) {
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, saved, name);
|
||||
return new SavedProgram(saved, factory);
|
||||
}
|
||||
}
|
||||
|
||||
final GlShader vertShader = createVertexShader(pass, pipeline);
|
||||
final GlShader geomShader = createGeometryShader(pass, pipeline);
|
||||
final GlShader fragShader = createFragmentShader(pass, pipeline);
|
||||
@@ -69,7 +116,7 @@ public class IrisCeleritasChunkProgramOverrides {
|
||||
}
|
||||
|
||||
try {
|
||||
final GlProgram.Builder builder = GlProgram.builder("iris:celeritas-chunk-" + pass.getName());
|
||||
final GlProgram.Builder builder = GlProgram.builder(name);
|
||||
|
||||
builder.attachShader(vertShader);
|
||||
if (geomShader != null) {
|
||||
@@ -78,17 +125,16 @@ public class IrisCeleritasChunkProgramOverrides {
|
||||
builder.attachShader(fragShader);
|
||||
|
||||
// Bind all attributes from the vertex format (includes base + Iris extended attributes)
|
||||
final var vertexType =pass.toTerrainPass(configuration).vertexType();
|
||||
int attrIndex = 0;
|
||||
for (var attr : vertexType.getVertexFormat().getAttributes()) {
|
||||
builder.bindAttribute(attr.getName(), attrIndex++);
|
||||
}
|
||||
|
||||
final CeleritasTerrainPipeline.PassInfo passInfo = pipeline.getPassInfo(pass);
|
||||
final BlendModeOverride blendOverride = passInfo.blendModeOverride();
|
||||
final List<BufferBlendOverride> bufferOverrides = passInfo.bufferBlendOverrides();
|
||||
|
||||
return builder.link(context -> new IrisCeleritasChunkShaderInterface(((GlObject) context).handle(), context, pipeline, pass.isShadow(), blendOverride, bufferOverrides, pipeline.getCustomUniforms()));
|
||||
final GlProgram<IrisCeleritasChunkShaderInterface> program = builder.link(factory);
|
||||
if (cacheKey != null) {
|
||||
ProgramBinaryCache.save(cacheKey, program.handle());
|
||||
}
|
||||
return program;
|
||||
} finally {
|
||||
vertShader.delete();
|
||||
if (geomShader != null) geomShader.delete();
|
||||
@@ -100,13 +146,21 @@ public class IrisCeleritasChunkProgramOverrides {
|
||||
* Create shaders for all Iris terrain passes.
|
||||
*/
|
||||
public void createShaders(CeleritasTerrainPipeline pipeline, RenderPassConfiguration<?> configuration) {
|
||||
createShaders(pipeline, pass -> pass.toTerrainPass(configuration).vertexType());
|
||||
}
|
||||
|
||||
public void createShaders(CeleritasTerrainPipeline pipeline, ChunkVertexType vertexType) {
|
||||
createShaders(pipeline, pass -> vertexType);
|
||||
}
|
||||
|
||||
private void createShaders(CeleritasTerrainPipeline pipeline, Function<IrisTerrainPass, ChunkVertexType> vertexTypeOf) {
|
||||
if (pipeline != null) {
|
||||
for (IrisTerrainPass pass : IrisTerrainPass.VALUES) {
|
||||
if (pass.isShadow() && !pipeline.hasShadowPass()) {
|
||||
this.programs.put(pass, null);
|
||||
continue;
|
||||
}
|
||||
this.programs.put(pass, createShader(pass, pipeline, configuration));
|
||||
this.programs.put(pass, createShader(pass, pipeline, vertexTypeOf.apply(pass)));
|
||||
}
|
||||
} else {
|
||||
deleteShaders();
|
||||
|
||||
@@ -70,6 +70,17 @@ public class IrisCeleritasShaderProvider implements IrisShaderProvider {
|
||||
overrides.deleteShaders();
|
||||
}
|
||||
|
||||
// A separate overrides instance, so the terrain programs in use are left alone
|
||||
public void compileTerrainPrograms(CeleritasTerrainPipeline pipeline) {
|
||||
if (pipeline == null) return;
|
||||
final IrisCeleritasChunkProgramOverrides scratch = new IrisCeleritasChunkProgramOverrides();
|
||||
try {
|
||||
scratch.createShaders(pipeline, EXTENDED_VERTEX_TYPE);
|
||||
} finally {
|
||||
scratch.deleteShaders();
|
||||
}
|
||||
}
|
||||
|
||||
public IrisCeleritasChunkProgramOverrides getOverrides() {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.coderbot.iris.config;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gui.option.IrisVideoSettings;
|
||||
|
||||
@@ -40,6 +41,8 @@ public class IrisConfig {
|
||||
|
||||
private boolean allowUnknownShaders;
|
||||
|
||||
private boolean saveCompiledShaders;
|
||||
|
||||
private final Path propertiesPath;
|
||||
|
||||
public IrisConfig(Path propertiesPath) {
|
||||
@@ -48,6 +51,7 @@ public class IrisConfig {
|
||||
disableUpdateMessage = false;
|
||||
enableDebugOptions = false;
|
||||
allowUnknownShaders = false;
|
||||
saveCompiledShaders = false;
|
||||
this.propertiesPath = propertiesPath;
|
||||
}
|
||||
|
||||
@@ -135,6 +139,16 @@ public class IrisConfig {
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean shouldSaveCompiledShaders() {
|
||||
return saveCompiledShaders;
|
||||
}
|
||||
|
||||
public void setSaveCompiledShaders(boolean save) throws IOException {
|
||||
this.saveCompiledShaders = save;
|
||||
ProgramBinaryCache.setEnabled(save);
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the config file and then populates the string, int, and boolean entries with the parsed entries
|
||||
*
|
||||
@@ -156,6 +170,8 @@ public class IrisConfig {
|
||||
enableDebugOptions = "true".equals(properties.getProperty("enableDebugOptions"));
|
||||
allowUnknownShaders = "true".equals(properties.getProperty("allowUnknownShaders"));
|
||||
disableUpdateMessage = "true".equals(properties.getProperty("disableUpdateMessage"));
|
||||
saveCompiledShaders = "true".equals(properties.getProperty("saveCompiledShaders"));
|
||||
ProgramBinaryCache.setEnabled(saveCompiledShaders);
|
||||
// TODO: GUI
|
||||
try {
|
||||
IrisVideoSettings.shadowDistance = Integer.parseInt(properties.getProperty("maxShadowRenderDistance", "32"));
|
||||
@@ -184,6 +200,7 @@ public class IrisConfig {
|
||||
properties.setProperty("enableDebugOptions", enableDebugOptions ? "true" : "false");
|
||||
properties.setProperty("allowUnknownShaders", allowUnknownShaders ? "true" : "false");
|
||||
properties.setProperty("disableUpdateMessage", disableUpdateMessage ? "true" : "false");
|
||||
properties.setProperty("saveCompiledShaders", saveCompiledShaders ? "true" : "false");
|
||||
properties.setProperty("maxShadowRenderDistance", String.valueOf(IrisVideoSettings.shadowDistance));
|
||||
// NB: This uses ISO-8859-1 with unicode escapes as the encoding
|
||||
try (OutputStream os = Files.newOutputStream(propertiesPath)) {
|
||||
|
||||
@@ -22,28 +22,35 @@ public class ShaderStorageBufferHolder {
|
||||
cachedWidth = width;
|
||||
cachedHeight = height;
|
||||
buffers = new ShaderStorageBuffer[Collections.max(overrides.keySet()) + 1];
|
||||
overrides.forEach((index, bufferInfo) -> {
|
||||
if (bufferInfo.size() > RenderSystem.getVRAM()) {
|
||||
throw new OutOfVideoMemoryError("We only have " + toMib(RenderSystem.getVRAM()) + "MiB of RAM to work with, but the pack is requesting " + bufferInfo.size() + "! Can't continue.");
|
||||
}
|
||||
try {
|
||||
overrides.forEach((index, bufferInfo) -> {
|
||||
if (bufferInfo.size() > RenderSystem.getVRAM()) {
|
||||
throw new OutOfVideoMemoryError("We only have " + toMib(RenderSystem.getVRAM()) + "MiB of RAM to work with, but the pack is requesting " + bufferInfo.size() + "! Can't continue.");
|
||||
}
|
||||
|
||||
if (index > SamplerLimits.get().getMaxShaderStorageUnits()) {
|
||||
throw new IllegalStateException("We don't have enough SSBO units??? (index: " + index + ", max: " + SamplerLimits.get().getMaxShaderStorageUnits());
|
||||
}
|
||||
if (index > SamplerLimits.get().getMaxShaderStorageUnits()) {
|
||||
throw new IllegalStateException("We don't have enough SSBO units??? (index: " + index + ", max: " + SamplerLimits.get().getMaxShaderStorageUnits());
|
||||
}
|
||||
|
||||
buffers[index] = new ShaderStorageBuffer(index, bufferInfo);
|
||||
int buffer = buffers[index].getId();
|
||||
buffers[index] = new ShaderStorageBuffer(index, bufferInfo);
|
||||
int buffer = buffers[index].getId();
|
||||
|
||||
if (bufferInfo.relative()) {
|
||||
buffers[index].resizeIfRelative(width, height);
|
||||
} else {
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, buffer);
|
||||
RenderSystem.bufferStorage(GL43.GL_SHADER_STORAGE_BUFFER, bufferInfo.size(), 0);
|
||||
RenderSystem.clearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 0, bufferInfo.size(), GL11.GL_RED, GL11.GL_BYTE, new int[]{0});
|
||||
RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, index, buffer);
|
||||
}
|
||||
});
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0);
|
||||
if (bufferInfo.relative()) {
|
||||
buffers[index].resizeIfRelative(width, height);
|
||||
} else {
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, buffer);
|
||||
RenderSystem.bufferStorage(GL43.GL_SHADER_STORAGE_BUFFER, bufferInfo.size(), 0);
|
||||
RenderSystem.clearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 0, bufferInfo.size(), GL11.GL_RED, GL11.GL_BYTE, new int[]{0});
|
||||
RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, index, buffer);
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
// The pipeline never receives this holder, so buffers created before the failure are freed here
|
||||
destroyBuffers();
|
||||
throw e;
|
||||
} finally {
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static long toMib(long x) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import net.coderbot.iris.gl.image.ImageHolder;
|
||||
import net.coderbot.iris.gl.sampler.GlSampler;
|
||||
import net.coderbot.iris.gl.sampler.SamplerHolder;
|
||||
@@ -43,6 +44,20 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
|
||||
public static ProgramBuilder begin(String name, @Nullable String vertexSource, @Nullable String geometrySource,
|
||||
@Nullable String tessControlSource, @Nullable String tessEvalSource,
|
||||
@Nullable String fragmentSource, ImmutableSet<Integer> reservedTextureUnits) {
|
||||
final ProgramBinaryCache.Key cacheKey = ProgramCreator.cacheKey();
|
||||
if (cacheKey != null) {
|
||||
cacheKey.stage(ShaderType.VERTEX.id, vertexSource);
|
||||
if (geometrySource != null) cacheKey.stage(ShaderType.GEOMETRY.id, geometrySource);
|
||||
if (tessControlSource != null) cacheKey.stage(ShaderType.TESSELATION_CONTROL.id, tessControlSource);
|
||||
if (tessEvalSource != null) cacheKey.stage(ShaderType.TESSELATION_EVAL.id, tessEvalSource);
|
||||
cacheKey.stage(ShaderType.FRAGMENT.id, fragmentSource);
|
||||
final int saved = ProgramCreator.load(name, cacheKey);
|
||||
if (saved != 0) {
|
||||
initializeFallbackUniforms(saved);
|
||||
return new ProgramBuilder(name, saved, reservedTextureUnits);
|
||||
}
|
||||
}
|
||||
|
||||
GlShader vertex = buildShader(ShaderType.VERTEX, name + ".vsh", vertexSource);
|
||||
GlShader geometry = geometrySource != null ? buildShader(ShaderType.GEOMETRY, name + ".gsh", geometrySource) : null;
|
||||
GlShader tessControl = tessControlSource != null ? buildShader(ShaderType.TESSELATION_CONTROL, name + ".tcs", tessControlSource) : null;
|
||||
@@ -56,7 +71,7 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
|
||||
if (tessEval != null) shaders.add(tessEval);
|
||||
shaders.add(fragment);
|
||||
|
||||
int programId = ProgramCreator.create(name, shaders.toArray(new GlShader[0]));
|
||||
int programId = ProgramCreator.create(name, cacheKey, shaders.toArray(new GlShader[0]));
|
||||
initializeFallbackUniforms(programId);
|
||||
|
||||
for (GlShader shader : shaders) {
|
||||
@@ -107,9 +122,18 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
|
||||
throw new IllegalStateException("This PC does not support compute shaders, but it's attempting to be used???");
|
||||
}
|
||||
|
||||
final ProgramBinaryCache.Key cacheKey = ProgramCreator.cacheKey();
|
||||
if (cacheKey != null) {
|
||||
cacheKey.stage(ShaderType.COMPUTE.id, source);
|
||||
final int saved = ProgramCreator.load(name, cacheKey);
|
||||
if (saved != 0) {
|
||||
return new ProgramBuilder(name, saved, reservedTextureUnits);
|
||||
}
|
||||
}
|
||||
|
||||
GlShader compute = buildShader(ShaderType.COMPUTE, name + ".csh", source);
|
||||
|
||||
int programId = ProgramCreator.create(name, compute);
|
||||
int programId = ProgramCreator.create(name, cacheKey, compute);
|
||||
|
||||
compute.destroy();
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
@@ -21,16 +23,38 @@ public class ProgramCreator {
|
||||
public static final int AT_TANGENT = ImmediateExtendedAttribHandler.LOC_TANGENT;
|
||||
public static final int AT_MIDBLOCK = ImmediateExtendedAttribHandler.LOC_MIDBLOCK;
|
||||
|
||||
// TODO: This is *really* hardcoded, we need to refactor this to support external calls to glBindAttribLocation
|
||||
private static final String[] ATTRIBUTE_NAMES = { "iris_Entity", "mc_Entity", "mc_midTexCoord", "iris_CubeTangent", "at_tangent", "at_midBlock" };
|
||||
private static final int[] ATTRIBUTE_LOCATIONS = { MC_ENTITY, MC_ENTITY, MC_MID_TEX_COORD, AT_TANGENT, AT_TANGENT, AT_MIDBLOCK };
|
||||
|
||||
@Nullable
|
||||
public static ProgramBinaryCache.Key cacheKey() {
|
||||
if (!ProgramBinaryCache.isEnabled()) return null;
|
||||
final ProgramBinaryCache.Key key = ProgramBinaryCache.key();
|
||||
for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) {
|
||||
key.attribute(ATTRIBUTE_NAMES[i], ATTRIBUTE_LOCATIONS[i]);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public static int load(String name, ProgramBinaryCache.Key cacheKey) {
|
||||
final int program = ProgramBinaryCache.load(cacheKey);
|
||||
if (program != 0) {
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, name);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
public static int create(String name, GlShader... shaders) {
|
||||
return create(name, null, shaders);
|
||||
}
|
||||
|
||||
public static int create(String name, @Nullable ProgramBinaryCache.Key cacheKey, GlShader... shaders) {
|
||||
int program = GLStateManager.glCreateProgram();
|
||||
|
||||
// TODO: This is *really* hardcoded, we need to refactor this to support external calls to glBindAttribLocation
|
||||
RenderSystem.bindAttributeLocation(program, MC_ENTITY, "iris_Entity");
|
||||
RenderSystem.bindAttributeLocation(program, MC_ENTITY, "mc_Entity");
|
||||
RenderSystem.bindAttributeLocation(program, MC_MID_TEX_COORD, "mc_midTexCoord");
|
||||
RenderSystem.bindAttributeLocation(program, AT_TANGENT, "iris_CubeTangent");
|
||||
RenderSystem.bindAttributeLocation(program, AT_TANGENT, "at_tangent");
|
||||
RenderSystem.bindAttributeLocation(program, AT_MIDBLOCK, "at_midBlock");
|
||||
for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) {
|
||||
RenderSystem.bindAttributeLocation(program, ATTRIBUTE_LOCATIONS[i], ATTRIBUTE_NAMES[i]);
|
||||
}
|
||||
|
||||
for (GlShader shader : shaders) {
|
||||
GLStateManager.glAttachShader(program, shader.getHandle());
|
||||
@@ -57,6 +81,10 @@ public class ProgramCreator {
|
||||
throw new RuntimeException("Shader program linking failed, see log for details");
|
||||
}
|
||||
|
||||
if (cacheKey != null) {
|
||||
ProgramBinaryCache.save(cacheKey, program);
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class serving as abstraction and
|
||||
* centralization for common GUI
|
||||
@@ -114,6 +116,17 @@ public final class GuiUtil {
|
||||
font.drawStringWithShadow(text, x + 4, y + 4, 0xFFFFFF);
|
||||
}
|
||||
|
||||
public static void drawTextPanel(FontRenderer font, List<String> lines, int x, int y) {
|
||||
int width = 0;
|
||||
for (String line : lines) {
|
||||
width = Math.max(width, font.getStringWidth(line));
|
||||
}
|
||||
drawPanel(x, y, width + 8, lines.size() * 10 + 6);
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
font.drawStringWithShadow(lines.get(i), x + 4, y + 4 + i * 10, 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;
|
||||
|
||||
@@ -2,7 +2,9 @@ package net.coderbot.iris.gui.screen;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gui.GuiUtil;
|
||||
import net.coderbot.iris.gui.NavigationController;
|
||||
@@ -58,6 +60,20 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
private static String selectTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title"); }
|
||||
private static String configureTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title"); }
|
||||
private static final int COMMENT_PANEL_WIDTH = 314;
|
||||
private static final int TOOLTIP_WIDTH = 300;
|
||||
|
||||
private static String saveCompiledLabel() {
|
||||
return I18n.format(Iris.getIrisConfig().shouldSaveCompiledShaders() ? "options.iris.saveCompiledShaders.on" : "options.iris.saveCompiledShaders.off");
|
||||
}
|
||||
|
||||
private List<String> saveCompiledTooltip() {
|
||||
final List<String> lines = new ArrayList<>(this.fontRendererObj.listFormattedStringToWidth(I18n.format("options.iris.saveCompiledShaders.tooltip"), TOOLTIP_WIDTH));
|
||||
if (!RenderSystem.supportsProgramBinary()) {
|
||||
lines.add("");
|
||||
lines.addAll(this.fontRendererObj.listFormattedStringToWidth(I18n.format("options.iris.saveCompiledShaders.unsupported"), TOOLTIP_WIDTH));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private final GuiScreen parent;
|
||||
private final String title;
|
||||
@@ -69,6 +85,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
private @Nullable ShaderPackOptionList shaderOptionList = null;
|
||||
private @Nullable NavigationController navigation = null;
|
||||
private GuiButton screenSwitchButton;
|
||||
private @Nullable GuiButton saveCompiledButton;
|
||||
|
||||
private String notificationDialog = null;
|
||||
private int notificationDialogTimer = 0;
|
||||
@@ -175,6 +192,13 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
}
|
||||
|
||||
final GuiButton saveButton = this.saveCompiledButton;
|
||||
if (saveButton != null && mouseX >= saveButton.xPosition && mouseY >= saveButton.yPosition
|
||||
&& mouseX < saveButton.xPosition + saveButton.width && mouseY < saveButton.yPosition + saveButton.height) {
|
||||
final List<String> tooltip = saveCompiledTooltip();
|
||||
GuiUtil.drawTextPanel(this.fontRendererObj, tooltip, saveButton.xPosition, saveButton.yPosition - (tooltip.size() * 10 + 10));
|
||||
}
|
||||
|
||||
// Render everything queued to drawScreen last
|
||||
for (Runnable render : TOP_LAYER_RENDER_QUEUE) {
|
||||
render.run();
|
||||
@@ -213,7 +237,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
}
|
||||
|
||||
this.shaderPackList = new ShaderPackSelectionList(this, this.mc, this.width, this.height, 32, this.height - 58, 0, this.width);
|
||||
this.shaderPackList = new ShaderPackSelectionList(this, this.mc, this.width, this.height, 32, this.height - 82, 0, this.width);
|
||||
|
||||
if (Iris.getCurrentPack().isPresent() && this.navigation != null) {
|
||||
final ShaderPack currentPack = Iris.getCurrentPack().get();
|
||||
@@ -222,7 +246,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
currentPack.getMenuContainer().setSearchQuery(null);
|
||||
}
|
||||
|
||||
this.shaderOptionList = new ShaderPackOptionList(this, this.navigation, currentPack, this.mc, this.width, this.height, 32, this.height - 58, 0, this.width);
|
||||
this.shaderOptionList = new ShaderPackOptionList(this, this.navigation, currentPack, this.mc, this.width, this.height, 32, this.height - 82, 0, this.width);
|
||||
this.navigation.setActiveOptionList(this.shaderOptionList);
|
||||
|
||||
this.shaderOptionList.rebuild();
|
||||
@@ -239,9 +263,17 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
|
||||
this.buttonList.clear();
|
||||
this.saveCompiledButton = null;
|
||||
|
||||
if (!this.guiHidden) {
|
||||
|
||||
this.saveCompiledButton = new IrisButton(topCenter - 78, this.height - 75, 308, 20,
|
||||
saveCompiledLabel(), button -> {
|
||||
this.toggleSaveCompiledShaders();
|
||||
button.displayString = saveCompiledLabel();
|
||||
});
|
||||
this.buttonList.add(this.saveCompiledButton);
|
||||
|
||||
this.buttonList.add(new IrisButton(bottomCenter + 104, this.height - 27, 100, 20,
|
||||
I18n.format("gui.done"), button -> this.onClose()));
|
||||
|
||||
@@ -632,6 +664,27 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
refreshForChangedPack();
|
||||
}
|
||||
|
||||
public void toggleSaveCompiledShaders() {
|
||||
final boolean save = !Iris.getIrisConfig().shouldSaveCompiledShaders();
|
||||
try {
|
||||
Iris.getIrisConfig().setSaveCompiledShaders(save);
|
||||
} catch (IOException e) {
|
||||
Iris.logger.error("Failed to save the Iris config", e);
|
||||
}
|
||||
if (!save) {
|
||||
ProgramBinaryCache.deleteAll();
|
||||
return;
|
||||
}
|
||||
if (!Iris.getIrisConfig().areShadersEnabled()) return;
|
||||
|
||||
try {
|
||||
Iris.reload();
|
||||
} catch (IOException e) {
|
||||
Iris.logger.error("Failed to reload shaders after turning on Save Compiled Shaders", e);
|
||||
}
|
||||
refreshForChangedPack();
|
||||
}
|
||||
|
||||
private void discardChanges() {
|
||||
Iris.clearShaderPackOptionQueue();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.uniforms.SystemTimeUniforms;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -43,6 +44,7 @@ public class PipelineManager {
|
||||
Iris.logger.info("Creating pipeline for dimension '{}'", currentDimension);
|
||||
pipeline = pipelineFactory.apply(currentDimension);
|
||||
pipelinesPerDimension.put(currentDimension, pipeline);
|
||||
BlockRenderingSettings.INSTANCE.reloadRendererIfRequired();
|
||||
if (Tracy.ENABLED) {
|
||||
shaderSection = Tracy.sectionEnter(Tracy.SECTION_SHADERS, "shaders " + Iris.getCurrentPackName() + " / " + currentDimension);
|
||||
}
|
||||
@@ -89,6 +91,11 @@ public class PipelineManager {
|
||||
versionCounterForSodiumShaderReload++;
|
||||
}
|
||||
|
||||
public void discardPipeline(WorldRenderingPipeline unused) {
|
||||
resetTextureState();
|
||||
unused.destroy();
|
||||
}
|
||||
|
||||
private void resetTextureState() {
|
||||
// Unbind all textures
|
||||
//
|
||||
|
||||
@@ -46,6 +46,7 @@ import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -489,9 +490,13 @@ public class ShaderPack {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// Read mcmeta for filtering data
|
||||
boolean blur = false;
|
||||
boolean clamp = false;
|
||||
// Read mcmeta for filtering data.
|
||||
// Upstream Iris semantics: raw textures default to bilinear filtering and clamping
|
||||
// ("By default, image files will use nearest neighbor and wrapping, and raw textures
|
||||
// will use bilinear filtering and clamping." -- Iris docs, Custom Textures / .mcmeta File).
|
||||
// Only an explicit .mcmeta should override these defaults.
|
||||
boolean blur = true;
|
||||
boolean clamp = true;
|
||||
|
||||
String mcMetaPath = path + ".mcmeta";
|
||||
Path mcMetaResolvedPath = root.resolve(mcMetaPath);
|
||||
@@ -685,27 +690,7 @@ public class ShaderPack {
|
||||
String folderName = resolveDimensionFolder(dimensionMap, foldersWithShaderFiles, legacyDefaultFolder, dimensionName, Iris.getCurrentDimensionId());
|
||||
|
||||
if (folderName != null) {
|
||||
ProgramSet programSet = dimensionProgramSets.get(folderName);
|
||||
|
||||
if (programSet == null) {
|
||||
// Create ProgramSet on-demand for dimension folder
|
||||
try {
|
||||
programSet = new ProgramSet(
|
||||
AbsolutePackPath.fromAbsolutePath("/" + folderName),
|
||||
sourceProvider,
|
||||
shaderProperties,
|
||||
this
|
||||
);
|
||||
dimensionProgramSets.put(folderName, programSet);
|
||||
|
||||
// Check cache size limit and evict LRU dimensions if needed
|
||||
evictOldDimensions();
|
||||
} catch (Exception e) {
|
||||
// This shouldn't happen but just in case.
|
||||
Iris.logger.error("Failed to create ProgramSet for dimension folder '{}', falling back to base", folderName, e);
|
||||
programSet = null;
|
||||
}
|
||||
}
|
||||
final ProgramSet programSet = getFolderProgramSet(folderName);
|
||||
|
||||
if (programSet != null) {
|
||||
return programSet;
|
||||
@@ -730,6 +715,53 @@ public class ShaderPack {
|
||||
return base;
|
||||
}
|
||||
|
||||
private ProgramSet getFolderProgramSet(String folderName) {
|
||||
ProgramSet programSet = dimensionProgramSets.get(folderName);
|
||||
|
||||
if (programSet == null) {
|
||||
// Create ProgramSet on-demand for dimension folder
|
||||
try {
|
||||
programSet = new ProgramSet(
|
||||
AbsolutePackPath.fromAbsolutePath("/" + folderName),
|
||||
sourceProvider,
|
||||
shaderProperties,
|
||||
this
|
||||
);
|
||||
dimensionProgramSets.put(folderName, programSet);
|
||||
|
||||
// Check cache size limit and evict LRU dimensions if needed
|
||||
evictOldDimensions();
|
||||
} catch (Exception e) {
|
||||
// This shouldn't happen but just in case.
|
||||
Iris.logger.error("Failed to create ProgramSet for dimension folder '{}', falling back to base", folderName, e);
|
||||
programSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
return programSet;
|
||||
}
|
||||
|
||||
// Keyed by folder name in sorted order; the base shaders are under null
|
||||
public Map<String, ProgramSet> getEveryProgramSet() {
|
||||
final Map<String, ProgramSet> sets = new LinkedHashMap<>();
|
||||
for (String folderName : new TreeSet<>(foldersWithShaderFiles)) {
|
||||
final ProgramSet programSet = getFolderProgramSet(folderName);
|
||||
if (programSet != null) sets.put(folderName, programSet);
|
||||
}
|
||||
if (canFallBackToBase(dimensionMap, foldersWithShaderFiles, legacyDefaultFolder)) {
|
||||
sets.put(null, base);
|
||||
}
|
||||
return sets;
|
||||
}
|
||||
|
||||
static boolean canFallBackToBase(Map<String, String> dimensionMap, Set<String> foldersWithShaderFiles, String legacyDefaultFolder) {
|
||||
for (String folderName : dimensionMap.values()) {
|
||||
if (!foldersWithShaderFiles.contains(folderName)) return true;
|
||||
}
|
||||
// A dimension that is named nowhere and has no world<id> folder of its own
|
||||
return resolveDimensionFolder(dimensionMap, foldersWithShaderFiles, legacyDefaultFolder, "\0", Integer.MIN_VALUE) == null;
|
||||
}
|
||||
|
||||
static String resolveDimensionFolder(Map<String, String> dimensionMap, Set<String> foldersWithShaderFiles,
|
||||
String legacyDefaultFolder, String dimensionName, int dimensionId) {
|
||||
String folderName = dimensionMap.get(dimensionName);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.irisshaders.iris.api.v0.item;
|
||||
|
||||
import com.gtnewhorizons.angelica.helpers.ItemBlockLight;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -11,8 +12,7 @@ public interface IrisItemLightProvider {
|
||||
|
||||
default int getLightEmission(EntityPlayer player, ItemStack stack) {
|
||||
if (stack.getItem() instanceof ItemBlock item) {
|
||||
|
||||
return item.field_150939_a.getLightValue();
|
||||
return ItemBlockLight.getLightValue(item, stack);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -184,6 +184,13 @@ options.iris.shaderPackOptions.importedSettings=Imported Shader Settings from "%
|
||||
options.iris.shaders.disabled=Shaders: Disabled
|
||||
options.iris.shaders.enabled=Shaders: Enabled
|
||||
options.iris.shaders.nonePresent=Shaders: No Packs Present
|
||||
options.iris.saveCompiledShaders.on=Save Compiled Shaders: ON
|
||||
options.iris.saveCompiledShaders.off=Save Compiled Shaders: OFF
|
||||
options.iris.saveCompiledShaders.tooltip=Saves compiled Iris shader programs to disk, so restarts and entering new dimensions load them instead of compiling on the spot. While this is on, every change to shader settings compiles the shaders for every dimension at once, so applying takes longer. Turning this off deletes the saved programs.
|
||||
options.iris.saveCompiledShaders.unsupported=Your graphics driver cannot save compiled programs, so this only prepares every dimension's shaders ahead of time.
|
||||
options.iris.saveCompiledShaders.progress=Compiling Shaders
|
||||
options.iris.saveCompiledShaders.progressStep=Shader folder %s of %s: %s
|
||||
options.iris.saveCompiledShaders.baseFolder=(default shaders)
|
||||
options.iris.back=Back
|
||||
options.iris.reset=Reset
|
||||
options.iris.reset.tooltip=Reset ALL options and apply?
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
|
||||
uniform sampler2D u_Texture;
|
||||
uniform sampler2D u_Depth;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(u_Texture, v_TexCoord);
|
||||
gl_FragDepth = texture(u_Depth, v_TexCoord).r;
|
||||
}
|
||||
@@ -10,19 +10,25 @@ void main() {
|
||||
vec4 c0 = gl_in[0].gl_Position;
|
||||
vec4 c1 = gl_in[1].gl_Position;
|
||||
|
||||
vec2 n0 = c0.xy / c0.w;
|
||||
vec2 n1 = c1.xy / c1.w;
|
||||
float d0 = c0.z + c0.w;
|
||||
float d1 = c1.z + c1.w;
|
||||
if (d0 < 0.0 && d1 < 0.0) return;
|
||||
vec4 p0 = d0 < 0.0 ? mix(c0, c1, d0 / (d0 - d1)) : c0;
|
||||
vec4 p1 = d1 < 0.0 ? mix(c0, c1, d0 / (d0 - d1)) : c1;
|
||||
|
||||
vec2 n0 = p0.xy / p0.w;
|
||||
vec2 n1 = p1.xy / p1.w;
|
||||
|
||||
vec2 dir = normalize((n1 - n0) * u_ViewportSize);
|
||||
vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;
|
||||
|
||||
gl_Position = vec4((n0 + offset) * c0.w, c0.z, c0.w);
|
||||
gl_Position = vec4((n0 + offset) * p0.w, p0.z, p0.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4((n0 - offset) * c0.w, c0.z, c0.w);
|
||||
gl_Position = vec4((n0 - offset) * p0.w, p0.z, p0.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4((n1 + offset) * c1.w, c1.z, c1.w);
|
||||
gl_Position = vec4((n1 + offset) * p1.w, p1.z, p1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4((n1 - offset) * c1.w, c1.z, c1.w);
|
||||
gl_Position = vec4((n1 - offset) * p1.w, p1.z, p1.w);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
+2
@@ -66,6 +66,7 @@ public class MixinRenderGlobal implements IRenderGlobalExt {
|
||||
@Unique private static final Tracy.ZoneId Z_MT_QUEUE = Tracy.zoneId("mtQueue", Tracy.COLOR_TERRAIN);
|
||||
|
||||
@Shadow public Minecraft mc;
|
||||
@Shadow public WorldClient theWorld;
|
||||
@Shadow @Final private TextureManager renderEngine;
|
||||
|
||||
@Unique private static final String SECTION_SOLID = "draw_chunk_layer_solid";
|
||||
@@ -264,6 +265,7 @@ public class MixinRenderGlobal implements IRenderGlobalExt {
|
||||
|
||||
@Inject(method = "loadRenderers", at = @At("RETURN"))
|
||||
private void onReload(CallbackInfo ci) {
|
||||
if (this.celeritas$renderer.getWorld() != this.theWorld) return;
|
||||
angelica$reload();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPhase;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -20,6 +21,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@Mixin(RenderGlobal.class)
|
||||
public class MixinRenderGlobal {
|
||||
|
||||
@Inject(method = "setWorldAndLoadRenderers", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;loadRenderers()V"))
|
||||
private void iris$preparePipelineForNewWorld(WorldClient world, CallbackInfo ci) {
|
||||
Iris.getPipelineManager().preparePipeline(Iris.getCurrentDimensionName());
|
||||
}
|
||||
|
||||
@Inject(method = "renderSky", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;"))
|
||||
private void iris$renderSky$beginNormalSky(float partialTicks, CallbackInfo ci) {
|
||||
// None of the vanilla sky is rendered until after this call, so if anything is rendered before, it's CUSTOM_SKY.
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class FontProviderCustomGLTest {
|
||||
|
||||
private static final int QUALITY = 30;
|
||||
private static final int THREADS = 8;
|
||||
private static final char SURROGATE = '\uD800';
|
||||
|
||||
private final Font font = new Font(Font.DIALOG, Font.PLAIN, QUALITY);
|
||||
private FontProviderCustom provider;
|
||||
private float scaleBefore;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
scaleBefore = FontConfig.customFontScale;
|
||||
FontConfig.customFontScale = 1.0f;
|
||||
provider = new FontProviderCustom(font);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
FontConfig.customFontScale = scaleBefore;
|
||||
provider.setFont(null);
|
||||
}
|
||||
|
||||
private int[] textures() {
|
||||
return Reflect.get(provider, "atlasTextures");
|
||||
}
|
||||
|
||||
private Object atlas(char chr) {
|
||||
return Reflect.<Object[]>get(provider, "fontAtlases")[chr / FontProviderCustom.ATLAS_SIZE];
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricsFromThreadWithoutContext() throws InterruptedException {
|
||||
TestThreads.run("font-no-context", () -> {
|
||||
assertTrue(provider.isGlyphAvailable('A'), "glyph not reported available off-thread");
|
||||
assertTrue(provider.getXAdvance('A') > 0.0f, "no advance measured off-thread");
|
||||
});
|
||||
assertArrayEquals(new int[FontProviderCustom.ATLAS_COUNT], textures(), "measuring created a texture");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTextureWithoutContextThrows() throws InterruptedException {
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
TestThreads.run("font-no-context", () -> assertThrows(IllegalStateException.class, () -> provider.getTexture('A')));
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureCreatedOnceOnFirstDraw() {
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
final int texture = provider.getTexture('A');
|
||||
assertNotEquals(0, texture, "no texture created on first draw-path lookup");
|
||||
assertTrue(GL11.glIsTexture(texture), "atlas id is not a texture");
|
||||
assertEquals(texture, provider.getTexture('A'), "atlas texture recreated on second lookup");
|
||||
assertEquals(texture, provider.getTexture('B'), "same block resolved to another texture");
|
||||
}
|
||||
|
||||
@Test
|
||||
void glyphInkLandsInsideMeasuredRects() {
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, provider.getTexture('A'));
|
||||
final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
|
||||
final IntBuffer pixels = BufferUtils.createIntBuffer(width * height);
|
||||
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
|
||||
|
||||
final int margin = QUALITY / 6;
|
||||
final boolean[] covered = new boolean[width * height];
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_SIZE; i++) {
|
||||
final char chr = (char) i;
|
||||
if (!provider.isGlyphAvailable(chr)) continue;
|
||||
final int x0 = (int) Math.floor(provider.getUStart(chr) * width);
|
||||
final int x1 = (int) Math.ceil((provider.getUStart(chr) + provider.getUSize(chr)) * width);
|
||||
final int y0 = (int) Math.floor(provider.getVStart(chr) * height);
|
||||
final int y1 = (int) Math.ceil((provider.getVStart(chr) + provider.getVSize(chr)) * height);
|
||||
int ink = 0;
|
||||
for (int y = Math.max(0, y0 - margin); y < Math.min(height, y1 + margin); y++) {
|
||||
for (int x = Math.max(0, x0 - margin); x < Math.min(width, x1 + margin); x++) {
|
||||
covered[y * width + x] = true;
|
||||
if (x >= x0 && x < x1 && y >= y0 && y < y1 && (pixels.get(y * width + x) >>> 24) != 0) ink++;
|
||||
}
|
||||
}
|
||||
if (Character.isLetterOrDigit(chr)) assertTrue(ink > 0, "no ink inside the measured rect of '" + chr + "'");
|
||||
}
|
||||
int stray = 0;
|
||||
for (int i = 0; i < covered.length; i++) {
|
||||
if (!covered[i] && (pixels.get(i) >>> 24) != 0) stray++;
|
||||
}
|
||||
assertEquals(0, stray, "ink outside every measured glyph rect");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setFontDropsAtlasesAndTextures() {
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
final Object atlasBefore = atlas('A');
|
||||
assertNotEquals(0, provider.getTexture('A'));
|
||||
|
||||
provider.setFont(font.deriveFont(20.0f));
|
||||
assertArrayEquals(new int[FontProviderCustom.ATLAS_COUNT], textures(), "font change kept a texture");
|
||||
assertNull(atlas('A'), "font change kept an atlas");
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
assertNotSame(atlasBefore, atlas('A'), "atlas not rebuilt for the new font");
|
||||
assertTrue(GL11.glIsTexture(provider.getTexture('A')), "no texture for the new font");
|
||||
|
||||
provider.setFont(null);
|
||||
assertFalse(provider.isGlyphAvailable('A'), "glyph available with no font set");
|
||||
assertArrayEquals(new int[FontProviderCustom.ATLAS_COUNT], textures(), "clearing the font kept a texture");
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentFirstTouchBuildsOneAtlas() throws Exception {
|
||||
final CyclicBarrier start = new CyclicBarrier(THREADS);
|
||||
final Object[] seen = new Object[THREADS];
|
||||
final TestThreads.Worker[] workers = new TestThreads.Worker[THREADS];
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
final int slot = i;
|
||||
workers[i] = TestThreads.start("font-metrics-race-" + i, () -> {
|
||||
start.await();
|
||||
assertTrue(provider.isGlyphAvailable('A'));
|
||||
seen[slot] = atlas('A');
|
||||
});
|
||||
}
|
||||
for (TestThreads.Worker worker : workers) worker.join();
|
||||
assertNotNull(seen[0], "atlas was not published");
|
||||
for (int i = 1; i < THREADS; i++) assertSame(seen[0], seen[i], "threads observed different atlases");
|
||||
}
|
||||
|
||||
@Test
|
||||
void availabilityMatchesFont() {
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_SIZE; i++) {
|
||||
assertEquals(font.canDisplay((char) i), provider.isGlyphAvailable((char) i), "availability differs from the font for slot " + i);
|
||||
assertEquals(font.canDisplay((char) (SURROGATE + i)), provider.isGlyphAvailable((char) (SURROGATE + i)), "availability differs from the font for surrogate slot " + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomReplacementKeepsWidth() {
|
||||
final float advance = provider.getXAdvance('A');
|
||||
for (int i = 0; i < 200; i++) {
|
||||
final char replacement = provider.getRandomReplacement('A');
|
||||
assertTrue(replacement < FontProviderCustom.ATLAS_SIZE, "replacement left the atlas");
|
||||
assertTrue(provider.isGlyphAvailable(replacement), "replacement has no glyph");
|
||||
assertEquals(advance, provider.getXAdvance(replacement), 0.0f, "replacement has another width");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class TextDrawStateGLTest {
|
||||
|
||||
@AfterEach
|
||||
void resetState() {
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
private static void setDefaults() {
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LESS);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glFrontFace(GL11.GL_CCW);
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
}
|
||||
|
||||
private static void setNonDefaults() {
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_GEQUAL);
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.glFrontFace(GL11.GL_CW);
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(-10.0f, -10.0f);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
|
||||
}
|
||||
|
||||
private static TextDrawState capture() {
|
||||
final TextDrawState state = new TextDrawState();
|
||||
state.captureLive();
|
||||
return state;
|
||||
}
|
||||
|
||||
private static List<Field> stateFields() {
|
||||
final List<Field> fields = new ArrayList<>();
|
||||
for (Field field : TextDrawState.class.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers()) || !field.getType().isPrimitive()) continue;
|
||||
field.setAccessible(true);
|
||||
fields.add(field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static boolean isGlState(Field field) {
|
||||
return !field.getName().equals("alphaRef");
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyFieldIsMovedByTheTestState() throws IllegalAccessException {
|
||||
setDefaults();
|
||||
final TextDrawState defaults = capture();
|
||||
setNonDefaults();
|
||||
final TextDrawState moved = capture();
|
||||
for (Field field : stateFields()) {
|
||||
assertNotEquals(field.get(defaults), field.get(moved), field.getName() + " - add it to setNonDefaults()");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameAsAndSetCoverEveryField() throws IllegalAccessException {
|
||||
setNonDefaults();
|
||||
final TextDrawState moved = capture();
|
||||
setDefaults();
|
||||
final TextDrawState defaults = capture();
|
||||
for (Field field : stateFields()) {
|
||||
final TextDrawState copy = new TextDrawState();
|
||||
copy.set(moved);
|
||||
assertEquals(field.get(moved), field.get(copy), field.getName() + " - missing from set()");
|
||||
assertTrue(copy.sameAs(moved));
|
||||
field.set(copy, field.get(defaults));
|
||||
assertFalse(copy.sameAs(moved), field.getName() + " - missing from sameAs()");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyReplaysCapturedState() throws IllegalAccessException {
|
||||
setNonDefaults();
|
||||
final TextDrawState captured = capture();
|
||||
setDefaults();
|
||||
captured.apply();
|
||||
final TextDrawState replayed = capture();
|
||||
for (Field field : stateFields()) {
|
||||
if (!isGlState(field)) continue;
|
||||
assertEquals(field.get(captured), field.get(replayed), field.getName());
|
||||
}
|
||||
assertEquals(GL11.GL_GEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
|
||||
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face");
|
||||
assertEquals(GL11.GL_CW, GL11.glGetInteger(GL11.GL_FRONT_FACE), "driver front face");
|
||||
assertEquals(-10.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_FACTOR), "driver polygon offset factor");
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL), "driver polygon offset fill");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fontStateSetRestoresEverythingApplyTouches() throws IllegalAccessException {
|
||||
assertApplyDoesNotLeak(StateSet.FONT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fontPipelineStateSetRestoresEverythingApplyTouches() throws IllegalAccessException {
|
||||
assertApplyDoesNotLeak(StateSet.FONT_PIPELINE);
|
||||
}
|
||||
|
||||
private static void assertApplyDoesNotLeak(StateSet set) throws IllegalAccessException {
|
||||
setNonDefaults();
|
||||
final TextDrawState moved = capture();
|
||||
setDefaults();
|
||||
final TextDrawState before = capture();
|
||||
|
||||
final int depth = GLStateManager.pushState(set);
|
||||
moved.apply();
|
||||
GLStateManager.popStateTo(depth);
|
||||
|
||||
final TextDrawState after = capture();
|
||||
for (Field field : stateFields()) {
|
||||
if (!isGlState(field)) continue;
|
||||
assertEquals(field.get(before), field.get(after), field.getName() + " - not restored by the font state set");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,51 @@ class PacerCoreTest {
|
||||
assertFalse(rig.core.probing(), "a gate proven off the grid must not be probed again");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anAbandonedGateStaysRetiredThroughAnotherOnGridRun() {
|
||||
final Rig rig = new Rig(P60, true, 120);
|
||||
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
|
||||
assertTrue(rig.core.locked());
|
||||
assertEquals(60, rig.core.effectiveCapHz());
|
||||
|
||||
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
|
||||
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
|
||||
end += P60 / 2;
|
||||
rig.feed(end, 5 * MS, end);
|
||||
}
|
||||
assertFalse(rig.core.locked());
|
||||
assertFalse(rig.core.probing());
|
||||
|
||||
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
|
||||
end += P60;
|
||||
rig.feed(end, 5 * MS, end);
|
||||
}
|
||||
assertFalse(rig.core.locked(), "a retired inconsistent gate must not lock again");
|
||||
assertFalse(rig.core.probing());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidateClearsAnAbandonedGate() {
|
||||
final Rig rig = new Rig(P60, true, 120);
|
||||
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
|
||||
|
||||
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
|
||||
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
|
||||
end += P60 / 2;
|
||||
rig.feed(end, 5 * MS, end);
|
||||
}
|
||||
assertFalse(rig.core.locked());
|
||||
assertFalse(rig.core.probing());
|
||||
|
||||
rig.core.invalidate();
|
||||
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
|
||||
end += P60;
|
||||
rig.feed(end, 5 * MS, end);
|
||||
}
|
||||
assertTrue(rig.core.locked());
|
||||
assertFalse(rig.core.probing());
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplesFurtherApartThanTheLockWindowNeverPairUp() {
|
||||
final Rig rig = new Rig(P60, true, 0);
|
||||
@@ -372,6 +417,18 @@ class PacerCoreTest {
|
||||
assertEquals(P60, rig.presents.get(300) - rig.presents.get(299), "an expired probe must fall back to the exact cadence");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anExpiredProbeLeadStillLocksWhenTheGateArrives() {
|
||||
final Rig rig = new Rig(P60, true, 0);
|
||||
for (int i = 0; i < 20; i++) rig.frame(4 * MS);
|
||||
assertFalse(rig.core.locked());
|
||||
assertFalse(rig.core.probing());
|
||||
|
||||
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
|
||||
assertTrue(rig.core.locked());
|
||||
assertFalse(rig.core.probing());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mailboxIsNeverProbedAndNeverLocks() {
|
||||
final Rig rig = new Rig(P60, true, false, 0);
|
||||
|
||||
Reference in new issue
Block a user