mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
More fixes (#1995)
This commit is contained in:
1 parent
4b21bc77b6
commit
166116d692
50 files changed
+1460
-195
No files matched your search
+7
-2
@@ -1,3 +1,4 @@
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
@@ -31,11 +32,11 @@ minecraft {
|
||||
|
||||
}
|
||||
|
||||
// Ensure flyby args are passed to the client
|
||||
// Forward -Dangelica.* from the gradle invocation to the client
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.flyby.")) {
|
||||
if (name.startsWith("angelica.")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
@@ -252,3 +253,7 @@ tasks.shadowJar {
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
}
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
}
|
||||
@@ -16,7 +16,8 @@ public final class SystemProperties {
|
||||
private static final Logger LOGGER = LogManager.getLogger("SystemProperties");
|
||||
|
||||
// Backend selection
|
||||
public static final boolean USE_SDL_GPU = Boolean.getBoolean("angelica.sdlgpu.enable");
|
||||
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
|
||||
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
|
||||
|
||||
@@ -613,6 +613,7 @@ public class DisplayListManager {
|
||||
// Get relative transform (changes since glNewList, not absolute matrix state)
|
||||
addAccumulatedDraw(tessellator, tessellator.getVertexFormat() != DefaultVertexFormat.POSITION);
|
||||
}
|
||||
transformCallback.clearBaked(tessellator);
|
||||
tessellator.reset();
|
||||
}
|
||||
|
||||
@@ -1322,6 +1323,16 @@ public class DisplayListManager {
|
||||
|
||||
private final Matrix4f transformMatrix = new Matrix4f();
|
||||
|
||||
private DirectTessellator bakedOwner;
|
||||
private int bakedVertices;
|
||||
|
||||
void clearBaked(DirectTessellator tessellator) {
|
||||
if (bakedOwner == tessellator) {
|
||||
bakedOwner = null;
|
||||
bakedVertices = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void scale(float x, float y, float z) {
|
||||
transformMatrix.scale(x, y, z);
|
||||
markDirty();
|
||||
@@ -1384,10 +1395,26 @@ public class DisplayListManager {
|
||||
return MatrixHelper.isIdentity(transformMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartDrawing(CallbackTessellator tessellator, int drawMode) {
|
||||
bakedOwner = tessellator;
|
||||
bakedVertices = 0;
|
||||
return super.onStartDrawing(tessellator, drawMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDraw(CallbackTessellator tessellator) {
|
||||
if (!tessellator.isEmpty()) {
|
||||
if (!isIdentity()) {
|
||||
final int baked = bakedOwner == tessellator ? bakedVertices : 0;
|
||||
if (bakedVertices == 0) {
|
||||
flushMatrix();
|
||||
} else if (baked != tessellator.vertexCount) {
|
||||
GLStateManager.warnOnce("dl-mixed-baked-raw", "Display list {}: draw of {} vertices carries {} transformed ones - the rest lose the pending transform", glListId, tessellator.vertexCount, baked);
|
||||
}
|
||||
}
|
||||
addAccumulatedDraw(tessellator, false);
|
||||
clearBaked(tessellator);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1396,6 +1423,11 @@ public class DisplayListManager {
|
||||
public void onVertex(CallbackTessellator tessellator, double x, double y, double z) {
|
||||
tessellator.syncVanillaTessellator();
|
||||
super.onVertex(tessellator, x, y, z);
|
||||
if (bakedOwner != tessellator) {
|
||||
bakedOwner = tessellator;
|
||||
bakedVertices = 0;
|
||||
}
|
||||
bakedVertices++;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,10 +26,13 @@ import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntStack;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -152,6 +155,8 @@ class GLContextState {
|
||||
public final BooleanStateStack map2Vertex4State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_4, false, true));
|
||||
public final BooleanStateStack[] clipPlaneStates = new BooleanStateStack[GLStateManager.MAX_CLIP_PLANES];
|
||||
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
|
||||
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
|
||||
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
|
||||
public final MatrixModeStack matrixMode = new MatrixModeStack();
|
||||
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
|
||||
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
|
||||
|
||||
@@ -528,7 +528,10 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
|
||||
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
|
||||
if (glVendor == null) {
|
||||
LOGGER.error("Backend {} reported no GL_VENDOR (context: {}); treating the vendor as unknown", RENDER_BACKEND.getName(), RENDER_BACKEND.hasContext());
|
||||
}
|
||||
VENDOR = Vendor.getVendor(glVendor == null ? "" : glVendor.toLowerCase());
|
||||
|
||||
final String os = System.getProperty("os.name").toLowerCase();
|
||||
windows = os.contains("win");
|
||||
@@ -1183,12 +1186,11 @@ public class GLStateManager {
|
||||
};
|
||||
}
|
||||
|
||||
private static final FloatBuffer glGetFloatScratch = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
public static void glGetFloat(int pname, float[] params) {
|
||||
final FloatBuffer scratch = ctx().queryScratch;
|
||||
final FloatBuffer buf;
|
||||
if (params.length <= glGetFloatScratch.capacity()) {
|
||||
buf = glGetFloatScratch;
|
||||
if (params.length <= scratch.capacity()) {
|
||||
buf = scratch;
|
||||
buf.clear();
|
||||
buf.limit(params.length);
|
||||
} else {
|
||||
@@ -1199,6 +1201,72 @@ public class GLStateManager {
|
||||
buf.get(params);
|
||||
}
|
||||
|
||||
public static double glGetDouble(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_DEPTH_CLEAR_VALUE -> ctx().depthState.getClearValue();
|
||||
default -> glGetFloat(pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static void glGetDouble(int pname, DoubleBuffer params) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if (!isCachingEnabled()) {
|
||||
RENDER_BACKEND.getDouble(pname, params);
|
||||
return;
|
||||
}
|
||||
|
||||
final int pos = params.position();
|
||||
switch (pname) {
|
||||
case GL11.GL_DEPTH_CLEAR_VALUE -> params.put(pos, glCtx.depthState.getClearValue());
|
||||
case GL11.GL_DEPTH_RANGE -> {
|
||||
params.put(pos, glCtx.viewportState.depthRangeNear);
|
||||
params.put(pos + 1, glCtx.viewportState.depthRangeFar);
|
||||
}
|
||||
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> widenFromFloat(pname, params, 16);
|
||||
case GL11.GL_COLOR_CLEAR_VALUE, GL11.GL_CURRENT_COLOR, GL11.GL_CURRENT_TEXTURE_COORDS, GL11.GL_FOG_COLOR,
|
||||
GL11.GL_LIGHT_MODEL_AMBIENT, GL14.GL_BLEND_COLOR -> widenFromFloat(pname, params, 4);
|
||||
case GL11.GL_CURRENT_NORMAL -> widenFromFloat(pname, params, 3);
|
||||
default -> {
|
||||
if (!HAS_MULTIPLE_SET.contains(pname)) {
|
||||
params.put(pos, glGetDouble(pname));
|
||||
} else {
|
||||
RENDER_BACKEND.getDouble(pname, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void glGetDouble(int pname, double[] params) {
|
||||
final DoubleBuffer scratch = ctx().queryScratchDouble;
|
||||
final DoubleBuffer buf;
|
||||
if (params.length <= scratch.capacity()) {
|
||||
buf = scratch;
|
||||
buf.clear();
|
||||
buf.limit(params.length);
|
||||
} else {
|
||||
buf = BufferUtils.createDoubleBuffer(params.length);
|
||||
}
|
||||
glGetDouble(pname, buf);
|
||||
buf.position(0);
|
||||
buf.get(params);
|
||||
}
|
||||
|
||||
private static void widenFromFloat(int pname, DoubleBuffer params, int count) {
|
||||
final FloatBuffer scratch = ctx().queryScratch;
|
||||
scratch.clear();
|
||||
glGetFloat(pname, scratch);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
params.put(pos + i, scratch.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void glGetClipPlane(int plane, DoubleBuffer equation) {
|
||||
final int index = plane - GL11.GL_CLIP_PLANE0;
|
||||
if (index < 0 || index >= MAX_CLIP_PLANES) return;
|
||||
ctx().clipPlaneState.getEyePlane(index, equation);
|
||||
}
|
||||
|
||||
// GLStateManager Functions
|
||||
|
||||
public static void glBlendColor(float red, float green, float blue, float alpha) {
|
||||
@@ -2258,7 +2326,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawElements(int mode, ByteBuffer indices) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
@@ -2283,7 +2351,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawElements(int mode, IntBuffer indices) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
@@ -2308,7 +2376,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawElements(int mode, ShortBuffer indices) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
@@ -2333,7 +2401,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
@@ -2358,7 +2426,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
@@ -2401,22 +2469,22 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
|
||||
if (Tracy.ENABLED) drawCalls += drawcount;
|
||||
drawCalls += drawcount;
|
||||
RENDER_BACKEND.multiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
public static void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
|
||||
if (Tracy.ENABLED) drawCalls += drawcount;
|
||||
drawCalls += drawcount;
|
||||
RENDER_BACKEND.multiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
|
||||
}
|
||||
|
||||
public static void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex);
|
||||
}
|
||||
|
||||
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadElementsAsTrianglesInstanced(count, type, indices, primcount);
|
||||
return;
|
||||
@@ -2426,7 +2494,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadsAsTrianglesInstanced(first, count, primcount);
|
||||
} else if (mode == GL11.GL_QUAD_STRIP) {
|
||||
@@ -2476,7 +2544,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDrawArrays(int mode, int first, int count) {
|
||||
if (Tracy.ENABLED) drawCalls++;
|
||||
drawCalls++;
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public final class BackendManager {
|
||||
@@ -39,8 +38,8 @@ public final class BackendManager {
|
||||
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
|
||||
best = backend;
|
||||
}
|
||||
} catch (ServiceConfigurationError | LinkageError e) {
|
||||
LOGGER.debug("Skipping unavailable backend: {}", e.getMessage());
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("Skipping unavailable backend: {}", t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1371,6 +1371,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL11.glGetFloat(pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int pname) {
|
||||
return GL11.glGetDouble(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDouble(int pname, DoubleBuffer params) {
|
||||
GL11.glGetDouble(pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int pname) {
|
||||
return GL11.glGetBoolean(pname);
|
||||
|
||||
@@ -5,8 +5,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -20,7 +18,7 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class RenderBackend {
|
||||
|
||||
public void onPostWindowCreate(long window, boolean debug) {}
|
||||
public void onPostWindowCreate(long window) {}
|
||||
|
||||
public abstract void init();
|
||||
public abstract void shutdown();
|
||||
@@ -75,7 +73,7 @@ public abstract class RenderBackend {
|
||||
public void onFrameEnd() {}
|
||||
|
||||
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
|
||||
public void onPreSwapchainInvalidatingChange(SwapchainInvalidatingChange change) {}
|
||||
public void onPreSwapchainInvalidatingChange(Object change) {}
|
||||
|
||||
public boolean handleMakeCurrent(Object drawable) { return false; }
|
||||
public boolean handleReleaseContext(Object drawable) { return false; }
|
||||
@@ -84,6 +82,11 @@ public abstract class RenderBackend {
|
||||
|
||||
public String getTransferDebugInfo() { return null; }
|
||||
|
||||
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
|
||||
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
|
||||
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
|
||||
public void endVoxelizationBatch(long pass) {}
|
||||
|
||||
public abstract void enable(int cap);
|
||||
public abstract void enablei(int cap, int index);
|
||||
public abstract void disable(int cap);
|
||||
@@ -389,6 +392,8 @@ public abstract class RenderBackend {
|
||||
public void getInteger(int pname, int[] params) { if (params.length > 0) params[0] = getInteger(pname); }
|
||||
public abstract float getFloat(int pname);
|
||||
public abstract void getFloat(int pname, FloatBuffer params);
|
||||
public double getDouble(int pname) { return getFloat(pname); }
|
||||
public void getDouble(int pname, DoubleBuffer params) { if (params.remaining() > 0) params.put(params.position(), getDouble(pname)); }
|
||||
public abstract boolean getBoolean(int pname);
|
||||
public abstract void getBoolean(int pname, ByteBuffer params);
|
||||
public abstract String getString(int pname);
|
||||
|
||||
@@ -236,6 +236,9 @@ public class GLSMRedirector {
|
||||
.add("glFogi")
|
||||
.add("glFrustum")
|
||||
.add("glGetBoolean")
|
||||
.add("glGetClipPlane")
|
||||
.add("glGetDouble")
|
||||
.add("glGetDoublev", "glGetDouble")
|
||||
.add("glGetFloat")
|
||||
.add("glGetFloatv", "glGetFloat")
|
||||
.add("glGetInteger")
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
@@ -31,4 +32,10 @@ public class ClipPlaneState {
|
||||
final float[] p = eyePlanes[index];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
}
|
||||
|
||||
public void getEyePlane(int index, DoubleBuffer buf) {
|
||||
final float[] p = eyePlanes[index];
|
||||
final int pos = buf.position();
|
||||
buf.put(pos, p[0]).put(pos + 1, p[1]).put(pos + 2, p[2]).put(pos + 3, p[3]);
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class GLSM_DisplayList_RawTessellatorTransform_Test {
|
||||
|
||||
private int testList = -1;
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
if (testList > 0) {
|
||||
GLStateManager.glDeleteLists(testList, 1);
|
||||
testList = -1;
|
||||
}
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
|
||||
private static void rawQuad(float size) {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
final float[][] corners = { { 0.0f, 0.0f }, { size, 0.0f }, { size, size }, { 0.0f, size } };
|
||||
tess.rawBuffer = new int[4 * 8];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final int base = i * 8;
|
||||
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
|
||||
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
|
||||
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
|
||||
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
|
||||
}
|
||||
tess.isDrawing = true;
|
||||
tess.drawMode = GL11.GL_QUADS;
|
||||
tess.hasTexture = true;
|
||||
tess.rawBufferIndex = 4 * 8;
|
||||
tess.vertexCount = 4;
|
||||
TessellatorManager.interceptDraw(tess);
|
||||
}
|
||||
|
||||
private static void immediateQuad(float size) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glVertex3f(size, 0.0f, 0.0f);
|
||||
GLStateManager.glVertex3f(size, size, 0.0f);
|
||||
GLStateManager.glVertex3f(0.0f, size, 0.0f);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingTransformIsEmittedBeforeARawDraw() {
|
||||
testList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
|
||||
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
|
||||
rawQuad(1.0f);
|
||||
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
|
||||
assertNotNull(compiled);
|
||||
|
||||
final IntList opcodes = compiled.getCommandOpcodes();
|
||||
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
|
||||
final int drawIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
|
||||
|
||||
assertTrue(scaleIndex >= 0, "The pending scale must be recorded");
|
||||
assertTrue(drawIndex >= 0, "The raw draw must be recorded");
|
||||
assertTrue(scaleIndex < drawIndex, "Scale must be emitted before the draw it applies to");
|
||||
assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.SCALE, 0), "Scale emitted once, not again by the end-of-list flush");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingTransformIsNotEmittedBeforeABakedDraw() {
|
||||
testList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
|
||||
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
|
||||
immediateQuad(1.0f);
|
||||
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
|
||||
assertNotNull(compiled);
|
||||
|
||||
final IntList opcodes = compiled.getCommandOpcodes();
|
||||
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
|
||||
final int drawIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
|
||||
|
||||
assertTrue(drawIndex >= 0, "The immediate mode draw must be recorded");
|
||||
assertTrue(scaleIndex < 0 || drawIndex < scaleIndex, "Baked geometry must draw before the delta reaches the stream");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRawDrawAfterABakedDrawStillGetsTheTransform() {
|
||||
testList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
|
||||
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
|
||||
immediateQuad(1.0f);
|
||||
rawQuad(1.0f);
|
||||
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
|
||||
assertNotNull(compiled);
|
||||
|
||||
final IntList opcodes = compiled.getCommandOpcodes();
|
||||
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
|
||||
final int lastDrawIndex = opcodes.lastIndexOf(GLCommand.DRAW_RANGE);
|
||||
final int firstDrawIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
|
||||
|
||||
assertTrue(scaleIndex >= 0, "The pending scale must be recorded");
|
||||
assertTrue(firstDrawIndex < scaleIndex, "The baked draw stays ahead of the transform");
|
||||
assertTrue(scaleIndex < lastDrawIndex, "The raw draw still gets the transform");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRawDrawDoesNotFlushWhileBakedGeometryIsPending() {
|
||||
testList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
|
||||
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glVertex3f(1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glVertex3f(1.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glVertex3f(0.0f, 1.0f, 0.0f);
|
||||
|
||||
rawQuad(1.0f);
|
||||
|
||||
GLStateManager.glEnd();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
|
||||
assertNotNull(compiled);
|
||||
|
||||
final IntList opcodes = compiled.getCommandOpcodes();
|
||||
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
|
||||
final int drawIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
|
||||
|
||||
assertTrue(drawIndex >= 0, "Draws must be recorded");
|
||||
assertTrue(scaleIndex < 0 || drawIndex < scaleIndex, "No flush while baked geometry is pending");
|
||||
}
|
||||
}
|
||||
+43
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -837,6 +839,47 @@ class GLSM_DisplayList_TransformCollapsing_Test {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRawTessellatorDrawPlaysBackWithTheTransform() {
|
||||
testList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
|
||||
|
||||
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
final float[][] corners = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };
|
||||
tess.rawBuffer = new int[4 * 8];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final int base = i * 8;
|
||||
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
|
||||
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
|
||||
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
|
||||
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
|
||||
}
|
||||
tess.isDrawing = true;
|
||||
tess.drawMode = GL11.GL_QUADS;
|
||||
tess.hasTexture = true;
|
||||
tess.rawBufferIndex = 4 * 8;
|
||||
tess.vertexCount = 4;
|
||||
TessellatorManager.interceptDraw(tess);
|
||||
|
||||
GLStateManager.glEndList();
|
||||
|
||||
CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
|
||||
assertNotNull(compiled);
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glCallList(testList);
|
||||
|
||||
FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, matrixBuf);
|
||||
assertEquals(0.5f, matrixBuf.get(0), 0.0001f, "Scale X after playback");
|
||||
assertEquals(0.5f, matrixBuf.get(5), 0.0001f, "Scale Y after playback");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
// ==================== Optimizer Push/Pop Reset Tests ====================
|
||||
|
||||
@Test
|
||||
|
||||
+10
@@ -1504,6 +1504,16 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
GL11C.glGetFloatv(pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int pname) {
|
||||
return GL11C.glGetDouble(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDouble(int pname, DoubleBuffer params) {
|
||||
GL11C.glGetDoublev(pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int pname) {
|
||||
return GL11C.glGetBoolean(pname);
|
||||
|
||||
@@ -2,11 +2,18 @@ package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLLog;
|
||||
import org.lwjgl.sdl.SDLProperties;
|
||||
import org.lwjgl.system.Platform;
|
||||
import org.lwjglx.Sys;
|
||||
import org.lwjglx.input.Mouse;
|
||||
import org.lwjglx.opengl.ContextAttribs;
|
||||
import org.lwjglx.opengl.Display;
|
||||
import org.lwjglx.opengl.PixelFormat;
|
||||
@@ -14,21 +21,67 @@ import org.lwjglx.opengl.PixelFormat;
|
||||
import me.eigenraven.lwjgl3ify.api.DisplayEvents;
|
||||
import me.eigenraven.lwjgl3ify.api.DisplayWindowContext;
|
||||
import me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class SDLGPUGate {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica/SDLGPU");
|
||||
|
||||
private SDLGPUGate() {}
|
||||
|
||||
private static volatile boolean fatalInit;
|
||||
private static Device device;
|
||||
|
||||
public static void markFatalInit() { fatalInit = true; }
|
||||
private static volatile Boolean deviceReady;
|
||||
private static volatile boolean engaged;
|
||||
private static volatile boolean disarmed;
|
||||
private static volatile Throwable initFailure;
|
||||
private static boolean listenersRegistered;
|
||||
private static ByteBuffer[] windowIcons;
|
||||
|
||||
public static boolean isFatalInit() { return fatalInit; }
|
||||
public static void rememberIcons(ByteBuffer[] icons) {
|
||||
windowIcons = icons;
|
||||
}
|
||||
|
||||
public static synchronized Device device() {
|
||||
if (device == null) device = new Device();
|
||||
return device;
|
||||
}
|
||||
|
||||
private static final boolean SDL_GPU_AVAILABLE = SDLGPUGate.class.getClassLoader().getResource("org/lwjgl/sdl/SDLGPU.class") != null;
|
||||
|
||||
public static boolean isSDLGPUAvailable() {
|
||||
return SDLGPUGate.class.getClassLoader().getResource("org/lwjgl/sdl/SDLGPU.class") != null;
|
||||
return SDL_GPU_AVAILABLE;
|
||||
}
|
||||
|
||||
public static boolean isDeviceReady() {
|
||||
final Boolean ready = deviceReady;
|
||||
return ready != null ? ready : probe();
|
||||
}
|
||||
|
||||
private static synchronized boolean probe() {
|
||||
if (deviceReady != null) return deviceReady;
|
||||
if (!SystemProperties.USE_SDL_GPU || !isSDLGPUAvailable()) {
|
||||
deviceReady = false;
|
||||
return false;
|
||||
}
|
||||
boolean ok;
|
||||
try {
|
||||
Sys.initialize();
|
||||
ok = MainThreadExec.runOnMainThread(device()::createDevice);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("SDL GPU device probe failed", t);
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) resetSdlLogging();
|
||||
deviceReady = ok;
|
||||
return ok;
|
||||
}
|
||||
|
||||
public static boolean isEngaged() {
|
||||
return engaged;
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
@@ -37,24 +90,88 @@ public final class SDLGPUGate {
|
||||
return rb instanceof SDLGPURenderBackend;
|
||||
}
|
||||
|
||||
public static Consumer<DisplayWindowContext> sdlGpuPreWindowCreateCallback() {
|
||||
if (Platform.get() != Platform.MACOSX) return null;
|
||||
return ctx -> Sys.checkSdl(SDLProperties.SDL_SetBooleanProperty(ctx.props(), SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, true));
|
||||
public static void ensureDrawableInstalled() {
|
||||
if (!isActive()) return;
|
||||
SDLGPUDisplayBridge.ensureDrawableInstalled();
|
||||
}
|
||||
|
||||
public static Consumer<DisplayWindowContext> sdlGpuPostWindowCreateCallback(boolean debug) {
|
||||
return ctx -> BackendManager.RENDER_BACKEND.onPostWindowCreate(ctx.window(), debug);
|
||||
public static void prewarmSpirv(String transformedSource, int glShaderType) {
|
||||
if (!isActive()) return;
|
||||
ShaderManager.prewarmSpirv(transformedSource, glShaderType);
|
||||
}
|
||||
|
||||
public static void clearShaderPrewarmCache() {
|
||||
if (!isActive()) return;
|
||||
ShaderManager.clearPrewarmCache();
|
||||
}
|
||||
|
||||
public static Consumer<SwapchainInvalidatingChange> sdlGpuPreSwapchainInvalidatingCallback() {
|
||||
return change -> BackendManager.RENDER_BACKEND.onPreSwapchainInvalidatingChange(change);
|
||||
}
|
||||
|
||||
public static void createSDLGPUDisplay(Object format, Object attribs, boolean debug) {
|
||||
public static boolean createSDLGPUDisplay(Object format, Object attribs) {
|
||||
if (!isDeviceReady()) return false;
|
||||
|
||||
initFailure = null;
|
||||
disarmed = false;
|
||||
registerListeners();
|
||||
|
||||
DisplayEvents.setCreateGLContext(false);
|
||||
final Consumer<DisplayWindowContext> pre = sdlGpuPreWindowCreateCallback(); // null on non-Mac
|
||||
if (pre != null) DisplayEvents.addPreWindowCreateListener(pre);
|
||||
DisplayEvents.addPostWindowCreateListener(sdlGpuPostWindowCreateCallback(debug));
|
||||
Display.create((PixelFormat) format, (ContextAttribs) attribs, /*sharedWindow*/ 0L);
|
||||
|
||||
final Throwable failure = initFailure;
|
||||
if (failure != null) {
|
||||
initFailure = null;
|
||||
LOG.error("SDL GPU could not take the window", failure);
|
||||
return false;
|
||||
}
|
||||
return engaged;
|
||||
}
|
||||
|
||||
private static void registerListeners() {
|
||||
if (listenersRegistered) return;
|
||||
listenersRegistered = true;
|
||||
|
||||
if (Platform.get() == Platform.MACOSX) {
|
||||
DisplayEvents.addPreWindowCreateListener((DisplayWindowContext ctx) -> {
|
||||
if (disarmed) return;
|
||||
Sys.checkSdl(SDLProperties.SDL_SetBooleanProperty(ctx.props(), SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, true));
|
||||
});
|
||||
}
|
||||
|
||||
DisplayEvents.addPostWindowCreateListener((DisplayWindowContext ctx) -> {
|
||||
if (disarmed) return;
|
||||
try {
|
||||
device().claimWindow(ctx.window());
|
||||
engaged = true;
|
||||
BackendManager.RENDER_BACKEND.onPostWindowCreate(ctx.window());
|
||||
} catch (Throwable t) {
|
||||
initFailure = t;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void fallBackToGL() {
|
||||
disarmed = true;
|
||||
engaged = false;
|
||||
deviceReady = false;
|
||||
|
||||
device().destroyDevice();
|
||||
resetSdlLogging();
|
||||
|
||||
Mouse.setGrabbed(false);
|
||||
|
||||
DisplayEvents.setCreateGLContext(true);
|
||||
if (Display.isCreated()) {
|
||||
Display.destroy();
|
||||
}
|
||||
Display.isCloseRequested();
|
||||
if (windowIcons != null) {
|
||||
Display.setIcon(windowIcons);
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetSdlLogging() {
|
||||
SDLLog.SDL_ResetLogPriorities();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import java.util.Arrays;
|
||||
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
|
||||
public final class SDLGPULWJGLService extends LWJGLService {
|
||||
@Override public int getPriority() {
|
||||
if (SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable()) {
|
||||
if (SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged()) {
|
||||
return 200;
|
||||
}
|
||||
return Integer.MIN_VALUE;
|
||||
|
||||
@@ -96,7 +96,6 @@ import org.lwjglx.opengl.Display;
|
||||
import me.eigenraven.lwjgl3ify.api.DisplayEvents;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compat.Lwjgl3GLCapabilitiesShim;
|
||||
import me.eigenraven.lwjgl3ify.api.GLCapabilitiesOverride;
|
||||
import me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
@@ -106,7 +105,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private static final Tracy.ZoneId Z_SDL_INDIRECT_DRAW = Tracy.zoneId("sdlIndirectDraw", Tracy.COLOR_TERRAIN);
|
||||
|
||||
private final Device device = new Device();
|
||||
private final Device device = SDLGPUGate.device();
|
||||
private final FrameManager frameManager = new FrameManager(device);
|
||||
private final ResourceManager resourceManager = new ResourceManager(device, frameManager);
|
||||
private final Image3DClear image3DClear = new Image3DClear();
|
||||
@@ -213,8 +212,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
persistentSync.onPersistentBufferWrite(glId, offset, size);
|
||||
}
|
||||
|
||||
@Override public void onPostWindowCreate(long window, boolean debug) {
|
||||
device.claimWindow(window, debug);
|
||||
@Override public void onPostWindowCreate(long window) {
|
||||
buildCachedStrings();
|
||||
populateGLCapabilities();
|
||||
}
|
||||
@@ -227,6 +225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
LOG.info("SDL-GPU debug markers ENABLED (lwjglDebug={}, angelica.debug.markers={}, captureTool={})", lwjglDebugFlag, SystemProperties.DEBUG_MARKERS, CaptureGate.TOOL_ATTACHED);
|
||||
}
|
||||
frameManager.setResourceManager(resourceManager);
|
||||
device.setLossDiagnostics(resourceManager::describeGpuState);
|
||||
frameManager.setBeforeEndCopyPassCallback(() -> resourceManager.flushBatchedUploads(frameManager.getCopyPass()));
|
||||
frameManager.setBeforeSubmitCallback(() -> {
|
||||
endComputeDispatchBatch();
|
||||
@@ -234,7 +233,6 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
drainDeferredPersistentRegions(s());
|
||||
debugLabels.popAutoDebugGroup(s());
|
||||
});
|
||||
frameManager.setBeforePendingUploadSubmitCallback(textureOps::drainPendingMipGen);
|
||||
frameManager.setAfterPresentCallback(() -> {
|
||||
frameManager.requestFlushOnAllRegisteredFrames();
|
||||
resourceManager.drainPendingFreeShadows();
|
||||
@@ -434,7 +432,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public boolean isAvailable() {
|
||||
return SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable();
|
||||
return SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged();
|
||||
}
|
||||
|
||||
@Override public String getName() {
|
||||
@@ -630,7 +628,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
resourceManager.recycleGpuBufferPool(frameManager.getFrameNumber());
|
||||
}
|
||||
|
||||
@Override public void onPreSwapchainInvalidatingChange(SwapchainInvalidatingChange change) {
|
||||
@Override public void onPreSwapchainInvalidatingChange(Object change) {
|
||||
endFrameUploadFlush();
|
||||
if (frameManager.isFrameActive()) {
|
||||
frameManager.endFrame();
|
||||
@@ -3521,7 +3519,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private int voxLocStart = -1;
|
||||
private int voxLocCount = -1;
|
||||
|
||||
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) {
|
||||
@Override public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) {
|
||||
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS || vertexBufferGlId == 0) return false;
|
||||
final ContextState st = s();
|
||||
if (st.boundProgram == 0) return false;
|
||||
@@ -3540,7 +3538,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return names == null ? new String[0] : names.clone();
|
||||
}
|
||||
|
||||
public long beginVoxelizationBatch(int ssboBinding) {
|
||||
@Override public long beginVoxelizationBatch(int ssboBinding) {
|
||||
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS) return 0;
|
||||
final ContextState st = s();
|
||||
final int program = st.boundProgram;
|
||||
@@ -3550,11 +3548,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return voxelizationDispatcher.beginBatch(st);
|
||||
}
|
||||
|
||||
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {
|
||||
@Override public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {
|
||||
voxelizationDispatcher.dispatchRange(pass, voxLocStart, voxLocCount, vertexOffset, vertexCount, s());
|
||||
}
|
||||
|
||||
public void endVoxelizationBatch(long pass) {
|
||||
@Override public void endVoxelizationBatch(long pass) {
|
||||
voxelizationDispatcher.endBatch(pass);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -12,12 +11,14 @@ import org.lwjgl.sdl.SDLStdinc;
|
||||
import org.lwjgl.sdl.SDL_DisplayMode;
|
||||
import org.lwjgl.sdl.SDL_LogOutputFunction;
|
||||
import org.lwjgl.sdl.SDLVersion;
|
||||
import org.lwjgl.sdl.SDLVulkan;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.system.Platform;
|
||||
import org.lwjglx.opengl.Display;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLHints.SDL_HINT_GPU_DRIVER;
|
||||
@@ -30,7 +31,10 @@ public final class Device {
|
||||
|
||||
public static final int FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
private static final int REQUESTED_SHADER_FORMATS = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL;
|
||||
|
||||
private long device;
|
||||
private boolean claimed;
|
||||
private int supportedShaderFormats;
|
||||
private String driverName;
|
||||
private String deviceName;
|
||||
@@ -39,12 +43,46 @@ public final class Device {
|
||||
private int presentMode = SDL_GPU_PRESENTMODE_VSYNC;
|
||||
private SDL_LogOutputFunction logCallback; // prevent GC
|
||||
|
||||
private volatile boolean lost;
|
||||
private volatile Supplier<String> lossDiagnostics = () -> "";
|
||||
|
||||
public boolean isLost() { return lost; }
|
||||
|
||||
public void setLossDiagnostics(Supplier<String> diagnostics) { this.lossDiagnostics = diagnostics; }
|
||||
|
||||
public static boolean isDeviceLossError(String sdlError) {
|
||||
if (sdlError == null) return false;
|
||||
return sdlError.contains("DEVICE_LOST") || sdlError.contains("DEVICE_REMOVED") || sdlError.contains("DEVICE_RESET");
|
||||
}
|
||||
|
||||
public void reportGpuFailure(String operation) {
|
||||
if (lost) return;
|
||||
final String err = SDLError.SDL_GetError();
|
||||
if (!isDeviceLossError(err)) {
|
||||
LOG.error("{}: {}", operation, err);
|
||||
return;
|
||||
}
|
||||
lost = true;
|
||||
LOG.error("GPU DEVICE LOST during {}: {}", operation, err);
|
||||
LOG.error(" gpu={} driver={} {} sdl={}", deviceName, driverName, driverVersion, SDLVersion.SDL_GetVersion());
|
||||
for (final String line : lossDiagnostics.get().split("\n")) {
|
||||
LOG.error(" {}", line);
|
||||
}
|
||||
throw new GpuDeviceLostException(operation, err);
|
||||
}
|
||||
|
||||
// Default to vulkan for now until D3D is more tested
|
||||
static String resolveDriverName(String userHint, boolean windows) {
|
||||
return userHint.isEmpty() && windows ? "vulkan" : null;
|
||||
}
|
||||
|
||||
public void claimWindow(long window, boolean lwjglDebug) {
|
||||
static boolean metalNeedsNewerSdl(String driverName, int sdlVersion) {
|
||||
return "metal".equalsIgnoreCase(driverName) && sdlVersion < SDLVersion.SDL_VERSIONNUM(3, 4, 6);
|
||||
}
|
||||
|
||||
public boolean createDevice() {
|
||||
if (device != 0) return true;
|
||||
|
||||
installLogCallback();
|
||||
|
||||
final int ver = SDLVersion.SDL_GetVersion();
|
||||
@@ -56,17 +94,15 @@ public final class Device {
|
||||
LOG.info("SDL GPU driver hint set to '{}'", driverHint);
|
||||
}
|
||||
|
||||
final int requestedFormats = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL;
|
||||
|
||||
final boolean gpuDebug = lwjglDebug || SystemProperties.SDL_GPU_DEBUG;
|
||||
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
|
||||
final String forcedDriver = resolveDriverName(driverHint, Platform.get() == Platform.WINDOWS);
|
||||
device = SDL_CreateGPUDevice(requestedFormats, gpuDebug, forcedDriver);
|
||||
if (device == 0 && forcedDriver != null) {
|
||||
LOG.warn("Failed to create a '{}' SDL GPU device ({}); falling back to SDL's default backend. Set -D{} to pin one.", forcedDriver, SDLError.SDL_GetError(), SystemProperties.KEY_SDL_GPU_DRIVER);
|
||||
device = SDL_CreateGPUDevice(requestedFormats, gpuDebug, (CharSequence) null);
|
||||
device = createGPUDevice(gpuDebug, forcedDriver);
|
||||
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
|
||||
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, forcedDriver));
|
||||
}
|
||||
if (device == 0) {
|
||||
throw new RuntimeException("Failed to create SDL GPU device: " + SDLError.SDL_GetError());
|
||||
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
|
||||
return false;
|
||||
}
|
||||
|
||||
driverName = SDL_GetGPUDeviceDriver(device);
|
||||
@@ -77,12 +113,29 @@ public final class Device {
|
||||
driverVersion = SDLProperties.SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, "");
|
||||
driverInfo = SDLProperties.SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING, "");
|
||||
|
||||
LOG.info("SDL GPU device created: gpu={}, driver={} {}, formats=0x{}, debug={}, forcedDriver={}", deviceName, driverName, driverVersion, Integer.toHexString(supportedShaderFormats), gpuDebug, forcedDriver);
|
||||
LOG.info("SDL GPU device created: gpu={}, driver={} {}, video={}, formats=0x{}, debug={}, forcedDriver={}", deviceName, driverName, driverVersion, SDL_GetCurrentVideoDriver(), Integer.toHexString(supportedShaderFormats), gpuDebug, forcedDriver);
|
||||
|
||||
if ("metal".equalsIgnoreCase(driverName) && ver < SDLVersion.SDL_VERSIONNUM(3, 4, 6)) {
|
||||
LOG.fatal("SDL GPU on Metal requires SDL 3.4.6 or newer; exiting");
|
||||
SDLGPUGate.markFatalInit();
|
||||
return;
|
||||
if (metalNeedsNewerSdl(driverName, ver)) {
|
||||
LOG.error("SDL GPU on Metal requires SDL 3.4.6 or newer (have {}.{}.{}); falling back to OpenGL", SDLVersion.SDL_VERSIONNUM_MAJOR(ver), SDLVersion.SDL_VERSIONNUM_MINOR(ver), SDLVersion.SDL_VERSIONNUM_MICRO(ver));
|
||||
destroyDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long createGPUDevice(boolean gpuDebug, String forcedDriver) {
|
||||
long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, forcedDriver);
|
||||
if (dev == 0 && forcedDriver != null) {
|
||||
LOG.warn("Failed to create a '{}' SDL GPU device ({}); falling back to SDL's default backend. Set -D{} to pin one.", forcedDriver, SDLError.SDL_GetError(), SystemProperties.KEY_SDL_GPU_DRIVER);
|
||||
dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, (CharSequence) null);
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
public void claimWindow(long window) {
|
||||
if (device == 0) {
|
||||
throw new IllegalStateException("SDL GPU device not created");
|
||||
}
|
||||
|
||||
if (window == 0) {
|
||||
@@ -92,6 +145,7 @@ public final class Device {
|
||||
if (!SDL_ClaimWindowForGPUDevice(device, window)) {
|
||||
throw new RuntimeException("Failed to claim window for SDL GPU device: " + SDLError.SDL_GetError());
|
||||
}
|
||||
claimed = true;
|
||||
|
||||
final int n = FRAMES_IN_FLIGHT;
|
||||
if (!SDL_SetGPUAllowedFramesInFlight(device, n)) {
|
||||
@@ -104,6 +158,53 @@ public final class Device {
|
||||
LOG.info("SDL GPU device claimed window successfully");
|
||||
}
|
||||
|
||||
private static boolean vulkanLoaderAvailable() {
|
||||
if (Platform.get() == Platform.MACOSX) return false;
|
||||
if (!SDLVulkan.SDL_Vulkan_LoadLibrary((CharSequence) null)) return false;
|
||||
SDLVulkan.SDL_Vulkan_UnloadLibrary();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void logDeviceCreationFailure(int requestedFormats) {
|
||||
final StringBuilder drivers = new StringBuilder();
|
||||
final int n = SDL_GetNumGPUDrivers();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (i > 0) drivers.append(", ");
|
||||
drivers.append(SDL_GetGPUDriver(i));
|
||||
}
|
||||
final String videoDriver = SDL_GetCurrentVideoDriver();
|
||||
LOG.error("SDL GPU device creation failed. platform={}, videoDriver={}, requestedShaderFormats=0x{}, compiledGpuDrivers=[{}]", Platform.get(), videoDriver, Integer.toHexString(requestedFormats), drivers);
|
||||
|
||||
if (Platform.get() != Platform.MACOSX) {
|
||||
if (SDLVulkan.SDL_Vulkan_LoadLibrary((CharSequence) null)) {
|
||||
SDLVulkan.SDL_Vulkan_UnloadLibrary();
|
||||
LOG.error("A Vulkan loader is present, so no GPU met SDL's requirements");
|
||||
} else {
|
||||
LOG.error("No usable Vulkan loader: {}", SDLError.SDL_GetError());
|
||||
if (!"x11".equalsIgnoreCase(videoDriver)) {
|
||||
LOG.error("Retrying under SDL_VIDEODRIVER=x11 did not help either.");
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.error("Continuing on OpenGL. Set -D{}=false to skip this probe entirely.", SystemProperties.KEY_USE_SDL_GPU);
|
||||
}
|
||||
|
||||
public void destroyDevice() {
|
||||
if (device == 0) return;
|
||||
if (claimed) {
|
||||
final long window = Display.getWindow();
|
||||
if (window != 0) SDL_ReleaseWindowFromGPUDevice(device, window);
|
||||
claimed = false;
|
||||
}
|
||||
SDL_DestroyGPUDevice(device);
|
||||
device = 0;
|
||||
driverName = null;
|
||||
deviceName = null;
|
||||
driverVersion = null;
|
||||
driverInfo = null;
|
||||
supportedShaderFormats = 0;
|
||||
}
|
||||
|
||||
private void logWindowDiagnostics(long window) {
|
||||
final long flags = SDL_GetWindowFlags(window);
|
||||
final boolean hasMetal = (flags & SDL_WINDOW_METAL) != 0;
|
||||
@@ -146,12 +247,7 @@ public final class Device {
|
||||
|
||||
public void shutdown() {
|
||||
if (device != 0) {
|
||||
final long window = Display.getWindow();
|
||||
if (window != 0) {
|
||||
SDL_ReleaseWindowFromGPUDevice(device, window);
|
||||
}
|
||||
SDL_DestroyGPUDevice(device);
|
||||
device = 0;
|
||||
destroyDevice();
|
||||
LOG.info("SDL GPU device destroyed");
|
||||
}
|
||||
}
|
||||
@@ -218,6 +314,7 @@ public final class Device {
|
||||
private void installLogCallback() {
|
||||
if (logCallback != null) return;
|
||||
logCallback = SDL_LogOutputFunction.create((userdata, category, priority, message) -> {
|
||||
if (lost) return;
|
||||
final String msg = memUTF8(message);
|
||||
switch (priority) {
|
||||
case SDLLog.SDL_LOG_PRIORITY_ERROR, SDLLog.SDL_LOG_PRIORITY_CRITICAL -> LOG.error("[SDL] {}", msg);
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
public final class GpuDeviceLostException extends Error {
|
||||
|
||||
public GpuDeviceLostException(String operation, String sdlError) {
|
||||
super("GPU device lost during " + operation + ": " + sdlError
|
||||
+ ". A lost device cannot be recovered, so the game cannot continue. Please report this at "
|
||||
+ "https://github.com/GTNewHorizons/Angelica/issues and attach your full log file (logs/fml-client-latest.log)");
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLHints;
|
||||
import org.lwjgl.sdl.SDLInit;
|
||||
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLInit.SDL_INIT_VIDEO;
|
||||
import static org.lwjgl.sdl.SDLVideo.SDL_GetCurrentVideoDriver;
|
||||
|
||||
public final class VideoDriverRecovery {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final String X11 = "x11";
|
||||
|
||||
private VideoDriverRecovery() {}
|
||||
|
||||
static boolean shouldRetryOnX11(String videoDriver, boolean vulkanLoaderAvailable, String displayEnv) {
|
||||
if (vulkanLoaderAvailable) return false;
|
||||
if (!"wayland".equalsIgnoreCase(videoDriver)) return false;
|
||||
return displayEnv != null && !displayEnv.isEmpty();
|
||||
}
|
||||
|
||||
static long retryUnderX11(LongSupplier createDevice) {
|
||||
final String original = SDL_GetCurrentVideoDriver();
|
||||
LOG.warn("SDL GPU is unavailable under the '{}' video driver; retrying under {}", original, X11);
|
||||
|
||||
if (!restartVideo(X11)) {
|
||||
LOG.warn("Could not restart the SDL video subsystem under {}: {}", X11, SDLError.SDL_GetError());
|
||||
restartVideo(original);
|
||||
return 0L;
|
||||
}
|
||||
|
||||
final long device = createDevice.getAsLong();
|
||||
if (device == 0) {
|
||||
LOG.warn("SDL GPU is unavailable under {} as well; restoring the '{}' video driver", X11, original);
|
||||
restartVideo(original);
|
||||
return 0L;
|
||||
}
|
||||
|
||||
LOG.warn("SDL video driver switched to {} so the SDL GPU backend can run. Set SDL_VIDEODRIVER=wayland to keep the native session and run on OpenGL instead.", X11);
|
||||
return device;
|
||||
}
|
||||
|
||||
private static boolean restartVideo(String driver) {
|
||||
SDLInit.SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
if (driver == null || driver.isEmpty()) {
|
||||
SDLHints.SDL_ResetHint(SDLHints.SDL_HINT_VIDEO_DRIVER);
|
||||
} else {
|
||||
SDLHints.SDL_SetHint(SDLHints.SDL_HINT_VIDEO_DRIVER, driver);
|
||||
}
|
||||
return SDLInit.SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
}
|
||||
}
|
||||
@@ -165,13 +165,20 @@ public final class FrameManager {
|
||||
return tlFrame.get();
|
||||
}
|
||||
|
||||
public FrameState busiestFrame() {
|
||||
FrameState best = null;
|
||||
for (final FrameState f : registeredFrames) {
|
||||
if (best == null || f.frameNumber > best.frameNumber) best = f;
|
||||
}
|
||||
return best != null ? best : frame();
|
||||
}
|
||||
|
||||
public static final long SWAPCHAIN_LAYOUT_HASH = 0x5CA1AB1E5CA1AB1EL;
|
||||
|
||||
public static final long MAX_PENDING_UPLOAD_BYTES = 16L * 1024L * 1024L;
|
||||
public static final int MAX_PENDING_UPLOAD_COMMANDS = 256;
|
||||
|
||||
private Runnable beforeEndCopyPass;
|
||||
private Runnable beforePendingUploadSubmit;
|
||||
|
||||
public FrameManager(Device device) {
|
||||
this.device = device;
|
||||
@@ -409,10 +416,6 @@ public final class FrameManager {
|
||||
this.beforeEndCopyPass = callback;
|
||||
}
|
||||
|
||||
public void setBeforePendingUploadSubmitCallback(Runnable callback) {
|
||||
this.beforePendingUploadSubmit = callback;
|
||||
}
|
||||
|
||||
public void setBeforeSubmitCallback(Runnable callback) {
|
||||
this.beforeSubmit = callback;
|
||||
}
|
||||
@@ -468,12 +471,13 @@ public final class FrameManager {
|
||||
try {
|
||||
if (f.wantFenceOnNextSubmit) {
|
||||
f.wantFenceOnNextSubmit = false;
|
||||
if (f.lastAcquiredFence != 0) SDL_ReleaseGPUFence(device.getDevice(), f.lastAcquiredFence);
|
||||
f.lastAcquiredFence = SDL_SubmitGPUCommandBufferAndAcquireFence(f.commandBuffer);
|
||||
if (f.lastAcquiredFence == 0) {
|
||||
LOG.error("Failed to submit+acquireFence GPU command buffer: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("submit+acquireFence GPU command buffer");
|
||||
}
|
||||
} else if (!SDL_SubmitGPUCommandBuffer(f.commandBuffer)) {
|
||||
LOG.error("Failed to submit GPU command buffer: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("submit GPU command buffer");
|
||||
}
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
@@ -530,12 +534,13 @@ public final class FrameManager {
|
||||
try {
|
||||
if (f.wantFenceOnNextSubmit) {
|
||||
f.wantFenceOnNextSubmit = false;
|
||||
if (f.lastAcquiredFence != 0) SDL_ReleaseGPUFence(device.getDevice(), f.lastAcquiredFence);
|
||||
f.lastAcquiredFence = SDL_SubmitGPUCommandBufferAndAcquireFence(f.commandBuffer);
|
||||
if (f.lastAcquiredFence == 0) {
|
||||
LOG.error("Mid-frame submit+acquireFence failed: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("mid-frame submit+acquireFence");
|
||||
}
|
||||
} else if (!SDL_SubmitGPUCommandBuffer(f.commandBuffer)) {
|
||||
LOG.error("Mid-frame submit failed: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("mid-frame submit");
|
||||
}
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
@@ -616,7 +621,6 @@ public final class FrameManager {
|
||||
return;
|
||||
}
|
||||
endCopyPassIfActive(f);
|
||||
if (beforePendingUploadSubmit != null) beforePendingUploadSubmit.run();
|
||||
if (f.pendingUploadCommandBuffer != 0) {
|
||||
Tracy.beginZone(Z_SDL_SUBMIT);
|
||||
final boolean submitted;
|
||||
@@ -626,7 +630,7 @@ public final class FrameManager {
|
||||
Tracy.endZone();
|
||||
}
|
||||
if (!submitted) {
|
||||
LOG.error("Failed to submit pending upload command buffer: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("submit pending upload command buffer");
|
||||
}
|
||||
f.submitsThisFrame++;
|
||||
f.pendingUploadCommandBuffer = 0;
|
||||
|
||||
+25
-5
@@ -189,6 +189,20 @@ public final class ResourceManager {
|
||||
return SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
}
|
||||
|
||||
public String describeGpuState() {
|
||||
final FrameManager.FrameState f = frameManager.busiestFrame();
|
||||
final StringBuilder sb = new StringBuilder(256);
|
||||
sb.append("frame=").append(f.frameNumber)
|
||||
.append(" submitsThisFrame=").append(f.submitsThisFrame)
|
||||
.append(" mipGensThisFrame=").append(f.mipGensThisFrame)
|
||||
.append(" pendingUpload=").append(f.pendingUploadBytes >> 10).append("KiB/").append(f.pendingUploadCommands).append("cmds")
|
||||
.append('\n');
|
||||
sb.append("arena capacity=").append(f.arena.capacity() >> 20).append("MiB used=").append(f.arena.usedBytes() >> 10)
|
||||
.append("KiB copies=").append(f.arena.copyCount())
|
||||
.append(" overflowFlushes=").append(f.arenaOverflowFlushesThisFrame);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private long createTextureCore(int glId, int glTarget, int sdlFormat, int glFormat, int width, int height, int depth, int levels, int usage) {
|
||||
usage = reconcileUsageAndWarn(glId, usage);
|
||||
final int sdlType = mapTextureType(glTarget);
|
||||
@@ -221,7 +235,7 @@ public final class ResourceManager {
|
||||
final long handle = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
if (props != 0) SDLProperties.SDL_DestroyProperties(props);
|
||||
if (handle == 0) {
|
||||
LOG.error("Failed to create texture {}x{} fmt=0x{} sdlFmt=0x{} usage=0x{}: {}", width, height, Integer.toHexString(glFormat), Integer.toHexString(sdlFormat), Integer.toHexString(usage), SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("createTexture " + width + "x" + height + " fmt=0x" + Integer.toHexString(glFormat) + " sdlFmt=0x" + Integer.toHexString(sdlFormat) + " usage=0x" + Integer.toHexString(usage));
|
||||
return 0;
|
||||
}
|
||||
wLock.lock();
|
||||
@@ -1311,7 +1325,7 @@ public final class ResourceManager {
|
||||
|
||||
final long handle = SDL_CreateGPUTransferBuffer(device.getDevice(), ci);
|
||||
if (handle == 0) {
|
||||
LOG.error("Failed to create transfer buffer size={}: {}", size, SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("createTransferBuffer size=" + size);
|
||||
} else {
|
||||
synchronized (xferPoolLock) { liveTransferBufferHandles.add(handle); }
|
||||
}
|
||||
@@ -1413,7 +1427,7 @@ public final class ResourceManager {
|
||||
f.arenaCommandBuffer = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (f.arenaCommandBuffer == 0) {
|
||||
LOG.error("Failed to acquire arena upload command buffer: {}", SDLError.SDL_GetError());
|
||||
f.arenaMapped = null;
|
||||
unmapArena(f);
|
||||
return false;
|
||||
}
|
||||
f.arenaCopyPass = SDL_BeginGPUCopyPass(f.arenaCommandBuffer);
|
||||
@@ -1421,12 +1435,18 @@ public final class ResourceManager {
|
||||
LOG.error("Failed to begin arena copy pass: {}", SDLError.SDL_GetError());
|
||||
SDL_SubmitGPUCommandBuffer(f.arenaCommandBuffer);
|
||||
f.arenaCommandBuffer = 0;
|
||||
f.arenaMapped = null;
|
||||
unmapArena(f);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void unmapArena(FrameManager.FrameState f) {
|
||||
if (f.arenaMapped == null) return;
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), f.arenaXfer);
|
||||
f.arenaMapped = null;
|
||||
}
|
||||
|
||||
public void flushUploadArena() {
|
||||
flushUploadArena(frameManager.frame());
|
||||
}
|
||||
@@ -1442,7 +1462,7 @@ public final class ResourceManager {
|
||||
}
|
||||
if (f.arenaCommandBuffer != 0) {
|
||||
if (!SDL_SubmitGPUCommandBuffer(f.arenaCommandBuffer)) {
|
||||
LOG.error("Failed to submit arena upload command buffer: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("submit arena upload command buffer");
|
||||
}
|
||||
f.arenaCommandBuffer = 0;
|
||||
if (Tracy.ENABLED) f.arenaSubmitsThisFrame++;
|
||||
|
||||
@@ -207,15 +207,13 @@ public final class TextureOps {
|
||||
SDL_GenerateMipmapsForGPUTexture(uploadCb, handle);
|
||||
}
|
||||
|
||||
if (!SDL_SubmitGPUCommandBuffer(uploadCb)) {
|
||||
device.reportGpuFailure("submit mipmap generation CB");
|
||||
}
|
||||
|
||||
if (f.commandBuffer == uploadCb) {
|
||||
if (!SDL_SubmitGPUCommandBuffer(uploadCb)) {
|
||||
LOG.error("drainPendingMipGen: failed to submit frame CB: {}", SDLError.SDL_GetError());
|
||||
}
|
||||
f.commandBuffer = 0;
|
||||
} else {
|
||||
if (!SDL_SubmitGPUCommandBuffer(uploadCb)) {
|
||||
LOG.error("drainPendingMipGen: failed to submit pending-upload CB: {}", SDLError.SDL_GetError());
|
||||
}
|
||||
f.pendingUploadCommandBuffer = 0;
|
||||
f.pendingUploadBytes = 0;
|
||||
f.pendingUploadCommands = 0;
|
||||
|
||||
+34
-24
@@ -17,6 +17,7 @@ import java.util.concurrent.locks.LockSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.GpuDeviceLostException;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
@@ -215,32 +216,41 @@ public final class TransferThread {
|
||||
|
||||
private void run() {
|
||||
LOG.info("Transfer thread started");
|
||||
while (!shutdown) {
|
||||
final long workStart = Tracy.ENABLED ? System.nanoTime() : 0L;
|
||||
// Drain all pending uploads
|
||||
DeferredUpload upload = queue.poll();
|
||||
if (upload != null) {
|
||||
do {
|
||||
processOne(upload);
|
||||
upload = queue.poll();
|
||||
} while (upload != null);
|
||||
}
|
||||
|
||||
if (shouldFlush(openCB != 0, flushRequestedSeq, submittedSeq, openBytes, openCount)) {
|
||||
flushOpenCB();
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) pendingActiveNanos += System.nanoTime() - workStart;
|
||||
|
||||
if (queue.isEmpty() && !shutdown) {
|
||||
parked = true;
|
||||
if (queue.isEmpty() && !shutdown && flushRequestedSeq <= submittedSeq) {
|
||||
LockSupport.parkNanos(1_000_000L); // 1ms
|
||||
try {
|
||||
while (!shutdown) {
|
||||
final long workStart = Tracy.ENABLED ? System.nanoTime() : 0L;
|
||||
// Drain all pending uploads
|
||||
DeferredUpload upload = queue.poll();
|
||||
if (upload != null) {
|
||||
do {
|
||||
processOne(upload);
|
||||
upload = queue.poll();
|
||||
} while (upload != null);
|
||||
}
|
||||
parked = false;
|
||||
|
||||
if (shouldFlush(openCB != 0, flushRequestedSeq, submittedSeq, openBytes, openCount)) {
|
||||
flushOpenCB();
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) pendingActiveNanos += System.nanoTime() - workStart;
|
||||
|
||||
if (queue.isEmpty() && !shutdown) {
|
||||
parked = true;
|
||||
if (queue.isEmpty() && !shutdown && flushRequestedSeq <= submittedSeq) {
|
||||
LockSupport.parkNanos(1_000_000L); // 1ms
|
||||
}
|
||||
parked = false;
|
||||
}
|
||||
}
|
||||
if (openCB != 0) flushOpenCB();
|
||||
} catch (GpuDeviceLostException e) {
|
||||
LOG.error("Transfer thread stopping: {}", e.getMessage());
|
||||
} finally {
|
||||
synchronized (submittedLock) {
|
||||
submittedSeq = Long.MAX_VALUE;
|
||||
submittedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
if (openCB != 0) flushOpenCB();
|
||||
drainAllPendingFreesOnShutdown();
|
||||
srcLoc.free();
|
||||
dstRegion.free();
|
||||
@@ -355,7 +365,7 @@ public final class TransferThread {
|
||||
|
||||
SDL_EndGPUCopyPass(openCopyPass);
|
||||
if (!SDL_SubmitGPUCommandBuffer(openCB)) {
|
||||
LOG.error("Failed to submit transfer command buffer: {}", SDLError.SDL_GetError());
|
||||
device.reportGpuFailure("submit transfer command buffer");
|
||||
}
|
||||
|
||||
synchronized (submittedLock) {
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
public final class UploadArena {
|
||||
public static final int INITIAL_CAPACITY = 16 * 1024 * 1024;
|
||||
|
||||
public static final int MAX_CAPACITY = 64 * 1024 * 1024;
|
||||
|
||||
private static final int ALIGNMENT = 16;
|
||||
|
||||
private int capacity = INITIAL_CAPACITY;
|
||||
@@ -23,8 +25,11 @@ public final class UploadArena {
|
||||
return size >= 0 && size <= capacity;
|
||||
}
|
||||
|
||||
public void requestGrow(int minSize) {
|
||||
while (growTo < minSize) growTo <<= 1;
|
||||
public void requestGrow(int minSize) {
|
||||
if (minSize <= growTo) return;
|
||||
long next = growTo;
|
||||
while (next < minSize && next < MAX_CAPACITY) next <<= 1;
|
||||
growTo = (int) Math.min(next, MAX_CAPACITY);
|
||||
}
|
||||
|
||||
public boolean applyGrow() {
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLInit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.lwjgl.sdl.SDLInit.SDL_INIT_VIDEO;
|
||||
|
||||
class DeviceLifecycleGpuTest {
|
||||
|
||||
private Device device;
|
||||
|
||||
@BeforeAll
|
||||
static void initSdl() {
|
||||
assumeTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (device != null) device.destroyDevice();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsWithoutAWindowAndIsMemoized() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
|
||||
final long handle = device.getDevice();
|
||||
assertNotEquals(0L, handle);
|
||||
assertTrue(device.createDevice(), "second createDevice should be a no-op");
|
||||
assertEquals(handle, device.getDevice(), "createDevice must not replace a live device");
|
||||
}
|
||||
|
||||
@Test
|
||||
void destroyIsIdempotentOnAnUnclaimedDevice() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
|
||||
device.destroyDevice();
|
||||
assertEquals(0L, device.getDevice());
|
||||
device.destroyDevice();
|
||||
assertEquals(0L, device.getDevice());
|
||||
}
|
||||
}
|
||||
+17
@@ -2,9 +2,13 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.metalNeedsNewerSdl;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.resolveDriverName;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLVersion.SDL_VERSIONNUM;
|
||||
|
||||
class DriverSelectionTest {
|
||||
|
||||
@@ -24,4 +28,17 @@ class DriverSelectionTest {
|
||||
assertNull(resolveDriverName("", false));
|
||||
assertNull(resolveDriverName("direct3d12", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void metalBelow346FallsBack() {
|
||||
assertTrue(metalNeedsNewerSdl("metal", SDL_VERSIONNUM(3, 4, 5)));
|
||||
assertFalse(metalNeedsNewerSdl("metal", SDL_VERSIONNUM(3, 4, 6)));
|
||||
assertFalse(metalNeedsNewerSdl("metal", SDL_VERSIONNUM(3, 5, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherDriversIgnoreTheMetalFloor() {
|
||||
assertFalse(metalNeedsNewerSdl("vulkan", SDL_VERSIONNUM(3, 4, 5)));
|
||||
assertFalse(metalNeedsNewerSdl("direct3d12", SDL_VERSIONNUM(3, 0, 0)));
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.VideoDriverRecovery.shouldRetryOnX11;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class VideoDriverRecoveryTest {
|
||||
|
||||
@Test
|
||||
void waylandWithoutVulkanSurfaceRetries() {
|
||||
assertTrue(shouldRetryOnX11("wayland", false, ":0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void waylandWithVulkanStays() {
|
||||
assertFalse(shouldRetryOnX11("wayland", true, ":0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noXWaylandMeansNoRetry() {
|
||||
assertFalse(shouldRetryOnX11("wayland", false, null));
|
||||
assertFalse(shouldRetryOnX11("wayland", false, ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherVideoDriversAreLeftAlone() {
|
||||
assertFalse(shouldRetryOnX11("x11", false, ":0"));
|
||||
assertFalse(shouldRetryOnX11("cocoa", false, ":0"));
|
||||
assertFalse(shouldRetryOnX11("windows", false, ":0"));
|
||||
assertFalse(shouldRetryOnX11(null, false, ":0"));
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,26 @@ class UploadArenaTest {
|
||||
assertEquals(UploadArena.INITIAL_CAPACITY * 4, a.capacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestGrowClampsAtMaxCapacityInsteadOfSpinning() {
|
||||
final UploadArena a = new UploadArena();
|
||||
a.requestGrow(Integer.MAX_VALUE);
|
||||
assertTrue(a.applyGrow());
|
||||
assertEquals(UploadArena.MAX_CAPACITY, a.capacity());
|
||||
a.requestGrow(Integer.MAX_VALUE);
|
||||
assertFalse(a.applyGrow());
|
||||
assertEquals(UploadArena.MAX_CAPACITY, a.capacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestGrowIsIdempotentBelowCurrentTarget() {
|
||||
final UploadArena a = new UploadArena();
|
||||
a.requestGrow(UploadArena.INITIAL_CAPACITY * 4);
|
||||
a.requestGrow(1);
|
||||
assertTrue(a.applyGrow());
|
||||
assertEquals(UploadArena.INITIAL_CAPACITY * 4, a.capacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void overflowThenGrowRoundTrip() {
|
||||
final UploadArena a = new UploadArena();
|
||||
|
||||
@@ -158,7 +158,7 @@ public class BatchingFontRenderer {
|
||||
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
private static final class DeferredTextSegment {
|
||||
private static final class TextSegment {
|
||||
final Matrix4f mvp = new Matrix4f();
|
||||
BatchingFontRenderer owner;
|
||||
int cmdStart;
|
||||
@@ -172,10 +172,17 @@ public class BatchingFontRenderer {
|
||||
private static int deferredIdxPos;
|
||||
|
||||
private int deferredCmdWatermark;
|
||||
private static final ObjectArrayList<DeferredTextSegment> deferredSegments = ObjectArrayList.wrap(new DeferredTextSegment[16], 0);
|
||||
private static final ObjectArrayList<DeferredTextSegment> deferredSegmentPool = ObjectArrayList.wrap(new DeferredTextSegment[16], 0);
|
||||
private static final ObjectArrayList<TextSegment> deferredSegments = ObjectArrayList.wrap(new TextSegment[16], 0);
|
||||
private static final ObjectArrayList<TextSegment> deferredSegmentPool = ObjectArrayList.wrap(new TextSegment[16], 0);
|
||||
private boolean deferCurrentBatch;
|
||||
|
||||
private final Matrix4f batchProj = new Matrix4f();
|
||||
private final Matrix4f batchModelView = new Matrix4f();
|
||||
private boolean batchMatrixValid;
|
||||
private final ObjectArrayList<TextSegment> batchSegments = ObjectArrayList.wrap(new TextSegment[16], 0);
|
||||
private int batchSealedEnd;
|
||||
private static BatchingFontRenderer arenaOwner;
|
||||
|
||||
|
||||
private void allocateBuffers() {
|
||||
populateEBO(rawCapacity / VERTEX_SIZE);
|
||||
@@ -298,8 +305,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
// Never coalesce into a command below the watermark - it belongs to a sealed deferred segment.
|
||||
if (batchCommands.size() > deferredCmdWatermark) {
|
||||
arenaOwner = this;
|
||||
// Never coalesce into a command below the watermark - it belongs to a sealed segment.
|
||||
if (batchCommands.size() > batchSealedEnd) {
|
||||
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
|
||||
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
|
||||
if (prevEndVtx == startIdx && lastCmd.texture == texture) {
|
||||
@@ -370,9 +378,55 @@ public class BatchingFontRenderer {
|
||||
* allow for easier optimizing of blocks of font rendering code.
|
||||
*/
|
||||
public void beginBatch() {
|
||||
if (arenaOwner != null && arenaOwner != this) {
|
||||
arenaOwner.flushBatch();
|
||||
}
|
||||
if (batchDepth > 0) {
|
||||
if (batchCommands.size() == batchSealedEnd) {
|
||||
captureBatchMatrix();
|
||||
} else if (!batchProj.equals(GLStateManager.getProjectionMatrix(), 0.0f) || !batchModelView.equals(GLStateManager.getModelViewMatrix(), 0.0f)) {
|
||||
sealBatchSegment();
|
||||
captureBatchMatrix();
|
||||
}
|
||||
}
|
||||
batchDepth++;
|
||||
}
|
||||
|
||||
private void captureBatchMatrix() {
|
||||
batchProj.set(GLStateManager.getProjectionMatrix());
|
||||
batchModelView.set(GLStateManager.getModelViewMatrix());
|
||||
batchMatrixValid = true;
|
||||
}
|
||||
|
||||
private void resolveMvp(Matrix4f dest) {
|
||||
if (batchMatrixValid) {
|
||||
batchProj.mul(batchModelView, dest);
|
||||
} else {
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), dest);
|
||||
}
|
||||
}
|
||||
|
||||
private void sealBatchSegment() {
|
||||
final int end = batchCommands.size();
|
||||
if (end == batchSealedEnd) return;
|
||||
final TextSegment segment = deferredSegmentPool.isEmpty() ? new TextSegment() : deferredSegmentPool.pop();
|
||||
segment.owner = this;
|
||||
segment.cmdStart = batchSealedEnd;
|
||||
segment.cmdEnd = end;
|
||||
resolveMvp(segment.mvp);
|
||||
batchSegments.add(segment);
|
||||
batchSealedEnd = end;
|
||||
}
|
||||
|
||||
private void recycleBatchSegments() {
|
||||
if (batchSegments.isEmpty()) return;
|
||||
for (int i = 0, n = batchSegments.size(); i < n; i++) {
|
||||
batchSegments.get(i).owner = null;
|
||||
}
|
||||
deferredSegmentPool.addAll(batchSegments);
|
||||
batchSegments.clear();
|
||||
}
|
||||
|
||||
public void endBatch() {
|
||||
if (batchDepth <= 0) {
|
||||
batchDepth = 0;
|
||||
@@ -404,7 +458,8 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
private void deferBatch() {
|
||||
if (batchCommands.size() == deferredCmdWatermark || vertexDataPos == deferredVertexPos) {
|
||||
sealBatchSegment();
|
||||
if (batchSegments.isEmpty()) {
|
||||
truncateBatchToWatermark();
|
||||
return;
|
||||
}
|
||||
@@ -412,15 +467,13 @@ public class BatchingFontRenderer {
|
||||
flushBatch();
|
||||
return;
|
||||
}
|
||||
final DeferredTextSegment segment = deferredSegmentPool.isEmpty() ? new DeferredTextSegment() : deferredSegmentPool.pop();
|
||||
segment.owner = this;
|
||||
segment.cmdStart = deferredCmdWatermark;
|
||||
segment.cmdEnd = batchCommands.size();
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), segment.mvp);
|
||||
deferredSegments.add(segment);
|
||||
deferredSegments.addAll(batchSegments);
|
||||
batchSegments.clear();
|
||||
deferredCmdWatermark = batchCommands.size();
|
||||
batchSealedEnd = deferredCmdWatermark;
|
||||
deferredVertexPos = vertexDataPos;
|
||||
deferredIdxPos = idxWriterIndex;
|
||||
batchMatrixValid = false;
|
||||
}
|
||||
|
||||
public static void flushDeferredText() {
|
||||
@@ -443,7 +496,7 @@ public class BatchingFontRenderer {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
for (int i = 0, n = deferredSegments.size(); i < n; i++) {
|
||||
final DeferredTextSegment segment = deferredSegments.get(i);
|
||||
final TextSegment segment = deferredSegments.get(i);
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf);
|
||||
@@ -459,8 +512,11 @@ public class BatchingFontRenderer {
|
||||
public static void discardDeferredText() {
|
||||
if (deferredSegments.isEmpty()) return;
|
||||
for (int i = 0, n = deferredSegments.size(); i < n; i++) {
|
||||
final DeferredTextSegment segment = deferredSegments.get(i);
|
||||
final TextSegment segment = deferredSegments.get(i);
|
||||
segment.owner.clearDeferredCommands();
|
||||
segment.owner.recycleBatchSegments();
|
||||
segment.owner.batchSealedEnd = 0;
|
||||
segment.owner.batchMatrixValid = false;
|
||||
segment.owner = null;
|
||||
}
|
||||
deferredSegmentPool.addAll(deferredSegments);
|
||||
@@ -469,6 +525,7 @@ public class BatchingFontRenderer {
|
||||
deferredIdxPos = 0;
|
||||
vertexDataPos = 0;
|
||||
idxWriterIndex = 0;
|
||||
arenaOwner = null;
|
||||
}
|
||||
|
||||
private void clearDeferredCommands() {
|
||||
@@ -477,6 +534,12 @@ public class BatchingFontRenderer {
|
||||
batchCommandPool.add(batchCommands.get(i));
|
||||
}
|
||||
batchCommands.removeElements(0, deferredCmdWatermark);
|
||||
for (int i = 0, n = batchSegments.size(); i < n; i++) {
|
||||
final TextSegment segment = batchSegments.get(i);
|
||||
segment.cmdStart -= deferredCmdWatermark;
|
||||
segment.cmdEnd -= deferredCmdWatermark;
|
||||
}
|
||||
batchSealedEnd = Math.max(0, batchSealedEnd - deferredCmdWatermark);
|
||||
deferredCmdWatermark = 0;
|
||||
}
|
||||
|
||||
@@ -495,7 +558,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
private void flushBatchInner() {
|
||||
final int cmdCount = batchCommands.size();
|
||||
if (vertexDataPos == deferredVertexPos || cmdCount == deferredCmdWatermark) {
|
||||
final int segmentCount = batchSegments.size();
|
||||
if (segmentCount == 0 && (vertexDataPos == deferredVertexPos || cmdCount == deferredCmdWatermark)) {
|
||||
truncateBatchToWatermark();
|
||||
return;
|
||||
}
|
||||
@@ -504,7 +568,12 @@ public class BatchingFontRenderer {
|
||||
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
Arrays.sort(batchCommands.elements(), deferredCmdWatermark, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
final FontDrawCmd[] cmds = batchCommands.elements();
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
final TextSegment segment = batchSegments.get(i);
|
||||
Arrays.sort(cmds, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
}
|
||||
Arrays.sort(cmds, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND);
|
||||
@@ -513,16 +582,25 @@ public class BatchingFontRenderer {
|
||||
final boolean depthMaskBefore = GLStateManager.getDepthState().isEnabled();
|
||||
|
||||
setupFontDrawState();
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
}
|
||||
|
||||
flushLastTexture = DUMMY_RESOURCE_LOCATION;
|
||||
flushTextureChanged = false;
|
||||
drawCommands(batchCommands.elements(), deferredCmdWatermark, cmdCount, this);
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
final TextSegment segment = batchSegments.get(i);
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
|
||||
}
|
||||
if (batchSealedEnd < cmdCount) {
|
||||
mvpBuf.clear();
|
||||
resolveMvp(scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
drawCommands(cmds, batchSealedEnd, cmdCount, this);
|
||||
}
|
||||
}
|
||||
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore);
|
||||
restoreDepth(depthTestBefore, depthMaskBefore);
|
||||
|
||||
@@ -622,12 +700,18 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
private void truncateBatchToWatermark() {
|
||||
recycleBatchSegments();
|
||||
for (int i = batchCommands.size() - 1; i >= deferredCmdWatermark; i--) {
|
||||
batchCommandPool.add(batchCommands.get(i));
|
||||
}
|
||||
batchCommands.size(deferredCmdWatermark);
|
||||
batchSealedEnd = deferredCmdWatermark;
|
||||
batchMatrixValid = false;
|
||||
vertexDataPos = deferredVertexPos;
|
||||
idxWriterIndex = deferredIdxPos;
|
||||
if (arenaOwner == this) {
|
||||
arenaOwner = null;
|
||||
}
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import net.coderbot.iris.Iris;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
@@ -59,10 +59,10 @@ public class IrisGLSMBridge {
|
||||
};
|
||||
GLSMHooks.postTransformProcessor = (src, shaderType) -> {
|
||||
if (Iris.ShaderTransformExecutor.isOnWorker()) {
|
||||
ShaderManager.prewarmSpirv(src, shaderType.id);
|
||||
SDLGPUGate.prewarmSpirv(src, shaderType.id);
|
||||
} else {
|
||||
Iris.ShaderTransformExecutor.submitTracked(() -> {
|
||||
ShaderManager.prewarmSpirv(src, shaderType.id);
|
||||
SDLGPUGate.prewarmSpirv(src, shaderType.id);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,7 +91,10 @@ public enum Mixins implements IMixins {
|
||||
ANGELICA_SDL_GPU_DISPLAY(new MixinBuilder("SDL-GPU-aware Display.create path")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable())
|
||||
.addClientMixins("sdlgpu.MixinForgeHooksClient_SDLGPUDisplay")
|
||||
.addClientMixins(
|
||||
"sdlgpu.MixinForgeHooksClient_SDLGPUDisplay",
|
||||
"sdlgpu.MixinMinecraft_SDLGPUIcons"
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_SDL_GPU_SHADOW_VOXEL_PREPASS(new MixinBuilder("Voxelization compute pre-pass before shadow raster (SDL_GPU)")
|
||||
@@ -168,7 +171,10 @@ public enum Mixins implements IMixins {
|
||||
.addClientMixins(
|
||||
"angelica.entity.MixinModelRenderer",
|
||||
"angelica.entity.MixinRenderGlobal_EntityBatch",
|
||||
"angelica.entity.MixinTextureManager"
|
||||
"angelica.entity.MixinRenderManager_BatchEligibility",
|
||||
"angelica.entity.MixinRender_BatchEligibility",
|
||||
"angelica.entity.MixinTextureManager",
|
||||
"angelica.tesr.MixinTileEntitySpecialRenderer_BatchEligibility"
|
||||
)
|
||||
),
|
||||
|
||||
@@ -197,9 +203,9 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_TESR_PROVIDER_DISPATCH(new MixinBuilder("Route TesrMeshProvider renderers through the batched mesh cache")
|
||||
ANGELICA_TESR_PROVIDER_DISPATCH(new MixinBuilder("Route TesrMeshProvider renderers through the batched mesh cache, and observe renderers that mix model parts with their own draws")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableTESRProviderDispatch)
|
||||
.setApplyIf(() -> AngelicaConfig.enableTESRProviderDispatch || AngelicaConfig.enableEntityBatching)
|
||||
.addClientMixins(
|
||||
"angelica.tesr.MixinTileEntityRendererDispatcher"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface IBatchEligibility {
|
||||
|
||||
byte angelica$batchState();
|
||||
|
||||
void angelica$setBatchState(byte state);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/** Excludes renderers that mix batched model parts with their own draws, which reorders their geometry. */
|
||||
public final class BatchEligibility {
|
||||
|
||||
public static final byte UNKNOWN = 0;
|
||||
public static final byte SAFE = 1;
|
||||
public static final byte DENIED = 2;
|
||||
|
||||
private static int depth;
|
||||
private static boolean allowed;
|
||||
private static long drawsAtStart;
|
||||
private static long expectedDraws;
|
||||
private static int parts;
|
||||
|
||||
private BatchEligibility() {}
|
||||
|
||||
public static boolean begin(byte state, long drawCount) {
|
||||
if (depth++ > 0) return allowed;
|
||||
drawsAtStart = drawCount;
|
||||
expectedDraws = 0L;
|
||||
parts = 0;
|
||||
allowed = state == SAFE;
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public static byte end(byte state, long drawCount) {
|
||||
if (--depth > 0) return state;
|
||||
allowed = false;
|
||||
if (parts == 0) return state;
|
||||
final long foreign = (drawCount - drawsAtStart) - expectedDraws;
|
||||
if (foreign > 0L) return DENIED;
|
||||
return state == UNKNOWN ? SAFE : state;
|
||||
}
|
||||
|
||||
public static boolean batchingAllowed() {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public static void onPartQueued() {
|
||||
parts++;
|
||||
}
|
||||
|
||||
public static void onPartFallback(long drawsBefore, long drawsNow) {
|
||||
parts++;
|
||||
if (!allowed) {
|
||||
expectedDraws += drawsNow - drawsBefore;
|
||||
}
|
||||
}
|
||||
|
||||
public static void onStateChange(Object renderer, byte state) {
|
||||
if (state != DENIED) return;
|
||||
final String name = renderer.getClass().getName();
|
||||
GLStateManager.warnOnce("tesr-mixed:" + name, "{} draws its own geometry alongside model parts - excluding it from model part batching", name);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,8 @@ public final class ModelPartBatcher {
|
||||
TEXTURE("tesr.bail.texture"),
|
||||
TEMPLATE("tesr.bail.template"),
|
||||
HURT_FLASH("tesr.bail.hurtFlash"),
|
||||
FOREIGN_PROGRAM("tesr.bail.foreignProgram");
|
||||
FOREIGN_PROGRAM("tesr.bail.foreignProgram"),
|
||||
MIXED_RENDERER("tesr.bail.mixedRenderer");
|
||||
|
||||
public static final BailReason[] VALUES = values();
|
||||
public final String plotName;
|
||||
@@ -204,6 +205,10 @@ public final class ModelPartBatcher {
|
||||
r.bail(BailReason.INACTIVE);
|
||||
return false;
|
||||
}
|
||||
if (!BatchEligibility.batchingAllowed()) {
|
||||
r.bail(BailReason.MIXED_RENDERER);
|
||||
return false;
|
||||
}
|
||||
if (DisplayListManager.isRecording()) {
|
||||
r.bail(BailReason.RECORDING);
|
||||
return false;
|
||||
@@ -212,6 +217,7 @@ public final class ModelPartBatcher {
|
||||
r.liveFallbacks++;
|
||||
return false;
|
||||
}
|
||||
BatchEligibility.onPartQueued();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.voxelization;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor;
|
||||
import org.embeddedt.embeddium.impl.gl.attribute.GlVertexFormat;
|
||||
import org.embeddedt.embeddium.impl.gl.buffer.GlBuffer;
|
||||
@@ -26,8 +26,8 @@ public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
|
||||
private float pendingY;
|
||||
private float pendingZ;
|
||||
|
||||
private static SDLGPURenderBackend sdl() {
|
||||
return (SDLGPURenderBackend) BackendManager.RENDER_BACKEND;
|
||||
private static RenderBackend sdl() {
|
||||
return BackendManager.RENDER_BACKEND;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,8 +9,7 @@ public class RenderPassAPI {
|
||||
|
||||
public static RenderPassAPI instance = new RenderPassAPI();
|
||||
|
||||
private static final String[] NAMES = new String[] { "solid", "cutout_mipped", "cutout", "translucent", "backface",
|
||||
"overlay" };
|
||||
private static final String[] NAMES = new String[] { "solid", "cutout_mipped", "cutout", "translucent", "backface", "overlay" };
|
||||
|
||||
public static final int SOLID_RENDER_PASS = 0;
|
||||
public static final int CUTOUT_MIPPED_RENDER_PASS = 1;
|
||||
|
||||
@@ -4,12 +4,14 @@ import com.google.common.base.Throwables;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.Tags;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import cpw.mods.fml.client.registry.ClientRegistry;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.InputEvent;
|
||||
@@ -309,9 +311,9 @@ public class Iris {
|
||||
// Clear transformation caches - no longer needed after loading
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
SpirvCompiler.clearCache();
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
ShaderManager.clearPrewarmCache();
|
||||
if (RenderSystem.isGLES() || BackendManager.RENDER_BACKEND.isSDLGPU()) SpirvCompiler.clearCache();
|
||||
SDLGPUGate.clearShaderPrewarmCache();
|
||||
} else {
|
||||
// Still active (or in-flight), schedule another check.
|
||||
final long remainingSeconds = Math.max(1, IDLE_TIMEOUT_SECONDS - idleSeconds + 1);
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4fc;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -13,7 +14,9 @@ public abstract class MixinModelRenderer {
|
||||
@Redirect(method = {"render", "renderWithRotation"}, at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", remap = false))
|
||||
private void angelica$batchPart(int list, float scale) {
|
||||
if (!ModelPartBatcher.partDraw((ModelRenderer) (Object) this, scale, null)) {
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
GLStateManager.glCallList(list);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IBatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(RenderManager.class)
|
||||
public class MixinRenderManager_BatchEligibility {
|
||||
|
||||
@Redirect(method = "func_147939_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V"))
|
||||
private void angelica$observeDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
final IBatchEligibility holder = (IBatchEligibility) render;
|
||||
final byte state = holder.angelica$batchState();
|
||||
BatchEligibility.begin(state, GLStateManager.drawCalls);
|
||||
try {
|
||||
render.doRender(entity, x, y, z, yaw, partialTicks);
|
||||
} finally {
|
||||
final byte next = BatchEligibility.end(state, GLStateManager.drawCalls);
|
||||
if (next != state) {
|
||||
holder.angelica$setBatchState(next);
|
||||
BatchEligibility.onStateChange(render, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IBatchEligibility;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(Render.class)
|
||||
public abstract class MixinRender_BatchEligibility implements IBatchEligibility {
|
||||
|
||||
@Unique
|
||||
private byte angelica$batchState;
|
||||
|
||||
@Override
|
||||
public byte angelica$batchState() {
|
||||
return angelica$batchState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void angelica$setBatchState(byte state) {
|
||||
angelica$batchState = state;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -8,11 +8,12 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.compat.DriverCompatabilityCheck;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.render.SelectionBoxRenderer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUDisplayBridge;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import org.embeddedt.embeddium.impl.gl.debug.GLDebug;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
@@ -27,7 +28,7 @@ public class MixinInitGLStateManager {
|
||||
|
||||
@Inject(method = "initializeTextures", at = @At("HEAD"))
|
||||
private static void angelica$installSDLDrawable(CallbackInfo ci) {
|
||||
SDLGPUDisplayBridge.ensureDrawableInstalled();
|
||||
SDLGPUGate.ensureDrawableInstalled();
|
||||
}
|
||||
|
||||
@Inject(method = "initializeTextures", at = @At("RETURN"))
|
||||
|
||||
+20
-1
@@ -1,5 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IBatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrProviderDispatch;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
@@ -13,8 +17,23 @@ public class MixinTileEntityRendererDispatcher {
|
||||
|
||||
@Redirect(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/tileentity/TileEntitySpecialRenderer;renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V"))
|
||||
private void angelica$dispatchTesrProvider(TileEntitySpecialRenderer renderer, TileEntity te, double x, double y, double z, float partialTicks) {
|
||||
if (!TesrProviderDispatch.tryRender(renderer, te, x, y, z)) {
|
||||
if (AngelicaConfig.enableTESRProviderDispatch && TesrProviderDispatch.tryRender(renderer, te, x, y, z)) return;
|
||||
if (!AngelicaConfig.enableEntityBatching) {
|
||||
renderer.renderTileEntityAt(te, x, y, z, partialTicks);
|
||||
return;
|
||||
}
|
||||
|
||||
final IBatchEligibility holder = (IBatchEligibility) renderer;
|
||||
final byte state = holder.angelica$batchState();
|
||||
BatchEligibility.begin(state, GLStateManager.drawCalls);
|
||||
try {
|
||||
renderer.renderTileEntityAt(te, x, y, z, partialTicks);
|
||||
} finally {
|
||||
final byte next = BatchEligibility.end(state, GLStateManager.drawCalls);
|
||||
if (next != state) {
|
||||
holder.angelica$setBatchState(next);
|
||||
BatchEligibility.onStateChange(renderer, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IBatchEligibility;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(TileEntitySpecialRenderer.class)
|
||||
public abstract class MixinTileEntitySpecialRenderer_BatchEligibility implements IBatchEligibility {
|
||||
|
||||
@Unique
|
||||
private byte angelica$batchState;
|
||||
|
||||
@Override
|
||||
public byte angelica$batchState() {
|
||||
return angelica$batchState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void angelica$setBatchState(byte state) {
|
||||
angelica$batchState = state;
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -1,8 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -26,20 +24,22 @@ public abstract class MixinForgeHooksClient_SDLGPUDisplay {
|
||||
|
||||
@Inject(method = "createDisplay", at = @At("HEAD"), cancellable = true)
|
||||
private static void angelicaSdl$createDisplay(CallbackInfo ci) throws LWJGLException {
|
||||
if (!SDLGPUGate.isDeviceReady()) {
|
||||
LOGGER.warn("No usable SDL GPU device; creating an OpenGL window instead");
|
||||
return;
|
||||
}
|
||||
|
||||
ImageIO.setUseCache(false);
|
||||
stencilBits = 8;
|
||||
|
||||
final PixelFormat format = new PixelFormat().withDepthBits(24).withStencilBits(stencilBits);
|
||||
try {
|
||||
SDLGPUGate.createSDLGPUDisplay(format, null, AngelicaMod.lwjglDebug);
|
||||
if (SDLGPUGate.isFatalInit()) {
|
||||
FMLCommonHandler.instance().exitJava(1, false);
|
||||
}
|
||||
|
||||
if (SDLGPUGate.createSDLGPUDisplay(format, null)) {
|
||||
LOGGER.info("Created SDL GPU window");
|
||||
ci.cancel();
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("FATAL: Failed to create SDL GPU window: {}", e.getMessage());
|
||||
throw new LWJGLException("Failed to create SDL GPU window", e);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.warn("SDL GPU could not take the window; falling back to OpenGL");
|
||||
SDLGPUGate.fallBackToGL();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public abstract class MixinMinecraft_SDLGPUIcons {
|
||||
|
||||
@ModifyArg(method = "startGame", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/Display;setIcon([Ljava/nio/ByteBuffer;)I", remap = false), index = 0)
|
||||
private ByteBuffer[] angelicaSdl$rememberIcons(ByteBuffer[] icons) {
|
||||
SDLGPUGate.rememberIcons(icons);
|
||||
return icons;
|
||||
}
|
||||
}
|
||||
@@ -84,11 +84,11 @@ class Lwjgl3AwareBoundaryTest {
|
||||
for (JavaMethod method : renderBackend.getMethods()) {
|
||||
final List<JavaClass> types = new ArrayList<>(method.getRawParameterTypes());
|
||||
types.add(method.getRawReturnType());
|
||||
if (types.stream().anyMatch(Lwjgl3AwareBoundaryTest::isLwjgl3Type)) {
|
||||
if (types.stream().anyMatch(Lwjgl3AwareBoundaryTest::isRemapUnsafeType)) {
|
||||
violations.add(method.getFullName());
|
||||
}
|
||||
}
|
||||
assertTrue(violations.isEmpty(), "RenderBackend methods with an org.lwjgl type in their signature:\n" + String.join("\n", violations));
|
||||
assertTrue(violations.isEmpty(), "RenderBackend methods with an org.lwjgl/org.lwjglx/lwjgl3ify type in their signature:\n" + String.join("\n", violations));
|
||||
}
|
||||
|
||||
private static boolean isAware(JavaClass c) {
|
||||
@@ -103,6 +103,11 @@ class Lwjgl3AwareBoundaryTest {
|
||||
return type.getName().startsWith("org.lwjgl.");
|
||||
}
|
||||
|
||||
private static boolean isRemapUnsafeType(JavaClass type) {
|
||||
final String name = type.getName();
|
||||
return isLwjgl3Type(type) || name.startsWith("org.lwjglx.") || name.startsWith("me.eigenraven.lwjgl3ify.");
|
||||
}
|
||||
|
||||
private static boolean mentionsLwjgl3Type(AccessTarget target) {
|
||||
return descriptorTypes(target).stream().anyMatch(Lwjgl3AwareBoundaryTest::isLwjgl3Type);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.arch;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
import com.tngtech.archunit.core.domain.JavaAccess;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.domain.ReferencedClassObject;
|
||||
import com.tngtech.archunit.core.domain.JavaStaticInitializer;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.core.importer.ImportOption;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Enforces the :sdl-gpu module boundary
|
||||
@@ -19,35 +25,73 @@ class SDLGPUEncapsulationTest {
|
||||
|
||||
private static final String SDLGPU_ROOT = "com.gtnewhorizons.angelica.sdlgpu";
|
||||
|
||||
private static final Set<String> PUBLIC_ENTRIES = Set.of(
|
||||
SDLGPU_ROOT + ".SDLGPURenderBackend", // ServiceLoader
|
||||
SDLGPU_ROOT + ".SDLGPULWJGLService", // ServiceLoader
|
||||
SDLGPU_ROOT + ".SDLGPUGate", // Mixins, MixinInitGLStateManager
|
||||
SDLGPU_ROOT + ".SDLGPUDisplayBridge", // MixinForgeHooksClient_SDLGPUDisplay
|
||||
SDLGPU_ROOT + ".device.SDLDrawable", // MixinForgeHooksClient_SDLGPUDisplay
|
||||
SDLGPU_ROOT + ".shader.ShaderManager" // Iris, IrisGLSMBridge
|
||||
);
|
||||
private static final String GATE = SDLGPU_ROOT + ".SDLGPUGate";
|
||||
|
||||
private static final DescribedPredicate<JavaClass> SDLGPU_INTERNAL =
|
||||
new DescribedPredicate<>("sdlgpu internals (anything under " + SDLGPU_ROOT + " not on the allow-list)") {
|
||||
private static final String LWJGL3_AWARE = "me.eigenraven.lwjgl3ify.api.Lwjgl3Aware";
|
||||
|
||||
private static final DescribedPredicate<JavaClass> SDLGPU_BEHIND_THE_GATE =
|
||||
new DescribedPredicate<>("anything under " + SDLGPU_ROOT + " other than SDLGPUGate") {
|
||||
@Override
|
||||
public boolean test(JavaClass c) {
|
||||
final String name = c.getName();
|
||||
return name.startsWith(SDLGPU_ROOT + ".") && !PUBLIC_ENTRIES.contains(name);
|
||||
return name.startsWith(SDLGPU_ROOT + ".") && !GATE.equals(name);
|
||||
}
|
||||
};
|
||||
|
||||
private static final DescribedPredicate<JavaClass> LWJGL3_ONLY_TYPE =
|
||||
new DescribedPredicate<>("types that only exist under lwjgl3ify/LWJGL3") {
|
||||
@Override
|
||||
public boolean test(JavaClass c) {
|
||||
final String name = c.getName();
|
||||
if (LWJGL3_AWARE.equals(name)) return false;
|
||||
return name.startsWith("org.lwjglx.") || name.startsWith("me.eigenraven.lwjgl3ify.") || name.startsWith("org.lwjgl.sdl.");
|
||||
}
|
||||
};
|
||||
|
||||
private static final JavaClasses CLASSES = new ClassFileImporter()
|
||||
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
|
||||
.importPaths("build/classes/java/main", "build/classes/java/mixin");
|
||||
.importPaths("build/classes/java/main", "build/classes/java/mixin", "glsm/build/classes/java/main");
|
||||
|
||||
private static final ArchRule NO_SDLGPU_INTERNALS_FROM_OUTSIDE = noClasses()
|
||||
private static final JavaClasses SDLGPU_CLASSES = new ClassFileImporter()
|
||||
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
|
||||
.importPaths("sdl-gpu/build/classes/java/main");
|
||||
|
||||
private static final ArchRule SDLGPU_REACHED_ONLY_THROUGH_THE_GATE = noClasses()
|
||||
.that().resideOutsideOfPackage(SDLGPU_ROOT + "..")
|
||||
.should().dependOnClassesThat(SDLGPU_INTERNAL)
|
||||
.because("only the allow-listed classes may be referenced by name from outside :sdl-gpu");
|
||||
.should().dependOnClassesThat(SDLGPU_BEHIND_THE_GATE)
|
||||
.because("SDLGPUGate is the only :sdl-gpu class safe to load without LWJGL3");
|
||||
|
||||
private static final ArchRule NO_LWJGL3_ONLY_TYPES = noClasses()
|
||||
.that().resideOutsideOfPackage(SDLGPU_ROOT + "..")
|
||||
.should().dependOnClassesThat(LWJGL3_ONLY_TYPE)
|
||||
.because("main, mixin and glsm all load on an LWJGL2 launch, where these types are absent");
|
||||
|
||||
@Test
|
||||
void noSdlGpuInternalsFromOutside() {
|
||||
NO_SDLGPU_INTERNALS_FROM_OUTSIDE.check(CLASSES);
|
||||
void sdlGpuIsReachedOnlyThroughTheGate() {
|
||||
SDLGPU_REACHED_ONLY_THROUGH_THE_GATE.check(CLASSES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noLwjgl3OnlyTypesOutsideSdlGpu() {
|
||||
NO_LWJGL3_ONLY_TYPES.check(CLASSES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gateStaticInitIsLwjgl3Free() {
|
||||
final Optional<JavaStaticInitializer> clinit = SDLGPU_CLASSES.get(GATE).getStaticInitializer();
|
||||
assertTrue(clinit.isPresent(), GATE + " has no static initializer to check; the importer path is likely wrong");
|
||||
|
||||
final List<String> violations = new ArrayList<>();
|
||||
for (JavaAccess<?> access : clinit.get().getAccessesFromSelf()) {
|
||||
if (isLwjgl3(access.getTargetOwner())) violations.add(" " + access.getTarget().getFullName());
|
||||
}
|
||||
for (ReferencedClassObject ref : clinit.get().getReferencedClassObjects()) {
|
||||
if (isLwjgl3(ref.getValue())) violations.add(" " + ref.getValue().getName() + ".class");
|
||||
}
|
||||
assertTrue(violations.isEmpty(), GATE + " static init resolves LWJGL3 types:\n" + String.join("\n", violations));
|
||||
}
|
||||
|
||||
private static boolean isLwjgl3(JavaClass c) {
|
||||
return LWJGL3_ONLY_TYPE.test(c) || c.getName().startsWith("org.lwjgl.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FontBatchMatrixTest {
|
||||
|
||||
private static Matrix4f overlayOrtho() {
|
||||
return new Matrix4f().ortho(0, 854, 480, 0, 1000, 3000).translate(0, 0, -2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnchangedMatrixDoesNotSegment() {
|
||||
final Matrix4f cached = overlayOrtho();
|
||||
assertTrue(cached.equals(new Matrix4f(cached), 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aScaleSegments() {
|
||||
final Matrix4f cached = overlayOrtho();
|
||||
assertFalse(cached.equals(new Matrix4f(cached).scale(0.5f), 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDepthOnlyTranslateSegments() {
|
||||
final Matrix4f cached = overlayOrtho();
|
||||
assertFalse(cached.equals(new Matrix4f(cached).translate(0, 0, 50), 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aStackIsComparedByValue() {
|
||||
final Matrix4f cached = overlayOrtho();
|
||||
final Matrix4fStack live = new Matrix4fStack(4);
|
||||
live.set(cached);
|
||||
live.pushMatrix();
|
||||
assertTrue(cached.equals(live, 0.0f));
|
||||
|
||||
live.scale(0.5f);
|
||||
assertFalse(cached.equals(live, 0.0f));
|
||||
|
||||
live.popMatrix();
|
||||
assertTrue(cached.equals(live, 0.0f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility.DENIED;
|
||||
import static com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility.SAFE;
|
||||
import static com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility.UNKNOWN;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class BatchEligibilityTest {
|
||||
|
||||
private long drawCalls;
|
||||
|
||||
@BeforeEach
|
||||
void reset() {
|
||||
drawCalls = 0L;
|
||||
}
|
||||
|
||||
private void draw(int count) {
|
||||
drawCalls += count;
|
||||
}
|
||||
|
||||
private void unbatchedPart(int draws) {
|
||||
final long before = drawCalls;
|
||||
draw(draws);
|
||||
BatchEligibility.onPartFallback(before, drawCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRendererDrawingOnlyModelPartsIsPromoted() {
|
||||
assertFalse(BatchEligibility.begin(UNKNOWN, drawCalls), "unproven renderers run unbatched");
|
||||
unbatchedPart(1);
|
||||
unbatchedPart(1);
|
||||
assertEquals(SAFE, BatchEligibility.end(UNKNOWN, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRendererMixingItsOwnDrawsIsDenied() {
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
unbatchedPart(1);
|
||||
draw(1);
|
||||
assertEquals(DENIED, BatchEligibility.end(UNKNOWN, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aPromotedRendererIsDemotedWhenItStartsMixing() {
|
||||
assertTrue(BatchEligibility.begin(SAFE, drawCalls), "proven renderers batch");
|
||||
BatchEligibility.onPartQueued();
|
||||
draw(1);
|
||||
assertEquals(DENIED, BatchEligibility.end(SAFE, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aBatchedPartFallingBackCountsAsMixing() {
|
||||
BatchEligibility.begin(SAFE, drawCalls);
|
||||
BatchEligibility.onPartQueued();
|
||||
unbatchedPart(1);
|
||||
assertEquals(DENIED, BatchEligibility.end(SAFE, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDeniedRendererIsNeverPromoted() {
|
||||
assertFalse(BatchEligibility.begin(DENIED, drawCalls));
|
||||
unbatchedPart(1);
|
||||
assertEquals(DENIED, BatchEligibility.end(DENIED, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDispatchWithoutPartsProvesNothing() {
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
draw(3);
|
||||
assertEquals(UNKNOWN, BatchEligibility.end(UNKNOWN, drawCalls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aNestedDispatchDoesNotObserveSeparately() {
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
unbatchedPart(1);
|
||||
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
draw(1);
|
||||
assertEquals(UNKNOWN, BatchEligibility.end(UNKNOWN, drawCalls), "the inner dispatch leaves the verdict to the outer one");
|
||||
|
||||
assertEquals(DENIED, BatchEligibility.end(UNKNOWN, drawCalls), "the nested draw still counts against the outer renderer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void statePersistsNothingBetweenDispatches() {
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
unbatchedPart(1);
|
||||
draw(1);
|
||||
assertEquals(DENIED, BatchEligibility.end(UNKNOWN, drawCalls));
|
||||
|
||||
BatchEligibility.begin(UNKNOWN, drawCalls);
|
||||
unbatchedPart(1);
|
||||
assertEquals(SAFE, BatchEligibility.end(UNKNOWN, drawCalls), "the previous dispatch's foreign draws must not leak");
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchingIsDisallowedOutsideADispatch() {
|
||||
BatchEligibility.begin(SAFE, drawCalls);
|
||||
BatchEligibility.end(SAFE, drawCalls);
|
||||
assertFalse(BatchEligibility.batchingAllowed());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user