mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Instance more things (#2104)
* Flyby: pin the start origin and fly just above the route's terrain * Instancing: particles, dropped items, entity cubes + shadow batching and some SDL-GPU fixes
This commit is contained in:
1 parent
94ee37bfed
commit
3876f7e693
158 files changed
+9956
-1404
No files matched your search
@@ -103,6 +103,11 @@ repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
val spirvTestRuntime by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
@@ -133,6 +138,7 @@ dependencies {
|
||||
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
|
||||
testRuntimeOnly(libs.lwjgl3)
|
||||
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
|
||||
spirvTestRuntime(libs.lwjgl3.opengl)
|
||||
// @Lwjgl3Aware annotation
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
@@ -309,7 +315,7 @@ val spirvTest by tasks.registering(Test::class) {
|
||||
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
}.plus(spirvTestRuntime)
|
||||
|
||||
filter {
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
|
||||
@@ -54,6 +54,7 @@ public final class SystemProperties {
|
||||
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
|
||||
// Debug
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
@@ -427,11 +428,7 @@ public class GLStateManager {
|
||||
public static boolean wideLineEmulationActive = false;
|
||||
public static boolean lineStippleActive = false;
|
||||
|
||||
public static boolean instancedFfpDrawActive = false;
|
||||
|
||||
|
||||
|
||||
|
||||
public static Instancing ffpInstancing = Instancing.NONE;
|
||||
|
||||
private static final MethodHandle MAT4_STACK_CURR_DEPTH;
|
||||
|
||||
@@ -1429,8 +1426,11 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void endForeignDraw() {
|
||||
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
|
||||
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
|
||||
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0) {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
if (GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
|
||||
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4062,6 +4062,14 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTextureMatrix(int unit, Matrix4fc m) {
|
||||
if (isCachingEnabled()) {
|
||||
final GLContextState glCtx = ctx();
|
||||
glCtx.textures.getTextureUnitMatrix(unit).set(m);
|
||||
glCtx.texMatrixGeneration++;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setProjectionMatrix(Matrix4fc m) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if (isCachingEnabled()) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class CubeInstancedAttribs {
|
||||
|
||||
public static final int LOC_LIGHTMAP_SCALE = InstancedAttribs.LOC_LIGHTMAP;
|
||||
public static final int LOC_CUBE_TEX = 15;
|
||||
|
||||
public static final int OFFSET_LIGHTMAP_SCALE = InstancedAttribs.HEAD_SIZE;
|
||||
public static final int OFFSET_CUBE_TEX = OFFSET_LIGHTMAP_SCALE + 16;
|
||||
public static final int STRIDE = OFFSET_CUBE_TEX + 16;
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
InstancedAttribs.pointHead(base, STRIDE);
|
||||
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP_SCALE, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP_SCALE);
|
||||
GLStateManager.glVertexAttribPointer(LOC_CUBE_TEX, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CUBE_TEX);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableHeadArrays();
|
||||
InstancedAttribs.enableInstanced(LOC_CUBE_TEX);
|
||||
}
|
||||
|
||||
private CubeInstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
|
||||
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
|
||||
|
||||
private static final float MIN_SPAN = 1.0e-4f;
|
||||
|
||||
public CubeParams {
|
||||
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
|
||||
}
|
||||
|
||||
public static CubeParams of(float texWidth, float texHeight, boolean mirror, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate) {
|
||||
if (texWidth <= 0.0f || texHeight <= 0.0f) return null;
|
||||
if (sizeX < 0 || sizeY < 0 || sizeZ < 0) return null;
|
||||
if (mirror && sizeX == 0) return null;
|
||||
final float minX = x - inflate;
|
||||
final float minY = y - inflate;
|
||||
final float minZ = z - inflate;
|
||||
final float spanX = ((x + sizeX) + inflate) - minX;
|
||||
final float spanY = ((y + sizeY) + inflate) - minY;
|
||||
final float spanZ = ((z + sizeZ) + inflate) - minZ;
|
||||
if (spanX <= MIN_SPAN || spanY <= MIN_SPAN || spanZ <= MIN_SPAN) return null;
|
||||
return new CubeParams(minX, minY, minZ, spanX, spanY, spanZ, texU / texWidth, texV / texHeight, (mirror ? -sizeX : sizeX) / texWidth, sizeY / texHeight, sizeZ / texWidth, sizeZ / texHeight);
|
||||
}
|
||||
|
||||
public void writeRows(long ptr, float[] mv, int off, float scale) {
|
||||
final float tx = minX * scale;
|
||||
final float ty = minY * scale;
|
||||
final float tz = minZ * scale;
|
||||
final float sx = spanX * scale;
|
||||
final float sy = spanY * scale;
|
||||
final float sz = spanZ * scale;
|
||||
for (int r = 0; r < 3; r++) {
|
||||
final float c0 = mv[off + r];
|
||||
final float c1 = mv[off + 4 + r];
|
||||
final float c2 = mv[off + 8 + r];
|
||||
final long row = ptr + InstancedAttribs.OFFSET_ROWS + r * 16L;
|
||||
memPutFloat(row, c0 * sx);
|
||||
memPutFloat(row + 4, c1 * sy);
|
||||
memPutFloat(row + 8, c2 * sz);
|
||||
memPutFloat(row + 12, mv[off + 12 + r] + c0 * tx + c1 * ty + c2 * tz);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTexture(long ptr) {
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
|
||||
* Packed-long fragment shader permutation key for FFP emulation.
|
||||
*
|
||||
* Layout:
|
||||
* long[0]: global bits (13) + unit 0 (47 bits at offset 13)
|
||||
* long[0]: global bits (14) + unit 0 (47 bits at offset 14)
|
||||
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
|
||||
* Only max(1, nrEnabledUnits) longs are significant.
|
||||
*
|
||||
@@ -63,7 +63,7 @@ public final class FragmentKey {
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 13;
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
@@ -72,6 +72,7 @@ public final class FragmentKey {
|
||||
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
|
||||
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
|
||||
private static final int BIT_COLOR_SUM = 12; // 1 bit
|
||||
private static final int BIT_OVERLAY_INSTANCED = 13;
|
||||
|
||||
private static final int U_ENABLED = 0;
|
||||
private static final int U_MODE = 1; // 3 bits
|
||||
@@ -123,7 +124,9 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
// Damage overlay
|
||||
if (GLStateManager.getOverlayA() != 0.0f) {
|
||||
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
|
||||
global |= (1L << BIT_OVERLAY_INSTANCED);
|
||||
} else if (GLStateManager.getOverlayA() != 0.0f) {
|
||||
global |= (1L << BIT_OVERLAY_ENABLED);
|
||||
}
|
||||
|
||||
@@ -245,6 +248,7 @@ public final class FragmentKey {
|
||||
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
|
||||
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
|
||||
|
||||
private long unitBits(int i) {
|
||||
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
|
||||
@@ -378,10 +382,7 @@ public final class FragmentKey {
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d",
|
||||
fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), colorSum(), overlayEnabled(), nrEnabledUnits()));
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
|
||||
@@ -35,6 +35,9 @@ public final class FragmentShaderGenerator {
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.overlayInstanced()) {
|
||||
sb.append("in vec4 v_Overlay;\n");
|
||||
}
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor;\n");
|
||||
}
|
||||
@@ -116,8 +119,11 @@ public final class FragmentShaderGenerator {
|
||||
* Emit the modern-style damage overlay mix.
|
||||
*/
|
||||
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.overlayEnabled()) return;
|
||||
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
|
||||
if (key.overlayInstanced()) {
|
||||
sb.append(" color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);\n");
|
||||
} else if (key.overlayEnabled()) {
|
||||
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
|
||||
@@ -1,18 +1,84 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
|
||||
|
||||
public final class InstancedAttribs {
|
||||
|
||||
public static final int LOC_MATRIX_COL0 = 5;
|
||||
public static final int LOC_MATRIX_COL1 = 6;
|
||||
public static final int LOC_MATRIX_COL2 = 7;
|
||||
public static final int LOC_MATRIX_COL3 = 8;
|
||||
public static final int LOC_COLOR = 9;
|
||||
public static final int LOC_ROW0 = 5;
|
||||
public static final int LOC_ROW1 = 6;
|
||||
public static final int LOC_ROW2 = 7;
|
||||
public static final int LOC_COLOR = 8;
|
||||
public static final int LOC_OVERLAY = 9;
|
||||
public static final int LOC_LIGHTMAP = 10;
|
||||
public static final int LOC_ENTITY = 11;
|
||||
|
||||
public static final int OFFSET_MATRIX = 0;
|
||||
public static final int OFFSET_COLOR = 64;
|
||||
public static final int OFFSET_LIGHTMAP = 68;
|
||||
public static final int STRIDE = 80;
|
||||
public static final int OFFSET_ROWS = 0;
|
||||
public static final int OFFSET_COLOR = 48;
|
||||
public static final int OFFSET_OVERLAY = 52;
|
||||
public static final int OFFSET_ENTITY = 56;
|
||||
public static final int HEAD_SIZE = 64;
|
||||
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
|
||||
public static final int STRIDE = 72;
|
||||
|
||||
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
|
||||
writeRows(ptr, colMajor, off);
|
||||
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
|
||||
}
|
||||
|
||||
private static void writeRows(long ptr, float[] colMajor, int off) {
|
||||
for (int r = 0; r < 3; r++) {
|
||||
final long row = ptr + OFFSET_ROWS + r * 16L;
|
||||
memPutFloat(row, colMajor[off + r]);
|
||||
memPutFloat(row + 4, colMajor[off + 4 + r]);
|
||||
memPutFloat(row + 8, colMajor[off + 8 + r]);
|
||||
memPutFloat(row + 12, colMajor[off + 12 + r]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeTail(long ptr, int colorABGR, int overlayABGR, long entityInfo) {
|
||||
memPutInt(ptr + OFFSET_COLOR, colorABGR);
|
||||
memPutInt(ptr + OFFSET_OVERLAY, overlayABGR);
|
||||
memPutLong(ptr + OFFSET_ENTITY, entityInfo);
|
||||
}
|
||||
|
||||
public static void writeLightmap(long ptr, int packedLight) {
|
||||
memPutFloat(ptr, packedLight & 0xFFFF);
|
||||
memPutFloat(ptr + 4, (packedLight >>> 16) & 0xFFFF);
|
||||
}
|
||||
|
||||
public static long packEntityInfo(int entity, int blockEntity, int item) {
|
||||
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
|
||||
}
|
||||
|
||||
public static void pointHead(long base, int stride) {
|
||||
for (int r = 0; r < 3; r++) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
|
||||
}
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
|
||||
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
|
||||
}
|
||||
|
||||
public static void pointTemplate(long base) {
|
||||
pointHead(base, STRIDE);
|
||||
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
|
||||
}
|
||||
|
||||
public static void enableHeadArrays() {
|
||||
for (int loc = LOC_ROW0; loc <= LOC_ENTITY; loc++) {
|
||||
enableInstanced(loc);
|
||||
}
|
||||
}
|
||||
|
||||
static void enableInstanced(int loc) {
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribDivisor(loc, 1);
|
||||
}
|
||||
|
||||
private InstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class InstancedGlslHelpers {
|
||||
|
||||
private InstancedGlslHelpers() {}
|
||||
|
||||
public static String mat4FromRows(String row0, String row1, String row2) {
|
||||
return "mat4("
|
||||
+ "vec4(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x, 0.0), "
|
||||
+ "vec4(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y, 0.0), "
|
||||
+ "vec4(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z, 0.0), "
|
||||
+ "vec4(" + row0 + ".w, " + row1 + ".w, " + row2 + ".w, 1.0))";
|
||||
}
|
||||
|
||||
public static String mat3FromRows(String row0, String row1, String row2) {
|
||||
return "mat3("
|
||||
+ "normalize(vec3(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x)), "
|
||||
+ "normalize(vec3(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y)), "
|
||||
+ "normalize(vec3(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z)))";
|
||||
}
|
||||
|
||||
public static String particlePos(String centerHalf, String offset) {
|
||||
return "vec4(" + centerHalf + ".xyz + " + offset + " * " + centerHalf + ".w, 1.0)";
|
||||
}
|
||||
|
||||
public static String particleUv(String uv, String corner) {
|
||||
return "vec4("
|
||||
+ "mix(" + uv + ".x, " + uv + ".z, " + corner + ".x), "
|
||||
+ "mix(" + uv + ".y, " + uv + ".w, " + corner + ".y), "
|
||||
+ "0.0, 1.0)";
|
||||
}
|
||||
|
||||
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
|
||||
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
|
||||
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
|
||||
+ "vec2 " + prefix + "cubeU = " + prefix + "cubeMidU + (1.0 - 2.0 * " + prefix + "cubeMirror) * " + delta + ".xy;" + sep
|
||||
+ "vec2 " + prefix + "cubeV = " + mid + ".zw + " + delta + ".zw;";
|
||||
}
|
||||
|
||||
public static String cubeUv(String tex, String lightScale, String uA, String uZ, String vA, String vZ) {
|
||||
return "vec4("
|
||||
+ tex + ".x + " + uA + " * abs(" + tex + ".z) + " + uZ + " * " + lightScale + ".z, "
|
||||
+ tex + ".y + " + vA + " * " + tex + ".w + " + vZ + " * " + lightScale + ".w, "
|
||||
+ "0.0, 1.0)";
|
||||
}
|
||||
|
||||
public static String[] attributeDecls(String prefix, Instancing kind) {
|
||||
final ArrayList<String> decls = new ArrayList<>(9);
|
||||
switch (kind) {
|
||||
case NONE -> {}
|
||||
case TEMPLATE -> addHead(decls, prefix, "vec2", "InstLightmap");
|
||||
case CUBE -> {
|
||||
decls.add(decl(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), "vec4", prefix + "CubeMid"));
|
||||
decls.add(decl(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), "vec4", prefix + "CubeDelta"));
|
||||
addHead(decls, prefix, "vec4", "InstLightmapScale");
|
||||
decls.add(decl(CubeInstancedAttribs.LOC_CUBE_TEX, "vec4", prefix + "CubeTex"));
|
||||
}
|
||||
case PARTICLE -> {
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_CENTER_HALF, "vec4", prefix + "InstCenterHalf"));
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_UV, "vec4", prefix + "InstUv"));
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
|
||||
}
|
||||
}
|
||||
return decls.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private static void addHead(ArrayList<String> decls, String prefix, String lightmapType, String lightmapName) {
|
||||
decls.add(decl(InstancedAttribs.LOC_ROW0, "vec4", prefix + "InstRow0"));
|
||||
decls.add(decl(InstancedAttribs.LOC_ROW1, "vec4", prefix + "InstRow1"));
|
||||
decls.add(decl(InstancedAttribs.LOC_ROW2, "vec4", prefix + "InstRow2"));
|
||||
decls.add(decl(InstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
|
||||
decls.add(decl(InstancedAttribs.LOC_OVERLAY, "vec4", prefix + "InstOverlay"));
|
||||
decls.add(decl(InstancedAttribs.LOC_LIGHTMAP, lightmapType, prefix + lightmapName));
|
||||
}
|
||||
|
||||
private static String decl(int loc, String type, String name) {
|
||||
return "layout(location = " + loc + ") in " + type + " " + name + ";";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
public enum Instancing {
|
||||
NONE(""),
|
||||
TEMPLATE("_instanced"),
|
||||
CUBE("_cubeinstanced"),
|
||||
PARTICLE("_particleinstanced");
|
||||
|
||||
public static final Instancing[] VALUES = values();
|
||||
|
||||
public final String variantSuffix;
|
||||
|
||||
Instancing(String variantSuffix) {
|
||||
this.variantSuffix = variantSuffix;
|
||||
}
|
||||
|
||||
public boolean hasInstanceHead() {
|
||||
return this == TEMPLATE || this == CUBE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
public final class ParticleInstancedAttribs {
|
||||
|
||||
public static final int LOC_CENTER_HALF = 5;
|
||||
public static final int LOC_UV = 6;
|
||||
public static final int LOC_COLOR = InstancedAttribs.LOC_COLOR;
|
||||
public static final int LOC_LIGHTMAP = InstancedAttribs.LOC_LIGHTMAP;
|
||||
|
||||
public static final int OFFSET_CENTER_HALF = 0;
|
||||
public static final int OFFSET_UV = 16;
|
||||
public static final int OFFSET_COLOR = 32;
|
||||
public static final int OFFSET_LIGHTMAP = 36;
|
||||
public static final int STRIDE = 48;
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_CENTER_HALF, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CENTER_HALF);
|
||||
GLStateManager.glVertexAttribPointer(LOC_UV, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_UV);
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, base + OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableInstanced(LOC_CENTER_HALF);
|
||||
InstancedAttribs.enableInstanced(LOC_UV);
|
||||
InstancedAttribs.enableInstanced(LOC_COLOR);
|
||||
InstancedAttribs.enableInstanced(LOC_LIGHTMAP);
|
||||
}
|
||||
|
||||
public static void writeInstance(long ptr, float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR, int packedLight) {
|
||||
memPutFloat(ptr + OFFSET_CENTER_HALF, cx);
|
||||
memPutFloat(ptr + OFFSET_CENTER_HALF + 4, cy);
|
||||
memPutFloat(ptr + OFFSET_CENTER_HALF + 8, cz);
|
||||
memPutFloat(ptr + OFFSET_CENTER_HALF + 12, half);
|
||||
memPutFloat(ptr + OFFSET_UV, u0);
|
||||
memPutFloat(ptr + OFFSET_UV + 4, v0);
|
||||
memPutFloat(ptr + OFFSET_UV + 8, u1);
|
||||
memPutFloat(ptr + OFFSET_UV + 12, v1);
|
||||
memPutInt(ptr + OFFSET_COLOR, colorABGR);
|
||||
InstancedAttribs.writeLightmap(ptr + OFFSET_LIGHTMAP, packedLight);
|
||||
}
|
||||
|
||||
private ParticleInstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
public final class ParticleQuadMesh {
|
||||
|
||||
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
|
||||
|
||||
public static final int VERTEX_COUNT = 4;
|
||||
private static final int VERTEX_STRIDE = 24;
|
||||
|
||||
private static final int OFFSET_OFFSET = 0;
|
||||
private static final int OFFSET_CORNER = 12;
|
||||
private static final int OFFSET_TANGENT = 20;
|
||||
|
||||
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
|
||||
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
|
||||
|
||||
private static int vao;
|
||||
private static int vbo;
|
||||
private static ByteBuffer staging;
|
||||
private static boolean written;
|
||||
private static float lastRotX, lastRotXZ, lastRotZ, lastRotYZ, lastRotXY;
|
||||
|
||||
private ParticleQuadMesh() {}
|
||||
|
||||
public static float cornerA(int corner) {
|
||||
return CORNER_A[corner];
|
||||
}
|
||||
|
||||
public static float cornerB(int corner) {
|
||||
return CORNER_B[corner];
|
||||
}
|
||||
|
||||
public static float offsetX(float a, float b, float rotX, float rotYZ) {
|
||||
return a * rotX + b * rotYZ;
|
||||
}
|
||||
|
||||
public static float offsetY(float b, float rotXZ) {
|
||||
return b * rotXZ;
|
||||
}
|
||||
|
||||
public static float offsetZ(float a, float b, float rotZ, float rotXY) {
|
||||
return a * rotZ + b * rotXY;
|
||||
}
|
||||
|
||||
public static int vao() {
|
||||
if (vao == 0) build();
|
||||
return vao;
|
||||
}
|
||||
|
||||
public static void update(float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
if (vao == 0) build();
|
||||
if (written && rotX == lastRotX && rotXZ == lastRotXZ && rotZ == lastRotZ && rotYZ == lastRotYZ && rotXY == lastRotXY) {
|
||||
return;
|
||||
}
|
||||
lastRotX = rotX;
|
||||
lastRotXZ = rotXZ;
|
||||
lastRotZ = rotZ;
|
||||
lastRotYZ = rotYZ;
|
||||
lastRotXY = rotXY;
|
||||
written = true;
|
||||
|
||||
write(memAddress0(staging), rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
staging.position(0).limit(VERTEX_COUNT * VERTEX_STRIDE);
|
||||
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, staging);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
}
|
||||
|
||||
private static void write(long dst, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
final int tangent = packTangent(rotX, 0.0f, rotZ);
|
||||
long ptr = dst;
|
||||
for (int i = 0; i < VERTEX_COUNT; i++) {
|
||||
final float a = cornerA(i);
|
||||
final float b = cornerB(i);
|
||||
memPutFloat(ptr + OFFSET_OFFSET, offsetX(a, b, rotX, rotYZ));
|
||||
memPutFloat(ptr + OFFSET_OFFSET + 4, offsetY(b, rotXZ));
|
||||
memPutFloat(ptr + OFFSET_OFFSET + 8, offsetZ(a, b, rotZ, rotXY));
|
||||
memPutFloat(ptr + OFFSET_CORNER, (a + 1.0f) * 0.5f);
|
||||
memPutFloat(ptr + OFFSET_CORNER + 4, (b + 1.0f) * 0.5f);
|
||||
memPutInt(ptr + OFFSET_TANGENT, tangent);
|
||||
ptr += VERTEX_STRIDE;
|
||||
}
|
||||
}
|
||||
|
||||
private static int packTangent(float x, float y, float z) {
|
||||
final float lenSq = x * x + y * y + z * z;
|
||||
final float nx, ny, nz;
|
||||
if (lenSq < 1.0e-12f) {
|
||||
nx = 1.0f;
|
||||
ny = 0.0f;
|
||||
nz = 0.0f;
|
||||
} else {
|
||||
final float inv = (float) (1.0 / Math.sqrt(lenSq));
|
||||
nx = x * inv;
|
||||
ny = y * inv;
|
||||
nz = z * inv;
|
||||
}
|
||||
return NormI8.pack(nx, ny, nz) | (127 << 24);
|
||||
}
|
||||
|
||||
private static void build() {
|
||||
final int savedVAO = GLStateManager.getBoundVAO();
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) VERTEX_COUNT * VERTEX_STRIDE, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_OFFSET);
|
||||
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 2, GL11.GL_FLOAT, false, OFFSET_CORNER);
|
||||
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
|
||||
|
||||
ParticleInstancedAttribs.enableInstanceArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(savedVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
|
||||
if (staging == null) staging = memAlloc(VERTEX_COUNT * VERTEX_STRIDE);
|
||||
written = false;
|
||||
}
|
||||
|
||||
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (vao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
vao = 0;
|
||||
}
|
||||
if (staging != null) {
|
||||
memFree(staging);
|
||||
staging = null;
|
||||
}
|
||||
written = false;
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,8 @@ public final class ShaderManager {
|
||||
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
VertexShaderGenerator.generate(vk);
|
||||
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
|
||||
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
|
||||
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
|
||||
GeometryShaderGenerator.generate(vk);
|
||||
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
|
||||
@@ -127,6 +129,7 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
public void preDraw() {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null) bh.flushDeferredBlend();
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
public final class UnitCubeMesh {
|
||||
|
||||
public static final int VERTEX_FLAGS = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
public static final int FACE_COUNT = 6;
|
||||
public static final int VERTEX_COUNT = 24;
|
||||
public static final int VERTEX_STRIDE = 64;
|
||||
|
||||
public static final int OFFSET_POSITION = 0;
|
||||
private static final int OFFSET_COLOR = 12;
|
||||
public static final int OFFSET_MID = 16;
|
||||
public static final int OFFSET_DELTA = 32;
|
||||
public static final int OFFSET_NORMAL = 48;
|
||||
public static final int OFFSET_TANGENT = 60;
|
||||
|
||||
public static final float[][] CORNERS = {
|
||||
{ 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 },
|
||||
{ 0, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 },
|
||||
};
|
||||
|
||||
public record Span(int a, int z) {
|
||||
public int texels(int sizeA, int sizeZ) {
|
||||
return a * sizeA + z * sizeZ;
|
||||
}
|
||||
}
|
||||
|
||||
public record Net(Span uLow, Span uHigh, Span vLow, Span vHigh) {
|
||||
public Span u(int vertex) {
|
||||
return vertex == 0 || vertex == 3 ? uHigh : uLow;
|
||||
}
|
||||
|
||||
public Span v(int vertex) {
|
||||
return vertex >= 2 ? vHigh : vLow;
|
||||
}
|
||||
}
|
||||
|
||||
public record Face(int[] corners, float[] normal, float[] tangent, Net net) {}
|
||||
|
||||
public static final Face[] FACES = {
|
||||
new Face(new int[] { 5, 1, 2, 6 }, new float[] { 1, 0, 0 }, new float[] { 0, 0, 1, 1 }, new Net(new Span(1, 1), new Span(1, 2), new Span(0, 1), new Span(1, 1))),
|
||||
new Face(new int[] { 0, 4, 7, 3 }, new float[] { -1, 0, 0 }, new float[] { 0, 0, -1, 1 }, new Net(new Span(0, 0), new Span(0, 1), new Span(0, 1), new Span(1, 1))),
|
||||
new Face(new int[] { 5, 4, 0, 1 }, new float[] { 0, -1, 0 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 0), new Span(0, 1))),
|
||||
new Face(new int[] { 2, 3, 7, 6 }, new float[] { 0, 1, 0 }, new float[] { 1, 0, 0, -1 }, new Net(new Span(1, 1), new Span(2, 1), new Span(0, 1), new Span(0, 0))),
|
||||
new Face(new int[] { 1, 0, 3, 2 }, new float[] { 0, 0, -1 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 1), new Span(1, 1))),
|
||||
new Face(new int[] { 4, 5, 6, 7 }, new float[] { 0, 0, 1 }, new float[] { -1, 0, 0, 1 }, new Net(new Span(1, 2), new Span(2, 2), new Span(0, 1), new Span(1, 1))),
|
||||
};
|
||||
|
||||
private static int vao;
|
||||
private static int vbo;
|
||||
|
||||
private UnitCubeMesh() {}
|
||||
|
||||
public static void write(long dst) {
|
||||
long ptr = dst;
|
||||
for (int f = 0; f < FACE_COUNT; f++) {
|
||||
final Face face = FACES[f];
|
||||
final Net net = face.net();
|
||||
final float midUA = (net.uLow().a() + net.uHigh().a()) * 0.5f;
|
||||
final float midUZ = (net.uLow().z() + net.uHigh().z()) * 0.5f;
|
||||
final float midVA = (net.vLow().a() + net.vHigh().a()) * 0.5f;
|
||||
final float midVZ = (net.vLow().z() + net.vHigh().z()) * 0.5f;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
final float[] corner = CORNERS[face.corners()[c]];
|
||||
final Span u = net.u(c);
|
||||
final Span v = net.v(c);
|
||||
memPutFloat(ptr + OFFSET_POSITION, corner[0]);
|
||||
memPutFloat(ptr + OFFSET_POSITION + 4, corner[1]);
|
||||
memPutFloat(ptr + OFFSET_POSITION + 8, corner[2]);
|
||||
memPutInt(ptr + OFFSET_COLOR, 0xFFFFFFFF);
|
||||
memPutFloat(ptr + OFFSET_MID, midUA);
|
||||
memPutFloat(ptr + OFFSET_MID + 4, midUZ);
|
||||
memPutFloat(ptr + OFFSET_MID + 8, midVA);
|
||||
memPutFloat(ptr + OFFSET_MID + 12, midVZ);
|
||||
memPutFloat(ptr + OFFSET_DELTA, u.a() - midUA);
|
||||
memPutFloat(ptr + OFFSET_DELTA + 4, u.z() - midUZ);
|
||||
memPutFloat(ptr + OFFSET_DELTA + 8, v.a() - midVA);
|
||||
memPutFloat(ptr + OFFSET_DELTA + 12, v.z() - midVZ);
|
||||
memPutFloat(ptr + OFFSET_NORMAL, face.normal()[0]);
|
||||
memPutFloat(ptr + OFFSET_NORMAL + 4, face.normal()[1]);
|
||||
memPutFloat(ptr + OFFSET_NORMAL + 8, face.normal()[2]);
|
||||
final float[] tangent = face.tangent();
|
||||
memPutInt(ptr + OFFSET_TANGENT, packTangent(tangent[0], tangent[1], tangent[2], tangent[3]));
|
||||
ptr += VERTEX_STRIDE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int packTangent(float x, float y, float z, float w) {
|
||||
return NormI8.pack(x, y, z) | (((int) (w * 127.0f) & 0xFF) << 24);
|
||||
}
|
||||
|
||||
public static int vao() {
|
||||
if (vao == 0) build();
|
||||
return vao;
|
||||
}
|
||||
|
||||
private static void build() {
|
||||
final int savedVAO = GLStateManager.getBoundVAO();
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
final int bytes = VERTEX_COUNT * VERTEX_STRIDE;
|
||||
final ByteBuffer data = memAlloc(bytes);
|
||||
try {
|
||||
write(memAddress0(data));
|
||||
data.position(0).limit(bytes);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
} finally {
|
||||
memFree(data);
|
||||
}
|
||||
|
||||
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_POSITION);
|
||||
attrib(VertexFormatElement.Usage.COLOR.getAttributeLocation(), 4, GL11.GL_UNSIGNED_BYTE, true, OFFSET_COLOR);
|
||||
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_MID);
|
||||
attrib(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_DELTA);
|
||||
attrib(VertexFormatElement.Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_NORMAL);
|
||||
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
|
||||
|
||||
CubeInstancedAttribs.enableInstanceArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(savedVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
}
|
||||
|
||||
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (vao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
vao = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public final class VertexKey {
|
||||
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCED = BIT_LINE_STIPPLE + 1;
|
||||
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
@@ -97,7 +97,7 @@ public final class VertexKey {
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
|
||||
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
|
||||
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
@@ -226,13 +226,15 @@ public final class VertexKey {
|
||||
bits |= (1L << BIT_LINE_STIPPLE);
|
||||
}
|
||||
|
||||
if (GLStateManager.instancedFfpDrawActive) {
|
||||
bits |= (1L << BIT_INSTANCED);
|
||||
}
|
||||
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
static long withInstancing(long packed, Instancing kind) {
|
||||
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
|
||||
}
|
||||
@@ -255,12 +257,6 @@ public final class VertexKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
|
||||
packed, lightingEnabled(), lightEnabled(0), lightEnabled(1),
|
||||
colorMaterialEnabled(), fogEnabled(),
|
||||
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
|
||||
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
|
||||
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
|
||||
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
|
||||
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
|
||||
}
|
||||
}
|
||||
@@ -55,31 +55,43 @@ public final class VertexShaderGenerator {
|
||||
}
|
||||
|
||||
private static void emitAttributes(StringBuilder sb, VertexKey key) {
|
||||
final Instancing kind = key.instancing();
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
|
||||
}
|
||||
if (key.hasVertexTexCoord()) {
|
||||
if (kind == Instancing.PARTICLE) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
} else {
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
|
||||
}
|
||||
if (kind != Instancing.CUBE) {
|
||||
if (key.hasVertexTexCoord()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
}
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
|
||||
}
|
||||
}
|
||||
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
|
||||
}
|
||||
}
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
|
||||
}
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
|
||||
}
|
||||
if (key.instancedDraw()) {
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
|
||||
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
|
||||
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
|
||||
sb.append(d).append('\n');
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static final String PARTICLE_UV = InstancedGlslHelpers.particleUv("a_InstUv", "a_TexCoord0");
|
||||
|
||||
private static String unit0Source(VertexKey key) {
|
||||
return switch (key.instancing()) {
|
||||
case CUBE -> "cubeUv";
|
||||
case PARTICLE -> PARTICLE_UV;
|
||||
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
|
||||
};
|
||||
}
|
||||
|
||||
private static void emitLineStart(StringBuilder sb) {
|
||||
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
|
||||
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
|
||||
@@ -93,6 +105,10 @@ public final class VertexShaderGenerator {
|
||||
private static void emitOutputs(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Outputs\n");
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.instancing().hasInstanceHead()) {
|
||||
sb.append("out vec4 v_Overlay;\n");
|
||||
sb.append("invariant gl_Position;\n");
|
||||
}
|
||||
if (key.lineStipple()) {
|
||||
sb.append("flat out vec2 v_LineStart;\n");
|
||||
}
|
||||
@@ -116,11 +132,16 @@ public final class VertexShaderGenerator {
|
||||
|
||||
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Position transform\n");
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
if (key.instancedDraw()) {
|
||||
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\n");
|
||||
if (key.instancing() == Instancing.PARTICLE) {
|
||||
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
|
||||
} else {
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
}
|
||||
if (key.instancing().hasInstanceHead()) {
|
||||
sb.append(" mat4 instMV = ").append(InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2")).append(";\n");
|
||||
sb.append(" vec4 eyePos = instMV * pos4;\n");
|
||||
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
|
||||
sb.append(" v_Overlay = a_InstOverlay;\n");
|
||||
} else {
|
||||
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
|
||||
|
||||
@@ -134,8 +155,8 @@ public final class VertexShaderGenerator {
|
||||
|
||||
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Normal transform\n");
|
||||
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
|
||||
if (key.instancedDraw()) {
|
||||
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
|
||||
if (key.instancing().hasInstanceHead()) {
|
||||
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
|
||||
sb.append('\n');
|
||||
return;
|
||||
@@ -156,9 +177,13 @@ public final class VertexShaderGenerator {
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
|
||||
sb.append(" vec4 matColor = ");
|
||||
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
|
||||
if (key.instancedDraw()) {
|
||||
sb.append(" * a_InstColor");
|
||||
if (key.instancing() == Instancing.PARTICLE) {
|
||||
sb.append("a_InstColor");
|
||||
} else {
|
||||
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
|
||||
if (key.instancing().hasInstanceHead()) {
|
||||
sb.append(" * a_InstColor");
|
||||
}
|
||||
}
|
||||
sb.append(";\n");
|
||||
|
||||
@@ -255,22 +280,27 @@ public final class VertexShaderGenerator {
|
||||
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Color passthrough (no lighting)\n");
|
||||
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
|
||||
if (key.instancedDraw()) {
|
||||
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
|
||||
} else {
|
||||
sb.append(" v_Color = ").append(src).append(";\n");
|
||||
switch (key.instancing()) {
|
||||
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
|
||||
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
|
||||
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
|
||||
final boolean cube = key.instancing() == Instancing.CUBE;
|
||||
final boolean unit0FromCube = cube && key.unitTexCoordEnabled(0) && !key.texGenEnabled();
|
||||
final boolean unit23FromCube = cube && key.unit23UvFromUnit0() && (key.unitTexCoordEnabled(2) || key.unitTexCoordEnabled(3));
|
||||
if (unit0FromCube || unit23FromCube) {
|
||||
emitCubeTexCoord(sb);
|
||||
}
|
||||
|
||||
if (key.texGenEnabled()) {
|
||||
emitTexGenCoordGeneration(sb, key);
|
||||
} else if (key.unitTexCoordEnabled(0)) {
|
||||
sb.append(" // Texture coordinates - unit 0\n");
|
||||
final String src = key.hasVertexTexCoord()
|
||||
? "vec4(a_TexCoord0, 0.0, 1.0)"
|
||||
: "u_CurrentTexCoord0";
|
||||
final String src = unit0Source(key);
|
||||
if (key.unitTexMatEnabled(0)) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
|
||||
} else {
|
||||
@@ -279,20 +309,17 @@ public final class VertexShaderGenerator {
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled()) {
|
||||
if (key.instancedDraw()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
|
||||
} else if (key.hasVertexLightmap()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
|
||||
}
|
||||
final String lightmap = switch (key.instancing()) {
|
||||
case PARTICLE, TEMPLATE -> "a_InstLightmap";
|
||||
case CUBE -> "a_InstLightmapScale.xy";
|
||||
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
|
||||
};
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
|
||||
}
|
||||
|
||||
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
|
||||
if (!key.unitTexCoordEnabled(i)) continue;
|
||||
final String src = key.unit23UvFromUnit0()
|
||||
? "vec4(a_TexCoord0, 0.0, 1.0)"
|
||||
: "u_CurrentTexCoord" + i;
|
||||
final String src = key.unit23UvFromUnit0() ? unit0Source(key) : "u_CurrentTexCoord" + i;
|
||||
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
|
||||
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
|
||||
} else {
|
||||
@@ -301,6 +328,12 @@ public final class VertexShaderGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitCubeTexCoord(StringBuilder sb) {
|
||||
sb.append(" // Cube texture net\n");
|
||||
sb.append(" ").append(InstancedGlslHelpers.cubePrelude("", "a_CubeMid", "a_CubeDelta", "a_Normal", "a_CubeTex", "\n ")).append('\n');
|
||||
sb.append(" vec4 cubeUv = ").append(InstancedGlslHelpers.cubeUv("a_CubeTex", "a_InstLightmapScale", "cubeU.x", "cubeU.y", "cubeV.x", "cubeV.y")).append(";\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
|
||||
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
|
||||
@@ -14,6 +15,12 @@ public final class GLSMHooks {
|
||||
public static ShaderTransformPostProcessor postTransformProcessor;
|
||||
public static PerFrameUniformBlock perFrameUniformBlock;
|
||||
public static PerFrameUniformBlock perPassUniformBlock;
|
||||
public static PendingProgramSelection pendingProgramSelection;
|
||||
|
||||
public static void resolvePendingProgram() {
|
||||
final PendingProgramSelection p = pendingProgramSelection;
|
||||
if (p != null && !DisplayListManager.isRecording()) p.resolvePendingProgram();
|
||||
}
|
||||
|
||||
// EventBus instances
|
||||
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface PendingProgramSelection {
|
||||
void resolvePendingProgram();
|
||||
}
|
||||
+46
@@ -1,8 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
@@ -12,6 +16,7 @@ import org.lwjgl.util.spvc.SpvcReflectedResource;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@@ -33,6 +38,47 @@ class FFPUniformBlockSpirvLayoutTest {
|
||||
assertBlockLayout(FragmentShaderGenerator.generate(fk), Shaderc.shaderc_fragment_shader);
|
||||
}
|
||||
|
||||
static Stream<Arguments> instancedKeys() {
|
||||
return Stream.of(
|
||||
Arguments.of("cube-lightmap-texmat", cubeKey(true, true, false)),
|
||||
Arguments.of("cube-texgen", cubeKey(false, false, true)),
|
||||
Arguments.of("particle-fog-texmat", particleFogTexMatKey())
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("instancedKeys")
|
||||
void instancedVariantCompilesToSpirv(String label, VertexKey key) {
|
||||
final String glsl = VertexShaderGenerator.generate(key);
|
||||
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, Shaderc.shaderc_vertex_shader, label, SpirvCompiler.Options.vulkanForced460Core());
|
||||
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
|
||||
MemoryUtil.memFree(result.spirv());
|
||||
}
|
||||
|
||||
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
|
||||
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
|
||||
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
|
||||
if (unit0TextureMatrix || texGen) bits |= bit("BIT_UNIT_TEXMAT_BASE");
|
||||
if (texGen) {
|
||||
bits |= (long) VertexKey.TG_OBJ_LINEAR << position("BIT_TEXGEN_S");
|
||||
bits |= (long) VertexKey.TG_OBJ_LINEAR << position("BIT_TEXGEN_T");
|
||||
}
|
||||
return VertexKey.fromPacked(VertexKey.withInstancing(bits, Instancing.CUBE));
|
||||
}
|
||||
|
||||
private static VertexKey particleFogTexMatKey() {
|
||||
final long bits = bit("BIT_UNIT_TEX_BASE") | 1L << (position("BIT_UNIT_TEX_BASE") + 1) | bit("BIT_UNIT_TEXMAT_BASE") | bit("BIT_FOG");
|
||||
return VertexKey.fromPacked(VertexKey.withInstancing(bits, Instancing.PARTICLE));
|
||||
}
|
||||
|
||||
private static long bit(String name) {
|
||||
return 1L << position(name);
|
||||
}
|
||||
|
||||
private static int position(String name) {
|
||||
return Reflect.getStatic(VertexKey.class, name);
|
||||
}
|
||||
|
||||
private static void assertBlockLayout(String glsl, int shaderKind) {
|
||||
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "ffp-layout-test", SpirvCompiler.Options.vulkanForced460Core());
|
||||
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
|
||||
|
||||
+251
-85
@@ -2,14 +2,23 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PendingProgramSelection;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.NORMAL_BIT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -22,41 +31,203 @@ class InstancedFFPDrawGLTest {
|
||||
private static final int TEMPLATE_STRIDE = 32;
|
||||
private static final int INSTANCE_STRIDE = InstancedAttribs.STRIDE;
|
||||
|
||||
private static final int PARITY_SIZE = 256;
|
||||
private static final int INTEGER_WIDTH = 300;
|
||||
private static final int INTEGER_HEIGHT = 100;
|
||||
|
||||
private static final List<String> log = new ArrayList<>();
|
||||
|
||||
private static final PendingProgramSelection LOGGING_SELECTION = () -> log.add("resolve");
|
||||
|
||||
private static final class LoggingBlendHandler implements DeferredBlendHandler {
|
||||
@Override
|
||||
public boolean isBlendLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deferBlendModeToggle(boolean enabled) {}
|
||||
|
||||
@Override
|
||||
public void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {}
|
||||
|
||||
@Override
|
||||
public void flushDeferredBlend() {
|
||||
log.add("flush");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean capturing;
|
||||
|
||||
private int vao, templateVbo, instanceVbo;
|
||||
|
||||
@BeforeAll
|
||||
static void registerForeignDrawEndListener() {
|
||||
capturing = true;
|
||||
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
|
||||
if (capturing) log.add("listener");
|
||||
});
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void stopCapturing() {
|
||||
capturing = false;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
sm.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLSMHooks.pendingProgramSelection = null;
|
||||
GLSMHooks.blendHandler = null;
|
||||
log.clear();
|
||||
FfpFixture.IntegerInstances.delete();
|
||||
FfpFixture.resetFfpState();
|
||||
CubeParityFixture.disableDirectionalLight();
|
||||
CubeParityFixture.deleteResources();
|
||||
ParticleParityFixture.deleteResources();
|
||||
if (templateVbo != 0) { GLStateManager.glDeleteBuffers(templateVbo); templateVbo = 0; }
|
||||
if (instanceVbo != 0) { GLStateManager.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
|
||||
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
|
||||
}
|
||||
|
||||
@Test
|
||||
void vertexKeyCarriesInstancedBit() {
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
final long plain = VertexKey.packFromState(true, true, false, false, 0);
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
final long instanced = VertexKey.packFromState(true, true, false, false, 0);
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
void drawArraysResolvesBeforeFlushDeferredBlend() {
|
||||
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
|
||||
GLSMHooks.blendHandler = new LoggingBlendHandler();
|
||||
|
||||
assertFalse(VertexKey.fromPacked(plain).instancedDraw());
|
||||
assertTrue(VertexKey.fromPacked(instanced).instancedDraw());
|
||||
assertEquals(1, Long.bitCount(plain ^ instanced), "only the instanced bit may differ");
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 0);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
assertEquals(List.of("resolve", "flush"), log);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveIsSuppressedWhileDisplayListIsRecording() {
|
||||
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
assertTrue(log.isEmpty());
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
assertEquals(List.of("resolve"), log);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedForeignDrawsResolveOnceAtOutermostEndBeforeListeners() {
|
||||
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
|
||||
|
||||
GLStateManager.beginForeignDraw();
|
||||
GLStateManager.beginForeignDraw();
|
||||
GLStateManager.endForeignDraw();
|
||||
assertTrue(log.isEmpty());
|
||||
|
||||
GLStateManager.endForeignDraw();
|
||||
assertEquals(List.of("resolve", "listener"), log);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unitCubeMatchesBakedQuadsWithLightmap() {
|
||||
assertCubeParity(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unitCubeMatchesBakedQuadsWhenLit() {
|
||||
assertCubeParity(false, true);
|
||||
}
|
||||
|
||||
private void assertCubeParity(boolean lightmap, boolean lit) {
|
||||
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
|
||||
CubeParityFixture.setupScene(lightmap, lit);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
sm.activate();
|
||||
|
||||
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
|
||||
|
||||
FfpFixture.clear();
|
||||
CubeParityFixture.drawReference(specs, lightmap);
|
||||
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
FfpFixture.clear();
|
||||
CubeParityFixture.drawInstanced(specs);
|
||||
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
|
||||
|
||||
CubeParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
|
||||
}
|
||||
|
||||
@Test
|
||||
void particleUnit0TextureMatrixMatchesReference() {
|
||||
final ParticleParityFixture.Rotation rot = ParticleParityFixture.Rotation.of(35f, -12f, false);
|
||||
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
|
||||
ParticleParityFixture.setupScene(true);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
sm.activate();
|
||||
|
||||
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
|
||||
|
||||
FfpFixture.clear();
|
||||
ParticleParityFixture.drawReference(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
|
||||
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
FfpFixture.clear();
|
||||
ParticleParityFixture.drawInstanced(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
|
||||
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
|
||||
ParticleParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityAttribSelectsColorPerInstance() {
|
||||
GLStateManager.glViewport(0, 0, INTEGER_WIDTH, INTEGER_HEIGHT);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.disableCull();
|
||||
|
||||
FfpFixture.IntegerInstances.build(0.66f);
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
FfpFixture.IntegerInstances.draw();
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced ivec4 draw must not raise a GL error");
|
||||
|
||||
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
|
||||
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
|
||||
assertPixel(249, 50, 0, 0, 255, "entity 42 must render blue");
|
||||
}
|
||||
|
||||
private static void assertPixel(int x, int y, int r, int g, int b, String label) {
|
||||
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
|
||||
GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
|
||||
final int gotR = pixel.get(0) & 0xFF;
|
||||
final int gotG = pixel.get(1) & 0xFF;
|
||||
final int gotB = pixel.get(2) & 0xFF;
|
||||
assertEquals(r, gotR, () -> label + ": red channel, got (" + gotR + "," + gotG + "," + gotB + ")");
|
||||
assertEquals(g, gotG, () -> label + ": green channel, got (" + gotR + "," + gotG + "," + gotB + ")");
|
||||
assertEquals(b, gotB, () -> label + ": blue channel, got (" + gotR + "," + gotG + "," + gotB + ")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void instancedSourceUsesInstanceAttribs() {
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
|
||||
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
|
||||
final String source = VertexShaderGenerator.generate(key);
|
||||
assertTrue(source.contains("a_InstCol0"), "instance matrix attribs declared");
|
||||
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
|
||||
assertTrue(source.contains("mat4 instMV"), "instance matrix used as modelview");
|
||||
assertTrue(source.contains("a_InstColor"), "instance color multiplier used");
|
||||
assertTrue(source.contains("a_InstLightmap"), "instance lightmap coord used");
|
||||
@@ -74,7 +245,7 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.disableCull();
|
||||
|
||||
buildTemplate();
|
||||
buildInstances();
|
||||
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
@@ -83,14 +254,12 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
drawInstances();
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced draw must not raise a GL error");
|
||||
|
||||
final int[] left = readPixel(200, 300);
|
||||
final int[] right = readPixel(600, 300);
|
||||
final int[] left = FfpFixture.readPixel(200, 300);
|
||||
final int[] right = FfpFixture.readPixel(600, 300);
|
||||
assertTrue(left[0] > 200 && left[1] < 50, "left instance red, got " + left[0] + "," + left[1] + "," + left[2]);
|
||||
assertTrue(right[1] > 200 && right[0] < 50, "right instance green, got " + right[0] + "," + right[1] + "," + right[2]);
|
||||
}
|
||||
@@ -106,7 +275,7 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.disableCull();
|
||||
|
||||
buildTemplate();
|
||||
buildInstancesWithAlpha();
|
||||
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
@@ -117,14 +286,12 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
drawInstances();
|
||||
GLStateManager.disableAlphaTest();
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
final int[] left = readPixel(200, 300);
|
||||
final int[] right = readPixel(600, 300);
|
||||
final int[] left = FfpFixture.readPixel(200, 300);
|
||||
final int[] right = FfpFixture.readPixel(600, 300);
|
||||
assertTrue(left[0] > 200, "opaque instance drawn, got " + left[0]);
|
||||
assertTrue(right[0] < 20 && right[1] < 20, "alpha 0.05 instance discarded, got " + right[0] + "," + right[1]);
|
||||
}
|
||||
@@ -141,7 +308,7 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.disableCull();
|
||||
|
||||
buildTemplate();
|
||||
buildInstances();
|
||||
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
@@ -150,48 +317,71 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
drawInstances();
|
||||
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.glColor4f(1f, 1f, 1f, 1f);
|
||||
rewriteInstanceColors(0x800000FF, 0x8000FF00);
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
uploadInstances(0x800000FF, 0, 0x8000FF00, 0);
|
||||
drawInstances();
|
||||
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.disableDepthTest();
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
final int[] left = readPixel(200, 300);
|
||||
final int[] background = readPixel(400, 550);
|
||||
final int[] left = FfpFixture.readPixel(200, 300);
|
||||
final int[] background = FfpFixture.readPixel(400, 550);
|
||||
assertTrue(left[0] > 200, "overlay blended over base, got " + left[0]);
|
||||
assertTrue(background[0] < 20 && background[1] < 20, "overlay must not draw off-base, got " + background[0]);
|
||||
}
|
||||
|
||||
private void rewriteInstanceColors(int leftABGR, int rightABGR) {
|
||||
final ByteBuffer patch = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
|
||||
putInstance(patch, -0.5f, leftABGR);
|
||||
putInstance(patch, 0.5f, rightABGR);
|
||||
patch.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, patch, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
@Test
|
||||
void perInstanceOverlayMixesOnlyTintedInstances() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glViewport(0, 0, 800, 600);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.disableCull();
|
||||
|
||||
buildTemplate();
|
||||
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.setOverlayColor(0f, 1f, 0f, 1f);
|
||||
drawInstances();
|
||||
GLStateManager.setOverlayColor(0f, 0f, 0f, 0f);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced overlay draw must not raise a GL error");
|
||||
|
||||
final int[] left = FfpFixture.readPixel(200, 300);
|
||||
final int[] right = FfpFixture.readPixel(600, 300);
|
||||
assertTrue(left[0] > 200 && left[1] > 200 && left[2] > 200, "left instance overlay alpha 0 keeps its base color, got " + left[0] + "," + left[1] + "," + left[2]);
|
||||
assertTrue(right[0] > 200 && right[1] < 50 && right[2] < 50, "right instance is mixed to its own opaque red overlay, got " + right[0] + "," + right[1] + "," + right[2]);
|
||||
}
|
||||
|
||||
private void buildInstancesWithAlpha() {
|
||||
private void uploadInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
|
||||
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
|
||||
putInstance(instances, -0.5f, 0xFF0000FF);
|
||||
putInstance(instances, 0.5f, 0x0D0000FF);
|
||||
putInstance(instances, -0.5f, leftColor, leftOverlay);
|
||||
putInstance(instances, 0.5f, rightColor, rightOverlay);
|
||||
instances.flip();
|
||||
|
||||
instanceVbo = GLStateManager.glGenBuffers();
|
||||
final boolean first = instanceVbo == 0;
|
||||
if (first) instanceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
|
||||
pointAndEnableInstanceAttribs();
|
||||
if (first) {
|
||||
pointAndEnableInstanceAttribs();
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -225,47 +415,23 @@ class InstancedFFPDrawGLTest {
|
||||
buf.position(base + TEMPLATE_STRIDE);
|
||||
}
|
||||
|
||||
private void buildInstances() {
|
||||
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
|
||||
putInstance(instances, -0.5f, 0xFF0000FF);
|
||||
putInstance(instances, 0.5f, 0xFF00FF00);
|
||||
instances.flip();
|
||||
|
||||
instanceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
|
||||
pointAndEnableInstanceAttribs();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
private static void pointAndEnableInstanceAttribs() {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
final int loc = InstancedAttribs.LOC_MATRIX_COL0 + c;
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribPointer(loc, 4, GL11.GL_FLOAT, false, INSTANCE_STRIDE, c * 16L);
|
||||
GLStateManager.glVertexAttribDivisor(loc, 1);
|
||||
}
|
||||
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, INSTANCE_STRIDE, InstancedAttribs.OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribDivisor(InstancedAttribs.LOC_COLOR, 1);
|
||||
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_LIGHTMAP);
|
||||
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, INSTANCE_STRIDE, InstancedAttribs.OFFSET_LIGHTMAP);
|
||||
GLStateManager.glVertexAttribDivisor(InstancedAttribs.LOC_LIGHTMAP, 1);
|
||||
InstancedAttribs.enableHeadArrays();
|
||||
InstancedAttribs.pointTemplate(0L);
|
||||
}
|
||||
|
||||
private void putInstance(ByteBuffer buf, float translateX, int colorABGR) {
|
||||
private static void putInstance(ByteBuffer buf, float translateX, int colorABGR, int overlayABGR) {
|
||||
final int base = buf.position();
|
||||
buf.putFloat(base, 1f).putFloat(base + 20, 1f).putFloat(base + 40, 1f).putFloat(base + 60, 1f);
|
||||
buf.putFloat(base + 48, translateX);
|
||||
buf.putInt(base + InstancedAttribs.OFFSET_COLOR, colorABGR);
|
||||
buf.putFloat(base + InstancedAttribs.OFFSET_LIGHTMAP, 240f);
|
||||
buf.putFloat(base + InstancedAttribs.OFFSET_LIGHTMAP + 4, 240f);
|
||||
final long ptr = memAddress0(buf) + base;
|
||||
InstancedAttribs.writeHead(ptr, FfpFixture.translationMatrix(translateX), 0, colorABGR, overlayABGR, 0L);
|
||||
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP, 240f);
|
||||
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP + 4, 240f);
|
||||
buf.position(base + INSTANCE_STRIDE);
|
||||
}
|
||||
|
||||
private static int[] readPixel(int x, int y) {
|
||||
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
|
||||
GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
|
||||
return new int[] { pixel.get(0) & 0xFF, pixel.get(1) & 0xFF, pixel.get(2) & 0xFF };
|
||||
private static void drawInstances() {
|
||||
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
|
||||
public final class CubeParityFixture {
|
||||
|
||||
private static final int TEX_WIDTH = 64;
|
||||
private static final int TEX_HEIGHT = 32;
|
||||
private static final float SCALE = 0.0625f;
|
||||
private static final short BRIGHTNESS = 240;
|
||||
|
||||
public record Spec(int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate, boolean mirror) {}
|
||||
|
||||
private static final float[] MODELVIEW = {
|
||||
1.25f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.25f, 0.0f, 0.0f,
|
||||
0.375f, 0.25f, -0.25f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
};
|
||||
|
||||
private static final float[] MODELVIEW_CONFORMAL = {
|
||||
1.0f, 0.0f, 0.75f, 0.0f,
|
||||
0.0f, 1.25f, 0.0f, 0.0f,
|
||||
-0.75f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
};
|
||||
|
||||
private static float[] modelView = MODELVIEW;
|
||||
|
||||
private static int netTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
private CubeParityFixture() {}
|
||||
|
||||
public static Spec[] specs() {
|
||||
return new Spec[] {
|
||||
new Spec(0, 0, -4, -6, -2, 8, 12, 4, 0.0f, false),
|
||||
new Spec(16, 8, -3, 1, -3, 6, 4, 6, 0.25f, true),
|
||||
};
|
||||
}
|
||||
|
||||
private static void setupCamera(boolean conformal) {
|
||||
modelView = conformal ? MODELVIEW_CONFORMAL : MODELVIEW;
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
final FloatBuffer mv = BufferUtils.createFloatBuffer(16);
|
||||
mv.put(modelView).flip();
|
||||
GLStateManager.glLoadMatrix(mv);
|
||||
}
|
||||
|
||||
public static void setupScene(boolean lightmap, boolean lit) {
|
||||
GLStateManager.glDisable(GL11.GL_FOG);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
setupCamera(lit);
|
||||
if (lit) {
|
||||
enableDirectionalLight();
|
||||
} else {
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
}
|
||||
|
||||
netTexture = createNetTexture();
|
||||
if (lightmap) {
|
||||
lightmapTexture = createNetTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glScalef(0.00390625f, 0.00390625f, 0.00390625f);
|
||||
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, netTexture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void enableDirectionalLight() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glEnable(GL11.GL_NORMALIZE);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, fill(buf, 0.2f, 0.2f, 0.2f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, fill(buf, 0.375f, 0.75f, 0.5f, 0.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, fill(buf, 1.0f, 1.0f, 1.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, fill(buf, 0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, fill(buf, 0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
}
|
||||
|
||||
public static void disableDirectionalLight() {
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glDisable(GL11.GL_NORMALIZE);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
}
|
||||
|
||||
private static FloatBuffer fill(FloatBuffer buf, float x, float y, float z, float w) {
|
||||
buf.clear();
|
||||
buf.put(x).put(y).put(z).put(w).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static int createNetTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
final ByteBuffer texels = BufferUtils.createByteBuffer(TEX_WIDTH * TEX_HEIGHT * 4);
|
||||
for (int y = 0; y < TEX_HEIGHT; y++) {
|
||||
for (int x = 0; x < TEX_WIDTH; x++) {
|
||||
texels.put((byte) (16 + x * 3));
|
||||
texels.put((byte) (16 + y * 7));
|
||||
texels.put((byte) (255 - x * 3));
|
||||
texels.put((byte) 255);
|
||||
}
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_WIDTH, TEX_HEIGHT, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static final int REF_STRIDE = 44;
|
||||
|
||||
private static int referenceVao;
|
||||
private static int referenceVbo;
|
||||
|
||||
private static ByteBuffer buildQuadData(Spec[] specs) {
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(specs.length * 24 * REF_STRIDE);
|
||||
for (Spec spec : specs) {
|
||||
final float[] corners = corners(spec);
|
||||
for (int face = 0; face < UnitCubeMesh.FACE_COUNT; face++) {
|
||||
final float[] uv = faceRect(spec, face);
|
||||
final float[] normal = UnitCubeMesh.FACES[face].normal();
|
||||
for (int corner = 0; corner < 4; corner++) {
|
||||
final int source = spec.mirror() ? 3 - corner : corner;
|
||||
final int index = UnitCubeMesh.FACES[face].corners()[source];
|
||||
final int base = data.position();
|
||||
data.putFloat(base, corners[index * 3] * SCALE);
|
||||
data.putFloat(base + 4, corners[index * 3 + 1] * SCALE);
|
||||
data.putFloat(base + 8, corners[index * 3 + 2] * SCALE);
|
||||
data.putInt(base + 12, 0xFFFFFFFF);
|
||||
data.putFloat(base + 16, uv[(source == 0 || source == 3) ? 2 : 0]);
|
||||
data.putFloat(base + 20, uv[source >= 2 ? 3 : 1]);
|
||||
data.putFloat(base + 24, BRIGHTNESS);
|
||||
data.putFloat(base + 28, BRIGHTNESS);
|
||||
data.putFloat(base + 32, spec.mirror() ? -normal[0] : normal[0]);
|
||||
data.putFloat(base + 36, normal[1]);
|
||||
data.putFloat(base + 40, normal[2]);
|
||||
data.position(base + REF_STRIDE);
|
||||
}
|
||||
}
|
||||
}
|
||||
data.flip();
|
||||
return data;
|
||||
}
|
||||
|
||||
private static int uploadQuads(int vbo, Spec[] specs, boolean lightmap) {
|
||||
final ByteBuffer data = buildQuadData(specs);
|
||||
if (vbo == 0) vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
|
||||
FfpFixture.attrib(1, 4, GL11.GL_UNSIGNED_BYTE, true, REF_STRIDE, 12);
|
||||
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 16);
|
||||
if (lightmap) {
|
||||
FfpFixture.attrib(3, 2, GL11.GL_FLOAT, false, REF_STRIDE, 24);
|
||||
} else {
|
||||
GLStateManager.glDisableVertexAttribArray(3);
|
||||
}
|
||||
FfpFixture.attrib(4, 3, GL11.GL_FLOAT, false, REF_STRIDE, 32);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
return vbo;
|
||||
}
|
||||
|
||||
public static void drawReference(Spec[] specs, boolean lightmap) {
|
||||
if (referenceVao == 0) referenceVao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(referenceVao);
|
||||
referenceVbo = uploadQuads(referenceVbo, specs, lightmap);
|
||||
VAOManager.setCurrentVertexFlags(UnitCubeMesh.VERTEX_FLAGS | (lightmap ? VertexFlags.BRIGHTNESS_BIT : 0));
|
||||
|
||||
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, specs.length * 24);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private static float[] corners(Spec spec) {
|
||||
final float minX = spec.x() - spec.inflate();
|
||||
final float minY = spec.y() - spec.inflate();
|
||||
final float minZ = spec.z() - spec.inflate();
|
||||
final float maxX = (spec.x() + spec.sizeX()) + spec.inflate();
|
||||
final float maxY = (spec.y() + spec.sizeY()) + spec.inflate();
|
||||
final float maxZ = (spec.z() + spec.sizeZ()) + spec.inflate();
|
||||
final float lowX = spec.mirror() ? maxX : minX;
|
||||
final float highX = spec.mirror() ? minX : maxX;
|
||||
final float[] out = new float[UnitCubeMesh.CORNERS.length * 3];
|
||||
for (int index = 0; index < UnitCubeMesh.CORNERS.length; index++) {
|
||||
final float[] corner = UnitCubeMesh.CORNERS[index];
|
||||
out[index * 3] = corner[0] > 0f ? highX : lowX;
|
||||
out[index * 3 + 1] = corner[1] > 0f ? maxY : minY;
|
||||
out[index * 3 + 2] = corner[2] > 0f ? maxZ : minZ;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static float[] faceRect(Spec spec, int face) {
|
||||
final int i = spec.texU();
|
||||
final int j = spec.texV();
|
||||
final int k = spec.sizeX();
|
||||
final int l = spec.sizeY();
|
||||
final int m = spec.sizeZ();
|
||||
final int[] rect = switch (face) {
|
||||
case 0 -> new int[] { i + m + k, j + m, i + m + k + m, j + m + l };
|
||||
case 1 -> new int[] { i, j + m, i + m, j + m + l };
|
||||
case 2 -> new int[] { i + m, j, i + m + k, j + m };
|
||||
case 3 -> new int[] { i + m + k, j + m, i + m + k + k, j };
|
||||
case 4 -> new int[] { i + m, j + m, i + m + k, j + m + l };
|
||||
default -> new int[] { i + m + k + m, j + m, i + m + k + m + k, j + m + l };
|
||||
};
|
||||
return new float[] {
|
||||
rect[0] / (float) TEX_WIDTH, rect[1] / (float) TEX_HEIGHT,
|
||||
rect[2] / (float) TEX_WIDTH, rect[3] / (float) TEX_HEIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
private static int instanceVbo;
|
||||
|
||||
public static void drawInstanced(Spec[] specs) {
|
||||
final ByteBuffer records = BufferUtils.createByteBuffer(specs.length * CubeInstancedAttribs.STRIDE);
|
||||
final long records0 = memAddress0(records);
|
||||
for (int index = 0; index < specs.length; index++) {
|
||||
final Spec spec = specs[index];
|
||||
final CubeParams cube = CubeParams.of(TEX_WIDTH, TEX_HEIGHT, spec.mirror(), spec.texU(), spec.texV(), spec.x(), spec.y(), spec.z(), spec.sizeX(), spec.sizeY(), spec.sizeZ(), spec.inflate());
|
||||
if (cube == null) {
|
||||
throw new AssertionError("spec " + index + " is not cube instanceable");
|
||||
}
|
||||
final long ptr = records0 + (long) index * CubeInstancedAttribs.STRIDE;
|
||||
cube.writeRows(ptr, modelView, 0, SCALE);
|
||||
InstancedAttribs.writeTail(ptr, 0xFFFFFFFF, 0, 0L);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, BRIGHTNESS);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 4, BRIGHTNESS);
|
||||
cube.writeTexture(ptr);
|
||||
}
|
||||
records.position(0).limit(specs.length * CubeInstancedAttribs.STRIDE);
|
||||
|
||||
GLStateManager.glBindVertexArray(UnitCubeMesh.vao());
|
||||
VAOManager.setCurrentVertexFlags(UnitCubeMesh.VERTEX_FLAGS);
|
||||
if (instanceVbo == 0) instanceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
|
||||
CubeInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GLStateManager.ffpInstancing = Instancing.CUBE;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, specs.length);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
|
||||
FfpFixture.assertPixelParity("instanced cubes", reference, instanced, size, background, 8, 1, describe);
|
||||
}
|
||||
|
||||
public static void deleteResources() {
|
||||
if (instanceVbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(instanceVbo);
|
||||
instanceVbo = 0;
|
||||
}
|
||||
if (referenceVbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(referenceVbo);
|
||||
referenceVbo = 0;
|
||||
}
|
||||
if (referenceVao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(referenceVao);
|
||||
referenceVao = 0;
|
||||
}
|
||||
if (netTexture != 0) {
|
||||
GLStateManager.glDeleteTextures(netTexture);
|
||||
netTexture = 0;
|
||||
}
|
||||
if (lightmapTexture != 0) {
|
||||
GLStateManager.glDeleteTextures(lightmapTexture);
|
||||
lightmapTexture = 0;
|
||||
}
|
||||
UnitCubeMesh.delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
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.Assertions.fail;
|
||||
|
||||
public final class FfpFixture {
|
||||
|
||||
private FfpFixture() {}
|
||||
|
||||
public static final class IntegerInstances {
|
||||
|
||||
private static final int INSTANCES = 3;
|
||||
|
||||
private static final String VS_HEAD = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
layout(location = 5) in vec4 a_InstRow0;
|
||||
layout(location = 6) in vec4 a_InstRow1;
|
||||
layout(location = 7) in vec4 a_InstRow2;
|
||||
layout(location = 11) in ivec4 iris_Entity;
|
||||
flat out vec3 v_Color;
|
||||
void main() {
|
||||
""";
|
||||
|
||||
private static final String VS_TAIL = """
|
||||
gl_Position = instMV * vec4(a_Position, 1.0);
|
||||
if (iris_Entity.x == -1) { v_Color = vec3(1.0, 0.0, 0.0); }
|
||||
else if (iris_Entity.x == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
|
||||
else { v_Color = vec3(0.0, 0.0, 1.0); }
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String VS = VS_HEAD + " mat4 instMV = " + InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2") + ";\n" + VS_TAIL;
|
||||
|
||||
private static final String FS = """
|
||||
#version 330 core
|
||||
flat in vec3 v_Color;
|
||||
out vec4 fragColor;
|
||||
void main() { fragColor = vec4(v_Color, 1.0); }
|
||||
""";
|
||||
|
||||
private static int program;
|
||||
private static int vao;
|
||||
private static int templateVbo;
|
||||
private static int instanceVbo;
|
||||
|
||||
private IntegerInstances() {}
|
||||
|
||||
public static void build(float spacing) {
|
||||
program = buildProgram();
|
||||
GLStateManager.glUseProgram(program);
|
||||
|
||||
buildTemplate();
|
||||
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 42 });
|
||||
}
|
||||
|
||||
public static void draw() {
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, INSTANCES);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
GLStateManager.glUseProgram(0);
|
||||
if (program != 0) { GLStateManager.glDeleteProgram(program); program = 0; }
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
if (templateVbo != 0) { GLStateManager.glDeleteBuffers(templateVbo); templateVbo = 0; }
|
||||
if (instanceVbo != 0) { GLStateManager.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
|
||||
}
|
||||
|
||||
private static void buildTemplate() {
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
final ByteBuffer quad = BufferUtils.createByteBuffer(4 * 12);
|
||||
putVertex(quad, -0.15f, -0.8f);
|
||||
putVertex(quad, 0.15f, -0.8f);
|
||||
putVertex(quad, 0.15f, 0.8f);
|
||||
putVertex(quad, -0.15f, 0.8f);
|
||||
quad.flip();
|
||||
|
||||
templateVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, templateVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, quad, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
private static void putVertex(ByteBuffer buf, float x, float y) {
|
||||
final int base = buf.position();
|
||||
buf.putFloat(base, x).putFloat(base + 4, y).putFloat(base + 8, 0f);
|
||||
buf.position(base + 12);
|
||||
}
|
||||
|
||||
private static void buildInstances(float[] translateX, int[] entities) {
|
||||
final ByteBuffer instances = BufferUtils.createByteBuffer(entities.length * InstancedAttribs.STRIDE);
|
||||
final long base0 = memAddress0(instances);
|
||||
for (int i = 0; i < entities.length; i++) {
|
||||
final long ptr = base0 + (long) i * InstancedAttribs.STRIDE;
|
||||
InstancedAttribs.writeHead(ptr, translationMatrix(translateX[i]), 0, 0xFFFFFFFF, 0, InstancedAttribs.packEntityInfo(entities[i], 0, 0));
|
||||
}
|
||||
instances.position(0).limit(entities.length * InstancedAttribs.STRIDE);
|
||||
|
||||
instanceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
|
||||
InstancedAttribs.enableHeadArrays();
|
||||
InstancedAttribs.pointTemplate(0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
private static int buildProgram() {
|
||||
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
|
||||
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
|
||||
final int p = GLStateManager.glCreateProgram();
|
||||
GLStateManager.glAttachShader(p, vs);
|
||||
GLStateManager.glAttachShader(p, fs);
|
||||
GLStateManager.glLinkProgram(p);
|
||||
assertNotEquals(0, GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "test program must link: " + GLStateManager.glGetProgramInfoLog(p, 4096));
|
||||
GLStateManager.glDeleteShader(vs);
|
||||
GLStateManager.glDeleteShader(fs);
|
||||
return p;
|
||||
}
|
||||
|
||||
private static int compile(int type, String source) {
|
||||
final int shader = GLStateManager.glCreateShader(type);
|
||||
GLStateManager.glShaderSource(shader, source);
|
||||
GLStateManager.glCompileShader(shader);
|
||||
if (GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == 0) {
|
||||
fail("test shader failed to compile:\n" + GLStateManager.glGetShaderInfoLog(shader, 4096) + "\n\n" + source);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
|
||||
public static void attrib(int loc, int size, int type, boolean normalized, int stride, int offset) {
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, stride, offset);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
public static int[] readRegion(int size) {
|
||||
final ByteBuffer pixels = BufferUtils.createByteBuffer(size * size * 4);
|
||||
GLStateManager.glReadPixels(0, 0, size, size, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
final int[] out = new int[size * size];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
out[i] = pixels.getInt(i * 4);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static int[] readPixel(int x, int y) {
|
||||
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
|
||||
GLStateManager.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
|
||||
return new int[] { pixel.get(0) & 0xFF, pixel.get(1) & 0xFF, pixel.get(2) & 0xFF, pixel.get(3) & 0xFF };
|
||||
}
|
||||
|
||||
public static int solidTexture(int r, int g, int b, int a) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
final ByteBuffer texels = BufferUtils.createByteBuffer(2 * 2 * 4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
texels.put((byte) r).put((byte) g).put((byte) b).put((byte) a);
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
return id;
|
||||
}
|
||||
|
||||
public static float[] translationMatrix(float x) {
|
||||
return new float[] {
|
||||
1f, 0f, 0f, 0f,
|
||||
0f, 1f, 0f, 0f,
|
||||
0f, 0f, 1f, 0f,
|
||||
x, 0f, 0f, 1f,
|
||||
};
|
||||
}
|
||||
|
||||
public static void assertPixelParity(String subject, int[] reference, int[] candidate, int size, int background, int minCoverageDivisor, int channelTolerance, IntFunction<String> describe) {
|
||||
int covered = 0;
|
||||
for (int pixel : reference) {
|
||||
if (pixel != background) covered++;
|
||||
}
|
||||
final int coveredPixels = covered;
|
||||
assertTrue(coveredPixels > reference.length / minCoverageDivisor, () -> "reference draw covered only " + coveredPixels + " of " + reference.length + " pixels");
|
||||
|
||||
int mismatches = 0;
|
||||
int firstMismatch = -1;
|
||||
for (int i = 0; i < reference.length; i++) {
|
||||
if (!withinTolerance(reference[i], candidate[i], channelTolerance)) {
|
||||
mismatches++;
|
||||
if (firstMismatch < 0) firstMismatch = i;
|
||||
}
|
||||
}
|
||||
final int index = firstMismatch;
|
||||
final int differing = mismatches;
|
||||
assertEquals(0, mismatches, () -> subject + " differ from baked quads in " + differing + " of " + reference.length + " pixels, first at (" + (index % size) + "," + (index / size) + "): reference " + describe.apply(reference[index]) + " instanced " + describe.apply(candidate[index]));
|
||||
}
|
||||
|
||||
private static boolean withinTolerance(int a, int b, int tolerance) {
|
||||
if (tolerance == 0) return a == b;
|
||||
for (int shift = 0; shift <= 24; shift += 8) {
|
||||
final int ca = (a >> shift) & 0xFF;
|
||||
final int cb = (b >> shift) & 0xFF;
|
||||
if (Math.abs(ca - cb) > tolerance) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void assertOverlayFootprint(int[] base, int[] overlay, int background, String overlayName) {
|
||||
int covered = 0;
|
||||
for (int i = 0; i < base.length; i++) {
|
||||
final int index = i;
|
||||
if (base[i] != background) {
|
||||
covered++;
|
||||
assertTrue(base[i] != overlay[i], () -> "pixel " + index + " covered by the base draw did not change under the " + overlayName + ": 0x" + Integer.toHexString(overlay[index]));
|
||||
} else {
|
||||
assertEquals(base[i], overlay[i], () -> "pixel " + index + " outside the base footprint must not be touched by the " + overlayName);
|
||||
}
|
||||
}
|
||||
final int coveredPixels = covered;
|
||||
assertTrue(coveredPixels > base.length / 8, "base draw covered only " + coveredPixels + " of " + base.length + " pixels");
|
||||
}
|
||||
|
||||
public static void resetFfpState() {
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
sm.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
|
||||
public final class ParticleParityFixture {
|
||||
|
||||
public record Particle(float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR) {}
|
||||
|
||||
public record Rotation(float x, float xz, float z, float yz, float xy) {
|
||||
public static Rotation of(float yawDeg, float pitchDeg, boolean thirdPersonFront) {
|
||||
final float sign = thirdPersonFront ? -1f : 1f;
|
||||
final float yaw = (float) Math.toRadians(yawDeg);
|
||||
final float pitch = (float) Math.toRadians(pitchDeg);
|
||||
final float rotX = (float) Math.cos(yaw) * sign;
|
||||
final float rotZ = (float) Math.sin(yaw) * sign;
|
||||
final float rotYZ = -rotZ * (float) Math.sin(pitch) * sign;
|
||||
final float rotXY = rotX * (float) Math.sin(pitch) * sign;
|
||||
final float rotXZ = (float) Math.cos(pitch);
|
||||
return new Rotation(rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
}
|
||||
|
||||
public float offsetX(float a, float b) {
|
||||
return a * x + b * yz;
|
||||
}
|
||||
|
||||
public float offsetY(float b) {
|
||||
return b * xz;
|
||||
}
|
||||
|
||||
public float offsetZ(float a, float b) {
|
||||
return a * z + b * xy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
|
||||
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
|
||||
|
||||
private static final int REF_STRIDE = 24;
|
||||
|
||||
private static int referenceVao;
|
||||
private static int referenceVbo;
|
||||
private static int instanceVbo;
|
||||
private static int checkerTexture;
|
||||
|
||||
private ParticleParityFixture() {}
|
||||
|
||||
public static float cornerA(int corner) {
|
||||
return CORNER_A[corner];
|
||||
}
|
||||
|
||||
public static float cornerB(int corner) {
|
||||
return CORNER_B[corner];
|
||||
}
|
||||
|
||||
public static Particle[] particles() {
|
||||
return new Particle[] {
|
||||
new Particle(-0.75f, 0.6f, 0f, 0.10f, 0f, 0f, 1f, 1f, 0xFF0000FF),
|
||||
new Particle(-0.45f, -0.55f, 0f, 0.16f, 0f, 0f, 1f, 1f, 0xFF00FF00),
|
||||
new Particle(-0.15f, 0.2f, 0f, 0.07f, 0f, 0f, 1f, 1f, 0xFFFF0000),
|
||||
new Particle(0.1f, 0.75f, 0f, -0.12f, 0f, 0f, 1f, 1f, 0xFF00FFFF),
|
||||
new Particle(0.35f, -0.3f, 0f, 0.20f, 0f, 0f, 1f, 1f, 0xFFFF00FF),
|
||||
new Particle(0.6f, 0.45f, 0f, -0.08f, 0f, 0f, 1f, 1f, 0xFFFFFF00),
|
||||
new Particle(0.8f, -0.7f, 0f, 0.13f, 0f, 0f, 1f, 1f, 0xFFFFFFFF),
|
||||
new Particle(-0.8f, -0.1f, 0f, 0.09f, 0f, 0f, 1f, 1f, 0xFF0080FF),
|
||||
};
|
||||
}
|
||||
|
||||
private static int createCheckerTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
final ByteBuffer texels = BufferUtils.createByteBuffer(2 * 2 * 4);
|
||||
putTexel(texels, 255, 32, 32);
|
||||
putTexel(texels, 32, 255, 32);
|
||||
putTexel(texels, 32, 32, 255);
|
||||
putTexel(texels, 255, 255, 32);
|
||||
texels.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void putTexel(ByteBuffer buf, int r, int g, int b) {
|
||||
buf.put((byte) r).put((byte) g).put((byte) b).put((byte) 255);
|
||||
}
|
||||
|
||||
public static void setupScene(boolean textureMatrix) {
|
||||
GLStateManager.glDisable(GL11.GL_FOG);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
checkerTexture = createCheckerTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, checkerTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
if (textureMatrix) {
|
||||
GLStateManager.glTranslatef(0.1f, 0.1f, 0.0f);
|
||||
GLStateManager.glScalef(0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
private static ByteBuffer buildReferenceData(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(particles.length * 4 * REF_STRIDE);
|
||||
for (Particle p : particles) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final float a = cornerA(i);
|
||||
final float b = cornerB(i);
|
||||
final float ox = a * rotX + b * rotYZ;
|
||||
final float oy = b * rotXZ;
|
||||
final float oz = a * rotZ + b * rotXY;
|
||||
final float u = a < 0f ? p.u0() : p.u1();
|
||||
final float v = b < 0f ? p.v0() : p.v1();
|
||||
final int base = data.position();
|
||||
data.putFloat(base, p.cx() + ox * p.half());
|
||||
data.putFloat(base + 4, p.cy() + oy * p.half());
|
||||
data.putFloat(base + 8, p.cz() + oz * p.half());
|
||||
data.putInt(base + 12, p.colorABGR());
|
||||
data.putFloat(base + 16, u);
|
||||
data.putFloat(base + 20, v);
|
||||
data.position(base + REF_STRIDE);
|
||||
}
|
||||
}
|
||||
data.flip();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void drawReference(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
final ByteBuffer data = buildReferenceData(particles, rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
|
||||
if (referenceVao == 0) referenceVao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(referenceVao);
|
||||
if (referenceVbo == 0) referenceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, referenceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
|
||||
FfpFixture.attrib(1, 4, GL11.GL_UNSIGNED_BYTE, true, REF_STRIDE, 12);
|
||||
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 16);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT);
|
||||
|
||||
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, particles.length * 4);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static void drawInstanced(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
ParticleQuadMesh.update(rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
|
||||
final ByteBuffer records = BufferUtils.createByteBuffer(particles.length * ParticleInstancedAttribs.STRIDE);
|
||||
final long records0 = memAddress0(records);
|
||||
for (int i = 0; i < particles.length; i++) {
|
||||
final Particle p = particles[i];
|
||||
final long ptr = records0 + (long) i * ParticleInstancedAttribs.STRIDE;
|
||||
ParticleInstancedAttribs.writeInstance(ptr, p.cx(), p.cy(), p.cz(), p.half(), p.u0(), p.v0(), p.u1(), p.v1(), p.colorABGR(), 0);
|
||||
}
|
||||
records.position(0).limit(particles.length * ParticleInstancedAttribs.STRIDE);
|
||||
|
||||
GLStateManager.glBindVertexArray(ParticleQuadMesh.vao());
|
||||
VAOManager.setCurrentVertexFlags(ParticleQuadMesh.VERTEX_FLAGS);
|
||||
if (instanceVbo == 0) instanceVbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
|
||||
ParticleInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GLStateManager.ffpInstancing = Instancing.PARTICLE;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, particles.length);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
|
||||
FfpFixture.assertPixelParity("instanced particles", reference, instanced, size, background, 16, 1, describe);
|
||||
}
|
||||
|
||||
public static void deleteResources() {
|
||||
if (instanceVbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(instanceVbo);
|
||||
instanceVbo = 0;
|
||||
}
|
||||
if (referenceVbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(referenceVbo);
|
||||
referenceVbo = 0;
|
||||
}
|
||||
if (referenceVao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(referenceVao);
|
||||
referenceVao = 0;
|
||||
}
|
||||
if (checkerTexture != 0) {
|
||||
GLStateManager.glDeleteTextures(checkerTexture);
|
||||
checkerTexture = 0;
|
||||
}
|
||||
ParticleQuadMesh.delete();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.testutil;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.reflect.Fields;
|
||||
import com.gtnewhorizon.gtnhlib.reflect.Fields.LookupType;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -99,6 +100,17 @@ public final class Reflect {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T allocate(Class<T> type) {
|
||||
try {
|
||||
final Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
f.setAccessible(true);
|
||||
return (T) ((Unsafe) f.get(null)).allocateInstance(type);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method resolve(Class<?> owner, String name, Class<?>[] signature) throws NoSuchMethodException {
|
||||
for (Class<?> c = owner; c != null; c = c.getSuperclass()) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: scripts/flyby-bench.sh <name> ffp|pack [flyby-capture.sh args...]
|
||||
# Env: FLYBY_SCENE, TIME_OF_DAY, QUICKPLAY_WORLD, TRACY_CAPTURE, SDLGPU, RENDER_DISTANCE.
|
||||
# Env: FLYBY_SCENE, FLYBY_ORIGIN, TIME_OF_DAY, QUICKPLAY_WORLD, TRACY_CAPTURE, SDLGPU, RENDER_DISTANCE.
|
||||
# Forces particles:0 for the run and restores it after exit; the game rewrites options.txt on quit.
|
||||
# A worktree needs tracy-client/src/main/resources/natives copied in.
|
||||
|
||||
@@ -40,6 +40,7 @@ fi
|
||||
|
||||
export FLYBY_SCENE="${FLYBY_SCENE:-scripts/flyby-scenes/entities-x8.txt}"
|
||||
export QUICKPLAY_WORLD="${QUICKPLAY_WORLD:-Flat World - No Mods}"
|
||||
if [ "$QUICKPLAY_WORLD" = "Flat World - No Mods" ]; then export FLYBY_ORIGIN="${FLYBY_ORIGIN-1509.5,-924.5,0}"; fi
|
||||
export TRACY_CAPTURE="${TRACY_CAPTURE:-$HOME/dev/mc/tracy/capture/build/tracy-capture}"
|
||||
export SDLGPU="${SDLGPU:-1}"
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
|
||||
# FLYBY_ORIGIN - x,z[,yaw] flight start. Unset or empty starts where the player is.
|
||||
# QUICKPLAY_WORLD - save to auto-load (default: latest).
|
||||
# FLYBY_SCENE - server command file run at flyby start.
|
||||
# SDLGPU=0|1 - force OpenGL (0) or SDL-GPU (1); unset uses the build default.
|
||||
@@ -43,6 +44,9 @@ fi
|
||||
if [ -n "${FLYBY_PACING:-}" ]; then
|
||||
BACKEND_ARGS+=(-Dangelica.flyby.pacing="$FLYBY_PACING")
|
||||
fi
|
||||
if [ -n "${FLYBY_ORIGIN:-}" ]; then
|
||||
BACKEND_ARGS+=(-Dangelica.flyby.origin="$FLYBY_ORIGIN")
|
||||
fi
|
||||
|
||||
SCENE_ABS=""
|
||||
SCENE_ARGS=()
|
||||
|
||||
@@ -8,12 +8,14 @@ import sys
|
||||
CSVEXPORT = os.environ.get("TRACY_CSVEXPORT", os.path.expanduser("~/dev/mc/tracy/csvexport/build/tracy-csvexport"))
|
||||
|
||||
PLOTS = [
|
||||
"gl.drawCalls", "gl.listPlaybacks", "gl.streamDraws", "entitiesRendered", "blockEntitiesRendered", "tesr.modelParts", "tesr.retainedDraws", "tesr.cacheMisses", "tesr.instancedDraws", "tesr.instancedInstances",
|
||||
"tesr.streamedInstances", "tesr.liveFallbacks", "tesr.bail.hurtFlash", "tesr.bail.foreignProgram",
|
||||
"tesr.bail.material", "tesr.bail.texture", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
|
||||
"gl.drawCalls", "gl.listPlaybacks", "gl.streamDraws", "entitiesRendered", "blockEntitiesRendered", "tesr.modelParts", "tesr.retainedDraws", "tesr.cacheMisses", "tesr.instancedDraws", "tesr.instancedInstances", "tesr.cubeInstances",
|
||||
"tesr.streamedInstances", "tesr.texMatrixRuns", "tesr.liveFallbacks", "tesr.bail.foreignProgram",
|
||||
"tesr.bail.material", "tesr.bail.texture", "entity.shadowQuads", "entity.shadowDraws", "items.instanced", "items.fallback", "items.glintInstanced",
|
||||
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
|
||||
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
|
||||
"sdl.acquireWaitUs",
|
||||
]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "tesrOpaque"]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass"]
|
||||
CLOCK = "gl.drawCalls"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# entities.txt at 8x density. Summons are relative to the player; start facing south (yaw 0).
|
||||
# entities.txt at 8x density. Summons are relative to the flight start; start facing south (yaw 0).
|
||||
|
||||
# gamerules
|
||||
difficulty 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Summons are relative to the player; start facing south (yaw 0).
|
||||
# Summons are relative to the flight start; start facing south (yaw 0).
|
||||
|
||||
# gamerules
|
||||
difficulty 1
|
||||
|
||||
@@ -1011,6 +1011,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void drawArrays(int mode, int first, int count) {
|
||||
if (count <= 0) return;
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
final ContextState st = s();
|
||||
@@ -3969,6 +3970,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (count <= 0 || primcount <= 0) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final ContextState st = s();
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
|
||||
@@ -109,6 +109,7 @@ public final class DrawDispatch {
|
||||
}
|
||||
|
||||
public void issueIndexedDraw(ContextState st, long rp, int ebo, int indexType, int count, int instances, int firstIndex, int baseVertex) {
|
||||
if (count <= 0 || instances <= 0) return;
|
||||
final EBOSplitScanner.EboSplit[] full;
|
||||
if (st.primitiveRestartEnabled) {
|
||||
full = resourceManager.getOrScanSplits(ebo, indexType, st.primitiveRestartSentinel);
|
||||
|
||||
+27
-11
@@ -69,8 +69,11 @@ public final class PipelineApplier {
|
||||
private final IntOpenHashSet dummyVboZeroWarned = new IntOpenHashSet();
|
||||
private final IntOpenHashSet staleFboWarned = new IntOpenHashSet();
|
||||
private final LongOpenHashSet bogusScissorWarned = new LongOpenHashSet();
|
||||
private final LongOpenHashSet clampedScissorWarned = new LongOpenHashSet();
|
||||
private final IntOpenHashSet pushUniformsNullCbWarned = new IntOpenHashSet();
|
||||
|
||||
private final int[] scissorRect = new int[4];
|
||||
|
||||
public PipelineApplier(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, PipelineStore pipelineStore, FBOClearTracker fboClearTracker, PersistentBufferSync persistentSync, SamplerBinder samplerBinder, StorageTextureBinder storageTextureBinder, StorageBufferBinder storageBufferBinder) {
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
@@ -461,27 +464,40 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.scissorDirty) {
|
||||
final long addr = st.cachedScissor.address();
|
||||
final int sx, sy, sw, sh;
|
||||
if (st.scissorEnabled) {
|
||||
sx = st.scissorX;
|
||||
sy = renderingToFbo ? st.scissorY : (fbo0Height(st) - st.scissorY - st.scissorH);
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, sx);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, sy);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, sw);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, sh);
|
||||
if ((sw <= 0 || sh <= 0) && bogusScissorWarned.add(Hashing.packHiLo(sw, sh))) {
|
||||
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
|
||||
}
|
||||
} else {
|
||||
final int rectY = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, (int) st.viewportX);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, rectY);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, (int) st.viewportW);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, (int) st.viewportH);
|
||||
sx = (int) st.viewportX;
|
||||
sy = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
|
||||
sw = (int) st.viewportW;
|
||||
sh = (int) st.viewportH;
|
||||
}
|
||||
|
||||
final boolean haveFboSize = renderingToFbo && fboState != null;
|
||||
final int targetW = haveFboSize ? fboState.width : frameManager.getFbo0Width();
|
||||
final int targetH = haveFboSize ? fboState.height : frameManager.getFbo0Height();
|
||||
final boolean nonEmpty = ScissorClamp.clamp(sx, sy, sw, sh, targetW, targetH, scissorRect);
|
||||
final int cx = scissorRect[ScissorClamp.X];
|
||||
final int cy = scissorRect[ScissorClamp.Y];
|
||||
final int cw = scissorRect[ScissorClamp.W];
|
||||
final int ch = scissorRect[ScissorClamp.H];
|
||||
if ((cx != sx || cy != sy || cw != sw || ch != sh) && clampedScissorWarned.add(Hashing.packHiLo(st.boundProgram, st.boundFboId))) {
|
||||
LOG.warn("applyPipelineAndState: scissor (x={} y={} w={} h={}) is outside the {}x{} target - clamped to (x={} y={} w={} h={}); boundProgram={} boundFbo={} scissorEn={}", sx, sy, sw, sh, targetW, targetH, cx, cy, cw, ch, st.boundProgram, st.boundFboId, st.scissorEnabled);
|
||||
}
|
||||
if (!nonEmpty) return false;
|
||||
|
||||
final long addr = st.cachedScissor.address();
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, cx);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, cy);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, cw);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, ch);
|
||||
SDL_SetGPUScissor(rp, st.cachedScissor);
|
||||
st.scissorDirty = false;
|
||||
}
|
||||
@@ -535,7 +551,7 @@ public final class PipelineApplier {
|
||||
final long elemAddr = st.vboBindingsAddr + (long) i * bindingSize;
|
||||
if (vboHandle != 0) {
|
||||
MemoryAccess.putAddress(elemAddr + SDL_GPUBufferBinding.BUFFER, vboHandle);
|
||||
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, (int) vao.bindingOffset[b]);
|
||||
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, PipelineCache.vertexOffsetBase(vao.bindingOffset[b]));
|
||||
} else {
|
||||
MemoryAccess.putAddress(elemAddr + SDL_GPUBufferBinding.BUFFER, dummyVBO);
|
||||
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, i * 16);
|
||||
|
||||
+12
-1
@@ -378,6 +378,7 @@ public final class PipelineCache {
|
||||
| (((long) vao.attribType[i] & 0xFFFFL) << 16)
|
||||
| ((vao.attribNormalized[i] ? 1L : 0L) << 32)
|
||||
| ((vao.attribIsInteger[i] ? 1L : 0L) << 33)
|
||||
| (((long) vertexOffsetMisalign(vao.bindingOffset[b]) & 3L) << 34)
|
||||
| (((long) b & 0xFF) << 40)
|
||||
| (((long) vao.attribStride[i] & 0xFFFFL) << 48);
|
||||
final long w1 =
|
||||
@@ -662,6 +663,16 @@ public final class PipelineCache {
|
||||
|
||||
private static final int GL_TYPE_BASE = GL11.GL_BYTE;
|
||||
static final int INVALID_FMT = -1;
|
||||
|
||||
public static final int VERTEX_BUFFER_OFFSET_ALIGN = 4;
|
||||
|
||||
public static int vertexOffsetBase(long bindingOffset) {
|
||||
return (int) (bindingOffset & ~(VERTEX_BUFFER_OFFSET_ALIGN - 1));
|
||||
}
|
||||
|
||||
public static int vertexOffsetMisalign(long bindingOffset) {
|
||||
return (int) (bindingOffset & (VERTEX_BUFFER_OFFSET_ALIGN - 1));
|
||||
}
|
||||
private static final int[][] VTX_FMT = buildVertexFormatTable();
|
||||
|
||||
private static int[] fmtSame(int s1, int s2, int s3, int s4) {
|
||||
@@ -839,7 +850,7 @@ public final class PipelineCache {
|
||||
final int boundStride = vao.bindingStride[b];
|
||||
final int stride = boundStride != 0 ? boundStride : (vao.attribStride[i] != 0 ? vao.attribStride[i] : vao.attribSize[i] * GLTypes.sizeBytes(vao.attribType[i]));
|
||||
final int divisor = vao.bindingDivisor[b];
|
||||
final int relOff = vao.attribRelativeOffset[i];
|
||||
final int relOff = vao.attribRelativeOffset[i] + vertexOffsetMisalign(vao.bindingOffset[b]);
|
||||
final int attribAlign = GLTypes.sizeBytes(vao.attribType[i]);
|
||||
if (attribAlign > 0 && (stride % attribAlign) != 0) {
|
||||
LOG.warn("[PipelineCache] unaligned vertex stride: location={} stride={} type=0x{} size={} -- attribAddress will not satisfy format alignment; fix producer", i, stride, Integer.toHexString(vao.attribType[i]), vao.attribSize[i]);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
public final class ScissorClamp {
|
||||
|
||||
public static final int X = 0;
|
||||
public static final int Y = 1;
|
||||
public static final int W = 2;
|
||||
public static final int H = 3;
|
||||
|
||||
private ScissorClamp() {}
|
||||
|
||||
public static boolean clamp(int x, int y, int w, int h, int targetW, int targetH, int[] out) {
|
||||
if (targetW <= 0 || targetH <= 0) {
|
||||
out[X] = x;
|
||||
out[Y] = y;
|
||||
out[W] = w;
|
||||
out[H] = h;
|
||||
return w > 0 && h > 0;
|
||||
}
|
||||
final int x0 = Math.max(x, 0);
|
||||
final int y0 = Math.max(y, 0);
|
||||
final int x1 = (int) Math.min((long) x + w, targetW);
|
||||
final int y1 = (int) Math.min((long) y + h, targetH);
|
||||
out[X] = x0;
|
||||
out[Y] = y0;
|
||||
out[W] = Math.max(x1 - x0, 0);
|
||||
out[H] = Math.max(y1 - y0, 0);
|
||||
return out[W] > 0 && out[H] > 0;
|
||||
}
|
||||
}
|
||||
+2
@@ -31,6 +31,8 @@ public final class PerFrameBlockInjector {
|
||||
private PerFrameBlockInjector() {}
|
||||
|
||||
public static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
if (normalize(perFrame) == null && normalize(perPass) == null) return source;
|
||||
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(source);
|
||||
|
||||
@@ -26,6 +26,8 @@ public final class SamplerStripper {
|
||||
private SamplerStripper() {}
|
||||
|
||||
public static String stripUnused(String source) {
|
||||
if (!source.contains("sampler")) return source;
|
||||
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(source);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
@@ -88,8 +89,14 @@ public final class ShaderManager {
|
||||
private static final int PREWARM_CACHE_MAX = 256;
|
||||
private static final Object2ObjectLinkedOpenHashMap<PrewarmKey, PrewarmEntry> PREWARM_CACHE = new Object2ObjectLinkedOpenHashMap<>();
|
||||
|
||||
private record TransformKey(String source, int glShaderType, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {}
|
||||
private record TransformEntry(String source, Set<String> boolUniforms) {}
|
||||
private static final int TRANSFORM_CACHE_MAX = 256;
|
||||
private static final Object2ObjectLinkedOpenHashMap<TransformKey, TransformEntry> TRANSFORM_CACHE = new Object2ObjectLinkedOpenHashMap<>();
|
||||
|
||||
public static void clearPrewarmCache() {
|
||||
synchronized (PREWARM_CACHE) { PREWARM_CACHE.clear(); }
|
||||
synchronized (TRANSFORM_CACHE) { TRANSFORM_CACHE.clear(); }
|
||||
}
|
||||
|
||||
private static PrewarmHit lookupPrewarm(String src, int glShaderType) {
|
||||
@@ -133,24 +140,35 @@ public final class ShaderManager {
|
||||
return;
|
||||
}
|
||||
|
||||
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, obj.type, "shader" + shader, true);
|
||||
obj.boolUniforms = pre != null ? pre.boolUniforms() : Set.of();
|
||||
String src = pre != null ? pre.rewrittenSource() : raw;
|
||||
if (obj.isVertex()) {
|
||||
src = ClipZRemap.injectGLToVulkanClipZ(src);
|
||||
final TransformKey transformKey = new TransformKey(raw, obj.type, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
TransformEntry transformed;
|
||||
synchronized (TRANSFORM_CACHE) {
|
||||
transformed = TRANSFORM_CACHE.getAndMoveToFirst(transformKey);
|
||||
}
|
||||
src = SamplerStripper.stripUnused(src);
|
||||
if (obj.type == GL20.GL_VERTEX_SHADER || obj.type == GL20.GL_FRAGMENT_SHADER) {
|
||||
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
if (transformed == null) {
|
||||
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, obj.type, "shader" + shader, true);
|
||||
String src = pre != null ? pre.rewrittenSource() : raw;
|
||||
if (obj.isVertex()) {
|
||||
src = ClipZRemap.injectGLToVulkanClipZ(src);
|
||||
}
|
||||
src = SamplerStripper.stripUnused(src);
|
||||
if (obj.type == GL20.GL_VERTEX_SHADER || obj.type == GL20.GL_FRAGMENT_SHADER) {
|
||||
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
}
|
||||
transformed = new TransformEntry(src, pre != null ? pre.boolUniforms() : Set.of());
|
||||
synchronized (TRANSFORM_CACHE) {
|
||||
TRANSFORM_CACHE.putAndMoveToFirst(transformKey, transformed);
|
||||
while (TRANSFORM_CACHE.size() > TRANSFORM_CACHE_MAX) TRANSFORM_CACHE.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
obj.source = src;
|
||||
obj.boolUniforms = transformed.boolUniforms();
|
||||
obj.source = transformed.source();
|
||||
|
||||
final ShaderWorkSubmitter submitter = GLSMHooks.shaderWorkSubmitter;
|
||||
if (submitter != null) {
|
||||
final int shaderKind = shaderKindFor(obj);
|
||||
final int glType = obj.type;
|
||||
final String finalSrc = src;
|
||||
final String finalSrc = obj.source;
|
||||
obj.spirvFuture = submitter.submit(() -> {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(finalSrc, shaderKind, "shader" + shader, SpirvCompiler.Options.vulkanForced460Core());
|
||||
if (r.spirv() != null && (glType == GL20.GL_VERTEX_SHADER || glType == GL20.GL_FRAGMENT_SHADER)) {
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlDegenerateDrawTest {
|
||||
|
||||
private static final int RED = 0xFFFF0000;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
@Test
|
||||
void zeroCountDrawArraysLeavesTheFollowingDrawIntact() {
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 0);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), RED, "after a zero-count drawArrays");
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeCountDrawArraysLeavesTheFollowingDrawIntact() {
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, -3);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), RED, "after a negative-count drawArrays");
|
||||
}
|
||||
}
|
||||
+34
@@ -62,6 +62,13 @@ class GlsmSdlFbo0ViewportTest {
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static void bindFbo0WithFullViewport() {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, FBO0_W, FBO0_H);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static int createCopyTarget() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
@@ -88,6 +95,33 @@ class GlsmSdlFbo0ViewportTest {
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorLargerThanFbo0StillCoversTheWholeTarget() {
|
||||
bindFbo0WithFullViewport();
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
try {
|
||||
GLStateManager.glScissor(0, 0, FBO0_W + 16, FBO0_H + 16);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W, FBO0_H), RED, "inside an oversize scissor");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorRunningPastTheRightEdgeClipsToTheTarget() {
|
||||
bindFbo0WithFullViewport();
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
try {
|
||||
GLStateManager.glScissor(FBO0_W - 8, 0, 32, FBO0_H);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(FBO0_W - 8, 0, 8, FBO0_H), RED, "inside the clipped scissor");
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W - 8, FBO0_H), BLUE, "left of the scissor");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DFromAPartialViewportKeepsGlOrientation() {
|
||||
bindFbo0WithSmallViewport();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleParityFixture;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -40,6 +45,14 @@ class GlsmSdlFfpTest {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanupInstancing() {
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
CubeParityFixture.deleteResources();
|
||||
ParticleParityFixture.deleteResources();
|
||||
FfpFixture.IntegerInstances.delete();
|
||||
}
|
||||
|
||||
private static int createLightmapTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
@@ -146,9 +159,7 @@ class GlsmSdlFfpTest {
|
||||
GLStateManager.glCallList(list);
|
||||
assertTexelEverywhere("two units, glCallList");
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
|
||||
final IntBuffer lists = BufferUtils.createIntBuffer(1);
|
||||
lists.put(0, list);
|
||||
@@ -172,6 +183,54 @@ class GlsmSdlFfpTest {
|
||||
assertTrue(red < ((TEXEL >> 16) & 0xFF), () -> "lit quad is not attenuated: " + describe(center));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unitCubeMatchesBakedQuadsWithLightmap() {
|
||||
CubeParityFixture.setupScene(true, false);
|
||||
|
||||
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
|
||||
|
||||
CubeParityFixture.drawReference(specs, true);
|
||||
final int[] reference = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
CubeParityFixture.drawInstanced(specs);
|
||||
final int[] instanced = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
CubeParityFixture.assertPixelParity(reference, instanced, SIZE, 0xFF000000, GlsmSdlHeadlessRig::describe);
|
||||
}
|
||||
|
||||
@Test
|
||||
void particleFirstPersonRotationMatchesReference() {
|
||||
final ParticleParityFixture.Rotation rot = ParticleParityFixture.Rotation.of(35f, -12f, false);
|
||||
ParticleParityFixture.setupScene(false);
|
||||
|
||||
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
|
||||
|
||||
ParticleParityFixture.drawReference(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
|
||||
final int[] reference = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
ParticleParityFixture.drawInstanced(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
|
||||
final int[] instanced = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
ParticleParityFixture.assertPixelParity(reference, instanced, SIZE, 0xFF000000, GlsmSdlHeadlessRig::describe);
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityAttribSelectsColorPerInstance() {
|
||||
FfpFixture.IntegerInstances.build(0.6f);
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
|
||||
FfpFixture.IntegerInstances.draw();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 13, SIZE / 2, 0xFFFF0000, "entity -1 must render red");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 32, SIZE / 2, 0xFF00FF00, "entity 7 must render green");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 42 must render blue");
|
||||
}
|
||||
|
||||
private static void enableGuiStandardItemLighting() {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glRotatef(-30.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
@@ -122,6 +122,12 @@ public final class GlsmSdlHeadlessRig {
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
public static void beginFrameAndClear() {
|
||||
beginFrame();
|
||||
bindTarget();
|
||||
clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static void beginFrameAndReset() {
|
||||
beginFrame();
|
||||
resetState();
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlUnalignedAttribTest {
|
||||
|
||||
private static final int STRIDE = 24;
|
||||
private static final int FACTOR = 204;
|
||||
private static final int DECOY = 64;
|
||||
|
||||
private static final String VERT = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
layout(location = 5) in float a_Factor;
|
||||
out float v_Factor;
|
||||
void main() {
|
||||
gl_Position = vec4(a_Position, 1.0);
|
||||
v_Factor = a_Factor;
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String FRAG = """
|
||||
#version 330 core
|
||||
in float v_Factor;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(v_Factor, v_Factor, v_Factor, 1.0);
|
||||
}
|
||||
""";
|
||||
|
||||
private static int program;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
private static int buildProgram() {
|
||||
if (program != 0) return program;
|
||||
final int vs = GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER);
|
||||
GLStateManager.glShaderSource(vs, VERT);
|
||||
GLStateManager.glCompileShader(vs);
|
||||
assertTrue(GLStateManager.glGetShaderi(vs, GL20.GL_COMPILE_STATUS) != 0,
|
||||
() -> "vertex shader: " + GLStateManager.glGetShaderInfoLog(vs));
|
||||
|
||||
final int fs = GLStateManager.glCreateShader(GL20.GL_FRAGMENT_SHADER);
|
||||
GLStateManager.glShaderSource(fs, FRAG);
|
||||
GLStateManager.glCompileShader(fs);
|
||||
assertTrue(GLStateManager.glGetShaderi(fs, GL20.GL_COMPILE_STATUS) != 0,
|
||||
() -> "fragment shader: " + GLStateManager.glGetShaderInfoLog(fs));
|
||||
|
||||
final int p = GLStateManager.glCreateProgram();
|
||||
GLStateManager.glAttachShader(p, vs);
|
||||
GLStateManager.glAttachShader(p, fs);
|
||||
GLStateManager.glLinkProgram(p);
|
||||
assertTrue(GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS) != 0, "program did not link");
|
||||
program = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
private static int redChannelForFactorAt(int factorOffset, int decoyOffset) {
|
||||
final int p = buildProgram();
|
||||
final int vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
final int vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(3 * STRIDE);
|
||||
try {
|
||||
for (int i = 0; i < 3 * STRIDE; i++) data.put(i, (byte) 0);
|
||||
final float[][] corners = {{-1.0f, -1.0f}, {3.0f, -1.0f}, {-1.0f, 3.0f}};
|
||||
for (int v = 0; v < 3; v++) {
|
||||
final int base = v * STRIDE;
|
||||
data.putFloat(base, corners[v][0]);
|
||||
data.putFloat(base + 4, corners[v][1]);
|
||||
data.putFloat(base + 8, 0.0f);
|
||||
if (decoyOffset >= 0) data.put(base + decoyOffset, (byte) DECOY);
|
||||
data.put(base + factorOffset, (byte) FACTOR);
|
||||
}
|
||||
data.position(0).limit(3 * STRIDE);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
} finally {
|
||||
MemoryUtil.memFree(data);
|
||||
}
|
||||
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
|
||||
GLStateManager.glEnableVertexAttribArray(5);
|
||||
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_UNSIGNED_BYTE, true, STRIDE, factorOffset);
|
||||
|
||||
GLStateManager.glUseProgram(p);
|
||||
try {
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
return (pixels[0] >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteAttribAtAnAlignedOffsetReadsItsOwnByte() {
|
||||
assertEquals(FACTOR, redChannelForFactorAt(20, -1), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteAttribAtAnUnalignedOffsetReadsItsOwnByte() {
|
||||
assertEquals(FACTOR, redChannelForFactorAt(23, 20), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteAttribAtEveryMisalignmentReadsItsOwnByte() {
|
||||
for (int offset = 20; offset <= 23; offset++) {
|
||||
final int decoy = offset == 20 ? -1 : 20;
|
||||
final int got = redChannelForFactorAt(offset, decoy);
|
||||
assertEquals(FACTOR, got, 1, "factor byte at offset " + offset + " read back as " + got);
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ScissorClampTest {
|
||||
|
||||
private final int[] out = new int[4];
|
||||
|
||||
@Test
|
||||
void rectInsideTheTargetIsUnchanged() {
|
||||
assertTrue(ScissorClamp.clamp(4, 8, 16, 32, 64, 96, out));
|
||||
assertArrayEquals(new int[]{4, 8, 16, 32}, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overhangPastTheRightAndBottomEdgesShrinks() {
|
||||
assertTrue(ScissorClamp.clamp(0, 0, 1710, 960, 1708, 960, out));
|
||||
assertArrayEquals(new int[]{0, 0, 1708, 960}, out);
|
||||
|
||||
assertTrue(ScissorClamp.clamp(60, 90, 16, 32, 64, 96, out));
|
||||
assertArrayEquals(new int[]{60, 90, 4, 6}, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeOriginShrinksTheRectInsteadOfShiftingIt() {
|
||||
assertTrue(ScissorClamp.clamp(-6, -10, 16, 32, 64, 96, out));
|
||||
assertArrayEquals(new int[]{0, 0, 10, 22}, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rectStraddlingBothEdgesIsClampedToTheWholeTarget() {
|
||||
assertTrue(ScissorClamp.clamp(-8, -8, 128, 128, 64, 96, out));
|
||||
assertArrayEquals(new int[]{0, 0, 64, 96}, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rectFullyOutsideTheTargetIsEmpty() {
|
||||
assertFalse(ScissorClamp.clamp(64, 0, 16, 32, 64, 96, out));
|
||||
assertArrayEquals(new int[]{64, 0, 0, 32}, out);
|
||||
|
||||
assertFalse(ScissorClamp.clamp(-40, 0, 16, 32, 64, 96, out));
|
||||
assertArrayEquals(new int[]{0, 0, 0, 32}, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void degenerateSourceRectIsEmpty() {
|
||||
assertFalse(ScissorClamp.clamp(0, 0, 0, 32, 64, 96, out));
|
||||
assertFalse(ScissorClamp.clamp(0, 0, 16, -4, 64, 96, out));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownTargetSizeLeavesTheRectAlone() {
|
||||
assertTrue(ScissorClamp.clamp(-6, 4, 1710, 32, 0, 0, out));
|
||||
assertArrayEquals(new int[]{-6, 4, 1710, 32}, out);
|
||||
|
||||
assertFalse(ScissorClamp.clamp(0, 0, 0, 32, 0, 96, out));
|
||||
}
|
||||
|
||||
@Test
|
||||
void largeRectDoesNotOverflow() {
|
||||
assertTrue(ScissorClamp.clamp(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, 64, 96, out));
|
||||
assertArrayEquals(new int[]{0, 0, 64, 96}, out);
|
||||
}
|
||||
}
|
||||
+8
@@ -14,6 +14,7 @@ import java.util.List;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.system.MemoryUtil.memFree;
|
||||
|
||||
@@ -80,6 +81,13 @@ class PerFrameBlockInjectorTest {
|
||||
assertEquals(src, PerFrameBlockInjector.inject(src, new PerFrameUniformBlock(List.of()), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noBlocksReturnsTheSourceInstance() {
|
||||
final String src = "#version 330 core\nuniform float frameTimeCounter;\nvoid main() {}\n";
|
||||
assertSame(src, PerFrameBlockInjector.inject(src, null, null));
|
||||
assertSame(src, PerFrameBlockInjector.inject(src, new PerFrameUniformBlock(List.of()), new PerFrameUniformBlock(List.of())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void memberShadowedByAPackDeclarationIsRenamedNotRedeclared() {
|
||||
final String out = PerFrameBlockInjector.inject("""
|
||||
|
||||
+2
-15
@@ -23,22 +23,9 @@ class PrewarmSingleParseEquivalenceTest {
|
||||
GLSMHooks.perPassUniformBlock = null;
|
||||
}
|
||||
|
||||
private static String legacy(String source, int glShaderType) {
|
||||
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, "test", true);
|
||||
String src = pre != null ? pre.rewrittenSource() : source;
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
src = ClipZRemap.injectGLToVulkanClipZ(src);
|
||||
}
|
||||
src = SamplerStripper.stripUnused(src);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
private static void assertEquivalent(String label, String source, int glShaderType) {
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
final String legacyOut = legacy(source, glShaderType);
|
||||
final String legacyOut = ShaderTransformChain.run(source, glShaderType);
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
final String parseOnceOut = ShaderManager.applyPrewarmTransforms(source, glShaderType);
|
||||
assertEquals(legacyOut, parseOnceOut, "single-parse vs chained pipeline diverged for " + label);
|
||||
@@ -46,7 +33,7 @@ class PrewarmSingleParseEquivalenceTest {
|
||||
|
||||
private static void assertEquivalentIgnoringWhitespace(String label, String source, int glShaderType) {
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
final String legacyOut = legacy(source, glShaderType);
|
||||
final String legacyOut = ShaderTransformChain.run(source, glShaderType);
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
final String parseOnceOut = ShaderManager.applyPrewarmTransforms(source, glShaderType);
|
||||
assertEquals(legacyOut.replaceAll("\\s+", " "), parseOnceOut.replaceAll("\\s+", " "), "single-parse vs chained pipeline diverged beyond whitespace for " + label);
|
||||
|
||||
+11
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SdlSamplerStripperPreprocessorTest {
|
||||
@@ -104,6 +105,16 @@ class SdlSamplerStripperPreprocessorTest {
|
||||
assertFalse(out.contains("uniform sampler2D u_S"), "a comment mention must not keep the decl; output:\n" + out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceWithoutSamplersReturnsTheSourceInstance() {
|
||||
final String src = "#version 460 core\n"
|
||||
+ "in vec3 v_Position;\n"
|
||||
+ "void main() {\n"
|
||||
+ " gl_Position = vec4(v_Position, 1.0);\n"
|
||||
+ "}\n";
|
||||
assertSame(src, SamplerStripper.stripUnused(src));
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerOnlyInMacroBody_isPreserved() {
|
||||
final String src = "#version 460 core\n"
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ShaderSourceTransformCacheTest {
|
||||
|
||||
private static final String FRAG = """
|
||||
#version 460 core
|
||||
uniform sampler2D u_Unused;
|
||||
uniform sampler2D u_Used;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = texture(u_Used, vec2(0.5));
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String VERT = """
|
||||
#version 460 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
in vec2 a_TexCoord;
|
||||
out vec2 v_TexCoord;
|
||||
void main() {
|
||||
v_TexCoord = a_TexCoord;
|
||||
gl_Position = vec4(a_Position, 1.0);
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String PFB_FRAG = """
|
||||
#version 460 core
|
||||
uniform float frameTimeCounter;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(frameTimeCounter);
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLIP_Z = "gl_Position.z = gl_Position.z * 0.5 + gl_Position.w * 0.5;";
|
||||
|
||||
@BeforeEach
|
||||
void reset() {
|
||||
ShaderManager.clearPrewarmCache();
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearBlocks() {
|
||||
ShaderManager.clearPrewarmCache();
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
GLSMHooks.perFrameUniformBlock = null;
|
||||
GLSMHooks.perPassUniformBlock = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedSourceReusesTheChainedTransform() {
|
||||
final String expected = ShaderTransformChain.run(FRAG, GL20.GL_FRAGMENT_SHADER);
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
|
||||
final ShaderManager sm = new ShaderManager(null);
|
||||
final int a = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
final int b = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(a, FRAG);
|
||||
sm.shaderSource(b, FRAG);
|
||||
|
||||
assertEquals(expected, sm.getShaderSource(a));
|
||||
assertSame(sm.getShaderSource(a), sm.getShaderSource(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
void vertexSourceMatchesTheChainAndCarriesClipZ() {
|
||||
final String expected = ShaderTransformChain.run(VERT, GL20.GL_VERTEX_SHADER);
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
|
||||
final ShaderManager sm = new ShaderManager(null);
|
||||
final int v = sm.createShader(GL20.GL_VERTEX_SHADER);
|
||||
sm.shaderSource(v, VERT);
|
||||
|
||||
assertEquals(expected, sm.getShaderSource(v));
|
||||
assertTrue(sm.getShaderSource(v).contains(CLIP_Z));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blockChangeMissesTheCache() {
|
||||
GLSMHooks.perFrameUniformBlock = new PerFrameUniformBlock(List.of(new Member("frameTimeCounter", UniformType.FLOAT)));
|
||||
|
||||
final ShaderManager sm = new ShaderManager(null);
|
||||
final int withBlock = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(withBlock, PFB_FRAG);
|
||||
assertTrue(sm.getShaderSource(withBlock).contains("AngelicaPerFrame"));
|
||||
|
||||
GLSMHooks.perFrameUniformBlock = null;
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
final String expected = ShaderTransformChain.run(PFB_FRAG, GL20.GL_FRAGMENT_SHADER);
|
||||
GlslVulkanPreprocess.clearCache();
|
||||
|
||||
final int withoutBlock = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(withoutBlock, PFB_FRAG);
|
||||
|
||||
assertEquals(expected, sm.getShaderSource(withoutBlock));
|
||||
assertFalse(sm.getShaderSource(withoutBlock).contains("AngelicaPerFrame"));
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
final class ShaderTransformChain {
|
||||
|
||||
private ShaderTransformChain() {}
|
||||
|
||||
static String run(String source, int glShaderType) {
|
||||
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, "test", true);
|
||||
String src = pre != null ? pre.rewrittenSource() : source;
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
src = ClipZRemap.injectGLToVulkanClipZ(src);
|
||||
}
|
||||
src = SamplerStripper.stripUnused(src);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public final class TesrMaterial {
|
||||
|
||||
private static final ConcurrentHashMap<TesrMaterial, TesrMaterial> INTERNED = new ConcurrentHashMap<>();
|
||||
|
||||
public static final TesrMaterial CURRENT_STATE = intern(new TesrMaterial(false, 0, 0, 0, 0, false, 0, 0, false, false, false, false, false, 0, false, Transparency.OPAQUE, SpecialRender.NONE, null));
|
||||
public static final TesrMaterial CURRENT_STATE = intern(new TesrMaterial(false, 0, 0, 0, 0, false, 0, 0, false, false, false, false, false, 0, false, false, Transparency.OPAQUE, SpecialRender.NONE, null));
|
||||
|
||||
private static TesrMaterial intern(TesrMaterial material) {
|
||||
final TesrMaterial existing = INTERNED.putIfAbsent(material, material);
|
||||
@@ -39,6 +39,7 @@ public final class TesrMaterial {
|
||||
private final boolean isStream;
|
||||
private final float cutoutAlpha;
|
||||
private final boolean isDepthEqual;
|
||||
private final boolean isUnfilteredAtlas;
|
||||
private final Transparency transparency;
|
||||
private final SpecialRender special;
|
||||
private final TesrShader shader;
|
||||
@@ -59,6 +60,7 @@ public final class TesrMaterial {
|
||||
private boolean stream;
|
||||
private float cutoutAlpha;
|
||||
private boolean depthEqual;
|
||||
private boolean unfilteredAtlas;
|
||||
private Transparency transparency = Transparency.OPAQUE;
|
||||
private SpecialRender special = SpecialRender.NONE;
|
||||
private TesrShader shader;
|
||||
@@ -119,6 +121,11 @@ public final class TesrMaterial {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder unfilteredAtlas() {
|
||||
this.unfilteredAtlas = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Standard alpha blending (glass, liquids). SRC_ALPHA, ONE_MINUS_SRC_ALPHA */
|
||||
public Builder translucent() {
|
||||
this.transparency = Transparency.TRANSLUCENT;
|
||||
@@ -158,7 +165,7 @@ public final class TesrMaterial {
|
||||
if (special != SpecialRender.NONE && shader != null) {
|
||||
throw new IllegalStateException("special() and shader() are mutually exclusive");
|
||||
}
|
||||
return intern(new TesrMaterial(hasColor, red, green, blue, alpha, hasLightmap, lightmapX, lightmapY, noCull, unlit, noDepthWrite, depthOnly, stream, cutoutAlpha, depthEqual, transparency, special, shader));
|
||||
return intern(new TesrMaterial(hasColor, red, green, blue, alpha, hasLightmap, lightmapX, lightmapY, noCull, unlit, noDepthWrite, depthOnly, stream, cutoutAlpha, depthEqual, unfilteredAtlas, transparency, special, shader));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/** TESR mesh cache/batch API (Render thread only) */
|
||||
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "1")
|
||||
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "2")
|
||||
package com.gtnewhorizons.angelica.api.tesr;
|
||||
|
||||
import cpw.mods.fml.common.API;
|
||||
+3
-13
@@ -5,7 +5,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.ITessellatorInstance;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
@@ -28,8 +27,7 @@ class DeferredBatchTessellator extends DirectTessellator {
|
||||
record DrawRange(long stateKey, int byteOffset, int byteLength, int vertexCount, int drawMode, int flags) {}
|
||||
|
||||
private final List<DrawRange> ranges = new ArrayList<>();
|
||||
private final Matrix4f defaultModelview = new Matrix4f();
|
||||
private final Matrix4f defaultModelviewInverse = new Matrix4f();
|
||||
private final ModelViewDelta modelViewDelta = new ModelViewDelta();
|
||||
private final Matrix4f deltaMatrix = new Matrix4f(); // reusable scratch
|
||||
private final Vector3f scratch = new Vector3f();
|
||||
private Matrix4fc currentTransform;
|
||||
@@ -68,22 +66,14 @@ class DeferredBatchTessellator extends DirectTessellator {
|
||||
|
||||
/** Snapshot the current modelview as the "default" for this bracket. */
|
||||
void snapshotDefaultModelview() {
|
||||
defaultModelview.set(GLStateManager.getModelViewMatrix());
|
||||
defaultModelview.invert(defaultModelviewInverse);
|
||||
modelViewDelta.snapshot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int interceptDraw(Tessellator tess) {
|
||||
inIntercept = true;
|
||||
try {
|
||||
// Compute delta: if current MV differs from default, pre-transform vertices
|
||||
final Matrix4f currentMV = GLStateManager.getModelViewMatrix();
|
||||
if (!currentMV.equals(defaultModelview)) {
|
||||
defaultModelviewInverse.mul(currentMV, deltaMatrix);
|
||||
this.currentTransform = deltaMatrix;
|
||||
} else {
|
||||
this.currentTransform = null;
|
||||
}
|
||||
this.currentTransform = modelViewDelta.deltaOrNull(deltaMatrix);
|
||||
|
||||
final int byteOffsetBefore = bufferLimit();
|
||||
final int vertCount = tess.vertexCount;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public final class ModelViewDelta {
|
||||
|
||||
private final Matrix4f base = new Matrix4f();
|
||||
private final Matrix4f baseInverse = new Matrix4f();
|
||||
|
||||
public void snapshot() {
|
||||
base.set(GLStateManager.getModelViewMatrix());
|
||||
base.invert(baseInverse);
|
||||
}
|
||||
|
||||
public Matrix4fc deltaOrNull(Matrix4f scratch) {
|
||||
final Matrix4f current = GLStateManager.getModelViewMatrix();
|
||||
if (current.equals(base)) return null;
|
||||
return baseInverse.mul(current, scratch);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import net.coderbot.batchedentityrendering.impl.BatchVertexFormats;
|
||||
import net.coderbot.batchedentityrendering.impl.BlendingStateHolder;
|
||||
import net.coderbot.batchedentityrendering.impl.TransparencyType;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.layer.PassOverride;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
@@ -69,9 +70,9 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return TRANSLUCENT;
|
||||
}
|
||||
|
||||
private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
|
||||
final MultiPhaseParameters.Builder b = MultiPhaseParameters.builder();
|
||||
b.texture(texture != null ? new RenderPhase.Texture(texture, false, false) : NO_TEXTURE);
|
||||
b.texture(texture != null ? new RenderPhase.Texture(texture, false, false, material.isUnfilteredAtlas()) : NO_TEXTURE);
|
||||
if (material.isNoCull()) {
|
||||
b.cull(DISABLE_CULLING);
|
||||
}
|
||||
@@ -95,7 +96,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
boolean shaderPhaseTaken = true;
|
||||
switch (material.special()) {
|
||||
case GLINT -> b.shader(SPECIAL_GLINT);
|
||||
case GLINT -> b.shader(glintShader(glintSlot));
|
||||
case BEACON_BEAM -> b.shader(SPECIAL_BEACON_BEAM);
|
||||
case NONE -> {
|
||||
final TesrShader shader = material.shader();
|
||||
@@ -113,19 +114,35 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return b;
|
||||
}
|
||||
|
||||
private static final RenderPhase.Shader SPECIAL_GLINT = new RenderPhase.Shader("angelica_special_glint",
|
||||
() -> GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT),
|
||||
GbufferPrograms::teardownSpecialRenderCondition);
|
||||
private static final RenderPhase.Shader[] GLINT_SHADERS = new RenderPhase.Shader[ShaderGlint.TINT_SLOTS + 1];
|
||||
|
||||
private static RenderPhase.Shader glintShader(int slot) {
|
||||
RenderPhase.Shader shader = GLINT_SHADERS[slot + 1];
|
||||
if (shader == null) {
|
||||
shader = new RenderPhase.Shader("angelica_special_glint_" + slot,
|
||||
() -> {
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
|
||||
ShaderGlint.bindTintedGlint(slot);
|
||||
},
|
||||
GbufferPrograms::teardownSpecialRenderCondition);
|
||||
GLINT_SHADERS[slot + 1] = shader;
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
private static final RenderPhase.Shader SPECIAL_BEACON_BEAM = new RenderPhase.Shader("angelica_special_beacon_beam",
|
||||
() -> GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.BEACON_BEAM),
|
||||
GbufferPrograms::teardownSpecialRenderCondition);
|
||||
|
||||
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material) {
|
||||
return tesr(texture, material, PassOverride.NONE, 0.0f, 0.0f);
|
||||
return tesr(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT);
|
||||
}
|
||||
|
||||
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material, pass, offsetFactor, offsetUnits).shadeModel(SMOOTH_SHADE_MODEL);
|
||||
return tesr(texture, material, pass, offsetFactor, offsetUnits, ShaderGlint.NO_TINT);
|
||||
}
|
||||
|
||||
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
|
||||
final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material, pass, offsetFactor, offsetUnits, glintSlot).shadeModel(SMOOTH_SHADE_MODEL);
|
||||
final boolean sortsTranslucent = material.transparency() != TesrMaterial.Transparency.OPAQUE;
|
||||
return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT) + pass.nameSuffix()
|
||||
+ (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits),
|
||||
@@ -135,7 +152,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material) {
|
||||
return of("angelica_tesr_nopass_" + material.transparency().name().toLowerCase(Locale.ROOT),
|
||||
BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, false,
|
||||
tesrMaterialPhases(texture, material, PassOverride.NONE, 0.0f, 0.0f).build(false));
|
||||
tesrMaterialPhases(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT).build(false));
|
||||
}
|
||||
|
||||
public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.coderbot.batchedentityrendering.impl.TransparencyType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@@ -406,18 +407,30 @@ public abstract class RenderPhase {
|
||||
private final Optional<ResourceLocation> id;
|
||||
protected boolean bilinear;
|
||||
protected boolean mipmap;
|
||||
protected boolean unfilteredAtlas;
|
||||
|
||||
public Texture(ResourceLocation id, boolean bilinear, boolean mipmap) {
|
||||
this(id, bilinear, mipmap, false);
|
||||
}
|
||||
|
||||
public Texture(ResourceLocation id, boolean bilinear, boolean mipmap, boolean unfilteredAtlas) {
|
||||
super("texture", () -> {
|
||||
GLStateManager.enableTexture();
|
||||
TextureManager lv = Minecraft.getMinecraft().getTextureManager();
|
||||
lv.bindTexture(id);
|
||||
//GLStateManager.setFilter(bilinear, mipmap); // breaks textures. TODO find out why
|
||||
if (unfilteredAtlas) {
|
||||
TextureUtil.func_152777_a(false, false, 1.0F);
|
||||
}
|
||||
}, () -> {
|
||||
if (unfilteredAtlas) {
|
||||
TextureUtil.func_147945_b();
|
||||
}
|
||||
});
|
||||
this.id = Optional.of(id);
|
||||
this.bilinear = bilinear;
|
||||
this.mipmap = mipmap;
|
||||
this.unfilteredAtlas = unfilteredAtlas;
|
||||
}
|
||||
|
||||
public Texture() {
|
||||
@@ -425,6 +438,7 @@ public abstract class RenderPhase {
|
||||
this.id = Optional.empty();
|
||||
this.bilinear = false;
|
||||
this.mipmap = false;
|
||||
this.unfilteredAtlas = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -433,7 +447,7 @@ public abstract class RenderPhase {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
Texture lv = (Texture)object;
|
||||
return this.id.equals(lv.id) && this.bilinear == lv.bilinear && this.mipmap == lv.mipmap;
|
||||
return this.id.equals(lv.id) && this.bilinear == lv.bilinear && this.mipmap == lv.mipmap && this.unfilteredAtlas == lv.unfilteredAtlas;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -446,7 +460,7 @@ public abstract class RenderPhase {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name + '[' + this.id + "(blur=" + this.bilinear + ", mipmap=" + this.mipmap + ")]";
|
||||
return this.name + '[' + this.id + "(blur=" + this.bilinear + ", mipmap=" + this.mipmap + ", unfilteredAtlas=" + this.unfilteredAtlas + ")]";
|
||||
}
|
||||
|
||||
protected Optional<ResourceLocation> getId() {
|
||||
|
||||
@@ -127,11 +127,16 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableTESRChestCache;
|
||||
|
||||
@Config.Comment("Batch and instance living-entity model parts on FFP-managed passes")
|
||||
@Config.Comment("Batch and instance entity model parts, items, and shadows on FFP-managed passes")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableEntityBatching;
|
||||
|
||||
@Config.Comment("Draw cuboid model parts from a shared unit cube (requires entity batching)")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableCubeInstancing;
|
||||
|
||||
@Config.Comment("Skip the end-of-frame shader buffer copy by ping-ponging buffers. Disable if a shader pack misrenders.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
@@ -251,7 +256,7 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeInWorldItemRendering;
|
||||
|
||||
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
|
||||
@Config.Comment("Upper limit for the amount of cached item meshes (VBOs and batched item templates) for optimized item rendering. Higher number can potentially use more memory and VRAM.")
|
||||
@Config.DefaultInt(512)
|
||||
@Config.RangeInt(min = 256, max = 1024)
|
||||
public static int itemRendererCacheSize;
|
||||
@@ -463,6 +468,10 @@ public class AngelicaConfig {
|
||||
GpuCulling.setMode(gpuCullingMode == null ? GpuCullingMode.CPU_ONLY : gpuCullingMode);
|
||||
}
|
||||
|
||||
public static boolean cubeInstancingEnabled() {
|
||||
return enableEntityBatching && enableCubeInstancing;
|
||||
}
|
||||
|
||||
public static GLProfile getEffectiveGlProfile() {
|
||||
final String sys = SystemProperties.GL_PROFILE.trim().toUpperCase();
|
||||
if ("GLES".equals(sys)) return GLProfile.ES;
|
||||
|
||||
@@ -15,9 +15,11 @@ final class FlybyCommandSender implements ICommandSender {
|
||||
private static final String NAME = "Flyby";
|
||||
|
||||
private final EntityPlayerMP player;
|
||||
private final ChunkCoordinates anchor;
|
||||
|
||||
FlybyCommandSender(EntityPlayerMP player) {
|
||||
FlybyCommandSender(EntityPlayerMP player, ChunkCoordinates anchor) {
|
||||
this.player = player;
|
||||
this.anchor = anchor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,7 +44,7 @@ final class FlybyCommandSender implements ICommandSender {
|
||||
|
||||
@Override
|
||||
public ChunkCoordinates getPlayerCoordinates() {
|
||||
return this.player.getPlayerCoordinates();
|
||||
return this.anchor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.debug.flyby;
|
||||
|
||||
record FlybyOrigin(double x, double z, float yaw) {
|
||||
|
||||
static FlybyOrigin parse(String raw) {
|
||||
if (raw == null || raw.isBlank()) return null;
|
||||
final String[] parts = raw.split(",", -1);
|
||||
if (parts.length != 2 && parts.length != 3) throw new IllegalArgumentException("Expected x,z[,yaw] but got '" + raw + "'");
|
||||
try {
|
||||
return new FlybyOrigin(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim()), parts.length == 3 ? Float.parseFloat(parts[2].trim()) : 0.0F);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Expected x,z[,yaw] but got '" + raw + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,19 +15,23 @@ import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.MinecraftException;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.IChunkProvider;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/** Flies a deterministic camera path */
|
||||
public final class FlybyRunner {
|
||||
public static final FlybyRunner INSTANCE = new FlybyRunner();
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/Flyby");
|
||||
private static final long[] NO_FRAMES = new long[0];
|
||||
private static final double[] NO_PATH = new double[0];
|
||||
@@ -36,7 +40,9 @@ public final class FlybyRunner {
|
||||
private static final String[] NO_COMMANDS = new String[0];
|
||||
private static final int MAX_RECORDED_FRAMES = 200_000;
|
||||
|
||||
private enum State { IDLE, WAITING, WARMUP, RUNNING, SETTLE, EXITING, DONE }
|
||||
public static final FlybyRunner INSTANCE = new FlybyRunner();
|
||||
|
||||
private enum State { IDLE, WAITING, PREPARING, WARMUP, RUNNING, SETTLE, EXITING, DONE }
|
||||
|
||||
private static final int EXIT_TICKS = 20;
|
||||
private static final int SETTLE_TICKS = 40;
|
||||
@@ -52,10 +58,9 @@ public final class FlybyRunner {
|
||||
private boolean waitForFocus;
|
||||
private boolean exitWhenDone;
|
||||
private boolean startedFromProperties;
|
||||
private volatile boolean freezeRequested;
|
||||
private final AtomicReference<FlybyRequest> pendingRequest = new AtomicReference<>();
|
||||
private FlybyRequest activeRequest;
|
||||
private String[] sceneCommands = NO_COMMANDS;
|
||||
private String scenePlayerName;
|
||||
private volatile boolean sceneRequested;
|
||||
private volatile boolean sceneClearRequested;
|
||||
private int sceneSpawnTotal;
|
||||
private boolean pauseOnLostFocusSaved;
|
||||
@@ -65,8 +70,13 @@ public final class FlybyRunner {
|
||||
private static volatile boolean sceneGuarded;
|
||||
private static volatile boolean worldChangesDiscarded;
|
||||
|
||||
private double originX, originY, originZ;
|
||||
private float originYaw, originPitch;
|
||||
private double parkedX, parkedY, parkedZ;
|
||||
private float parkedYaw, parkedPitch;
|
||||
private double originX, originZ;
|
||||
private float originYaw;
|
||||
private FlybyOrigin fixedOrigin;
|
||||
private float eyeOffset;
|
||||
private double flightY;
|
||||
|
||||
private double[] pathX = NO_PATH;
|
||||
private double[] pathZ = NO_PATH;
|
||||
@@ -111,8 +121,17 @@ public final class FlybyRunner {
|
||||
return;
|
||||
}
|
||||
|
||||
final FlybyOrigin origin;
|
||||
try {
|
||||
origin = FlybyOrigin.parse(SystemProperties.FLYBY_ORIGIN);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Invalid flyby origin '{}', expected x,z[,yaw]", SystemProperties.FLYBY_ORIGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
this.startedFromProperties = true;
|
||||
this.start(configured, SystemProperties.FLYBY_LENGTH, SystemProperties.FLYBY_WARMUP_TICKS, SystemProperties.FLYBY_SPEED);
|
||||
this.fixedOrigin = origin;
|
||||
this.waitForTracy = SystemProperties.FLYBY_WAIT_FOR_TRACY;
|
||||
this.waitForFocus = SystemProperties.FLYBY_WAIT_FOR_FOCUS;
|
||||
this.exitWhenDone = SystemProperties.FLYBY_EXIT_WHEN_DONE;
|
||||
@@ -137,6 +156,7 @@ public final class FlybyRunner {
|
||||
this.waitForTracy = false;
|
||||
this.waitForFocus = false;
|
||||
this.exitWhenDone = false;
|
||||
this.fixedOrigin = null;
|
||||
sceneGuarded = false;
|
||||
this.state = State.WAITING;
|
||||
}
|
||||
@@ -160,7 +180,7 @@ public final class FlybyRunner {
|
||||
}
|
||||
if (this.state == State.IDLE || this.state == State.DONE) return;
|
||||
|
||||
if (mc.theWorld == null && (this.state == State.WARMUP || this.state == State.RUNNING || this.state == State.SETTLE)) {
|
||||
if (mc.theWorld == null && (this.state == State.PREPARING || this.state == State.WARMUP || this.state == State.RUNNING || this.state == State.SETTLE)) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
@@ -182,6 +202,12 @@ public final class FlybyRunner {
|
||||
if (this.waitForFocus && !Display.isActive()) return;
|
||||
this.begin(mc, player);
|
||||
}
|
||||
case PREPARING -> {
|
||||
final Double feetY = this.activeRequest.feetY().getNow(null);
|
||||
if (feetY == null) return;
|
||||
this.flightY = feetY + this.eyeOffset;
|
||||
this.startRun(mc);
|
||||
}
|
||||
case WARMUP -> {
|
||||
this.applyPosition(player, 0);
|
||||
if (++this.tick >= this.warmupTicks) {
|
||||
@@ -231,25 +257,49 @@ public final class FlybyRunner {
|
||||
mc.gameSettings.pauseOnLostFocus = false;
|
||||
}
|
||||
|
||||
this.originX = player.posX;
|
||||
this.originY = player.posY;
|
||||
this.originZ = player.posZ;
|
||||
this.originPitch = player.rotationPitch;
|
||||
this.parkedX = player.posX;
|
||||
this.parkedY = player.posY;
|
||||
this.parkedZ = player.posZ;
|
||||
this.parkedYaw = player.rotationYaw;
|
||||
this.parkedPitch = player.rotationPitch;
|
||||
this.eyeOffset = player.yOffset;
|
||||
|
||||
this.originYaw = Math.round(player.rotationYaw / 90.0F) * 90.0F;
|
||||
if (this.originYaw != player.rotationYaw) {
|
||||
LOGGER.info("Flyby snapped heading {} -> {}", player.rotationYaw, this.originYaw);
|
||||
final boolean creative = player.capabilities.isCreativeMode;
|
||||
if (!creative) {
|
||||
LOGGER.warn("Flyby: player is not in creative mode, flying at the player height{}", this.fixedOrigin != null ? " and ignoring the pinned origin" : "");
|
||||
}
|
||||
|
||||
if (creative && this.fixedOrigin != null) {
|
||||
this.originX = this.fixedOrigin.x();
|
||||
this.originZ = this.fixedOrigin.z();
|
||||
this.originYaw = this.fixedOrigin.yaw();
|
||||
} else {
|
||||
this.originX = player.posX;
|
||||
this.originZ = player.posZ;
|
||||
this.originYaw = Math.round(player.rotationYaw / 90.0F) * 90.0F;
|
||||
if (this.originYaw != player.rotationYaw) {
|
||||
LOGGER.info("Flyby snapped heading {} -> {}", player.rotationYaw, this.originYaw);
|
||||
}
|
||||
}
|
||||
|
||||
this.buildPath();
|
||||
this.freezeTimeAndWeather(mc);
|
||||
if (this.sceneCommands.length > 0 && mc.getIntegratedServer() != null) {
|
||||
this.scenePlayerName = player.getCommandSenderName();
|
||||
this.sceneRequested = true;
|
||||
}
|
||||
|
||||
this.suppressPacing();
|
||||
|
||||
this.tick = 0;
|
||||
if (mc.getIntegratedServer() != null) {
|
||||
final FlybyRequest request = new FlybyRequest(this.pathX, this.pathZ, this.originX, this.originZ, player.posY - this.eyeOffset, player.getCommandSenderName(), creative, this.sceneCommands.length > 0, new CompletableFuture<>());
|
||||
this.activeRequest = request;
|
||||
sceneGuarded = true;
|
||||
this.pendingRequest.set(request);
|
||||
this.state = State.PREPARING;
|
||||
} else {
|
||||
LOGGER.warn("Flyby: not singleplayer, cannot freeze time/weather or sample terrain - flying at the player height, results may not be comparable");
|
||||
this.flightY = player.posY;
|
||||
this.startRun(mc);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRun(Minecraft mc) {
|
||||
this.tick = 0;
|
||||
if (this.warmupTicks > 0) {
|
||||
this.state = State.WARMUP;
|
||||
@@ -258,8 +308,7 @@ public final class FlybyRunner {
|
||||
this.beginMeasuring(mc);
|
||||
}
|
||||
|
||||
LOGGER.info("Flyby {} starting at {} {} {} yaw {}", this.route.id(),
|
||||
this.originX, this.originY, this.originZ, this.originYaw);
|
||||
LOGGER.info("Flyby {} starting at {} {} {} yaw {}", this.route.id(), this.originX, this.flightY, this.originZ, this.originYaw);
|
||||
}
|
||||
|
||||
private void suppressPacing() {
|
||||
@@ -393,12 +442,12 @@ public final class FlybyRunner {
|
||||
player.ySize = 0.0F;
|
||||
|
||||
player.prevPosX = player.lastTickPosX = this.pathX[last];
|
||||
player.prevPosY = player.lastTickPosY = this.originY;
|
||||
player.prevPosY = player.lastTickPosY = this.flightY;
|
||||
player.prevPosZ = player.lastTickPosZ = this.pathZ[last];
|
||||
player.prevRotationYaw = player.rotationYaw = this.pathYaw[last];
|
||||
player.prevRotationYawHead = player.rotationYawHead = this.pathYaw[last];
|
||||
player.prevRotationPitch = player.rotationPitch = this.originPitch;
|
||||
player.setPosition(this.pathX[last], this.originY, this.pathZ[last]);
|
||||
player.prevRotationPitch = player.rotationPitch = this.parkedPitch;
|
||||
player.setPosition(this.pathX[last], this.flightY, this.pathZ[last]);
|
||||
}
|
||||
|
||||
private void applyPosition(EntityClientPlayerMP player, int index) {
|
||||
@@ -418,37 +467,23 @@ public final class FlybyRunner {
|
||||
player.ySize = 0.0F;
|
||||
|
||||
player.prevPosX = player.lastTickPosX = prevX;
|
||||
player.prevPosY = player.lastTickPosY = this.originY;
|
||||
player.prevPosY = player.lastTickPosY = this.flightY;
|
||||
player.prevPosZ = player.lastTickPosZ = prevZ;
|
||||
|
||||
player.prevRotationYaw = prevYaw;
|
||||
player.rotationYaw = yaw;
|
||||
player.prevRotationYawHead = prevYaw;
|
||||
player.rotationYawHead = yaw;
|
||||
player.prevRotationPitch = player.rotationPitch = this.originPitch;
|
||||
player.prevRotationPitch = player.rotationPitch = this.parkedPitch;
|
||||
|
||||
player.setPosition(x, this.originY, z);
|
||||
}
|
||||
|
||||
/** Requests the freeze; the write happens on the server thread in {@link #onServerTick}. */
|
||||
private void freezeTimeAndWeather(Minecraft mc) {
|
||||
final MinecraftServer server = mc.getIntegratedServer();
|
||||
if (server == null) {
|
||||
LOGGER.warn("Flyby: not singleplayer, cannot freeze time/weather - results may not be comparable");
|
||||
return;
|
||||
}
|
||||
|
||||
sceneGuarded = true;
|
||||
this.freezeRequested = true;
|
||||
player.setPosition(x, this.flightY, z);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
final boolean clear = this.sceneClearRequested;
|
||||
final boolean freeze = this.freezeRequested;
|
||||
final boolean scene = this.sceneRequested;
|
||||
if (!clear && !freeze && !scene) return;
|
||||
if (!clear && this.pendingRequest.get() == null) return;
|
||||
|
||||
final MinecraftServer server = MinecraftServer.getServer();
|
||||
if (server == null) return;
|
||||
@@ -457,16 +492,16 @@ public final class FlybyRunner {
|
||||
this.sceneClearRequested = false;
|
||||
this.clearScene(server);
|
||||
}
|
||||
if (freeze || scene) {
|
||||
this.discardWorldChanges(server);
|
||||
}
|
||||
if (freeze) {
|
||||
this.freezeRequested = false;
|
||||
this.applyFreeze(server);
|
||||
}
|
||||
if (scene) {
|
||||
this.sceneRequested = false;
|
||||
this.runScene(server);
|
||||
|
||||
final FlybyRequest request = this.pendingRequest.getAndSet(null);
|
||||
if (request == null) return;
|
||||
|
||||
this.discardWorldChanges(server);
|
||||
final double feetY = this.flightFeetY(server, request);
|
||||
request.feetY().complete(feetY);
|
||||
this.applyFreeze(server);
|
||||
if (request.scene()) {
|
||||
this.runScene(server, request, feetY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,16 +546,52 @@ public final class FlybyRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void runScene(MinecraftServer server) {
|
||||
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(this.scenePlayerName);
|
||||
private double flightFeetY(MinecraftServer server, FlybyRequest request) {
|
||||
if (!request.sampleTerrain()) return request.parkedFeetY();
|
||||
|
||||
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(request.playerName());
|
||||
if (player == null) {
|
||||
LOGGER.warn("Flyby scene: no server player named '{}', skipping scene", this.scenePlayerName);
|
||||
LOGGER.warn("Flyby terrain: no server player named '{}', flying at the parked feet Y {}", request.playerName(), request.parkedFeetY());
|
||||
return request.parkedFeetY();
|
||||
}
|
||||
|
||||
final WorldServer world = player.getServerForPlayer();
|
||||
if (world.provider.hasNoSky) {
|
||||
LOGGER.info("Flyby terrain: dimension has a ceiling, flying at the parked feet Y {}", request.parkedFeetY());
|
||||
return request.parkedFeetY();
|
||||
}
|
||||
|
||||
final IChunkProvider chunks = world.getChunkProvider();
|
||||
final int floor = FlybyTerrain.routeFloor(request.pathX(), request.pathZ(), FlybyTerrain.CORRIDOR_RADIUS, (x, z) -> precipitationHeight(chunks, x, z));
|
||||
if (floor < 1) {
|
||||
LOGGER.info("Flyby terrain: no ground within {} blocks of the path, flying at the parked feet Y {}", FlybyTerrain.CORRIDOR_RADIUS, request.parkedFeetY());
|
||||
return request.parkedFeetY();
|
||||
}
|
||||
|
||||
final double feetY = floor + FlybyTerrain.HOVER_BLOCKS;
|
||||
LOGGER.info("Flyby terrain: route floor {} within {} blocks of the path, flight feet Y {}", floor, FlybyTerrain.CORRIDOR_RADIUS, feetY);
|
||||
if (Tracy.ENABLED) Tracy.message("flyby terrain floor=" + floor + " radius=" + FlybyTerrain.CORRIDOR_RADIUS + " feetY=" + feetY);
|
||||
return feetY;
|
||||
}
|
||||
|
||||
private static int precipitationHeight(IChunkProvider chunks, int x, int z) {
|
||||
final int chunkX = x >> 4;
|
||||
final int chunkZ = z >> 4;
|
||||
Chunk chunk = chunks.loadChunk(chunkX, chunkZ);
|
||||
if (chunk == null) chunk = chunks.provideChunk(chunkX, chunkZ);
|
||||
return chunk == null ? Integer.MIN_VALUE : chunk.getPrecipitationHeight(x & 15, z & 15);
|
||||
}
|
||||
|
||||
private void runScene(MinecraftServer server, FlybyRequest request, double feetY) {
|
||||
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(request.playerName());
|
||||
if (player == null) {
|
||||
LOGGER.warn("Flyby scene: no server player named '{}', skipping scene", request.playerName());
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearScene(server);
|
||||
|
||||
final FlybyCommandSender sender = new FlybyCommandSender(player);
|
||||
final FlybyCommandSender sender = new FlybyCommandSender(player, new ChunkCoordinates(MathHelper.floor_double(request.originX()), MathHelper.floor_double(feetY + 0.5D), MathHelper.floor_double(request.originZ())));
|
||||
for (String line : this.sceneCommands) {
|
||||
if (server.getCommandManager().executeCommand(sender, line) == 0) {
|
||||
LOGGER.warn("flyby scene command did not execute: {}", line);
|
||||
@@ -603,11 +674,11 @@ public final class FlybyRunner {
|
||||
player.motionY = 0.0D;
|
||||
player.motionZ = 0.0D;
|
||||
player.ySize = 0.0F;
|
||||
player.setPositionAndRotation(this.originX, this.originY, this.originZ, this.originYaw, this.originPitch);
|
||||
player.lastTickPosX = this.originX;
|
||||
player.lastTickPosY = this.originY;
|
||||
player.lastTickPosZ = this.originZ;
|
||||
LOGGER.info("Flyby returned to origin {} {} {}", this.originX, this.originY, this.originZ);
|
||||
player.setPositionAndRotation(this.parkedX, this.parkedY, this.parkedZ, this.parkedYaw, this.parkedPitch);
|
||||
player.lastTickPosX = this.parkedX;
|
||||
player.lastTickPosY = this.parkedY;
|
||||
player.lastTickPosZ = this.parkedZ;
|
||||
LOGGER.info("Flyby returned to {} {} {}", this.parkedX, this.parkedY, this.parkedZ);
|
||||
}
|
||||
|
||||
private String summarise(long elapsedNs, EntityClientPlayerMP player) {
|
||||
@@ -656,8 +727,8 @@ public final class FlybyRunner {
|
||||
FramePacer.endStats();
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.freezeRequested = false;
|
||||
this.sceneRequested = false;
|
||||
this.pendingRequest.set(null);
|
||||
this.activeRequest = null;
|
||||
sceneGuarded = false;
|
||||
this.sceneClearRequested = this.sceneCommands.length > 0;
|
||||
this.restorePacing(mc);
|
||||
@@ -667,4 +738,6 @@ public final class FlybyRunner {
|
||||
public boolean startedFromProperties() {
|
||||
return this.startedFromProperties;
|
||||
}
|
||||
|
||||
private record FlybyRequest(double[] pathX, double[] pathZ, double originX, double originZ, double parkedFeetY, String playerName, boolean sampleTerrain, boolean scene, CompletableFuture<Double> feetY) {}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gtnewhorizons.angelica.debug.flyby;
|
||||
|
||||
import java.util.function.IntBinaryOperator;
|
||||
|
||||
final class FlybyTerrain {
|
||||
|
||||
static final int CORRIDOR_RADIUS = 2;
|
||||
static final int HOVER_BLOCKS = 2;
|
||||
|
||||
private FlybyTerrain() {}
|
||||
|
||||
static int routeFloor(double[] pathX, double[] pathZ, int radius, IntBinaryOperator heights) {
|
||||
int floor = Integer.MIN_VALUE;
|
||||
int prevCellX = 0;
|
||||
int prevCellZ = 0;
|
||||
for (int i = 0; i < pathX.length; i++) {
|
||||
final int cellX = (int) Math.floor(pathX[i]);
|
||||
final int cellZ = (int) Math.floor(pathZ[i]);
|
||||
if (i > 0 && cellX == prevCellX && cellZ == prevCellZ) continue;
|
||||
prevCellX = cellX;
|
||||
prevCellZ = cellZ;
|
||||
for (int x = cellX - radius; x <= cellX + radius; x++) {
|
||||
for (int z = cellZ - radius; z <= cellZ + radius; z++) {
|
||||
floor = Math.max(floor, heights.applyAsInt(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
return floor;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package com.gtnewhorizons.angelica.helpers;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
|
||||
import net.minecraft.client.renderer.entity.RenderDragon;
|
||||
import net.minecraft.client.renderer.entity.RenderEnderman;
|
||||
import net.minecraft.client.renderer.entity.RenderSpider;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -11,6 +14,10 @@ public final class RendererLivingEntityHelper {
|
||||
|
||||
private RendererLivingEntityHelper() {}
|
||||
|
||||
public static boolean hasEyePass(Object renderer) {
|
||||
return renderer instanceof RenderSpider || renderer instanceof RenderEnderman || renderer instanceof RenderDragon;
|
||||
}
|
||||
|
||||
public static String getUpsideDownName(EntityLivingBase entity) {
|
||||
if (entity instanceof EntityLiving living) {
|
||||
return living.hasCustomNameTag() ? entity.getCommandSenderName() : "";
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.iris;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
@@ -81,6 +82,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
|
||||
|
||||
@Override
|
||||
public boolean wantsExtended() {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
return Iris.enabled && currentProgramWants != 0 && GLStateManager.getActiveProgram() != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,6 @@ public class IrisGLSMBridge {
|
||||
private static Runnable fogDensityListener = null;
|
||||
private static Runnable colorModulatorListener = null;
|
||||
|
||||
private static boolean inputsDeferred = false;
|
||||
|
||||
private static boolean blendDeferred = false;
|
||||
|
||||
private static boolean programRestoreDeferred = false;
|
||||
|
||||
private static final Int2IntOpenHashMap programLastUpdatedFrame = new Int2IntOpenHashMap();
|
||||
@@ -269,32 +265,19 @@ public class IrisGLSMBridge {
|
||||
}
|
||||
if (!updatePipeline) return;
|
||||
|
||||
if (GLStateManager.isForeignDraw()) {
|
||||
inputsDeferred = true;
|
||||
return;
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (pipeline != null) {
|
||||
pipeline.setInputs(StateTracker.INSTANCE.getInputs());
|
||||
}
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs()));
|
||||
});
|
||||
|
||||
GLSMHooks.VANILLA_BLEND_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (GLStateManager.isForeignDraw()) {
|
||||
blendDeferred = true;
|
||||
return;
|
||||
}
|
||||
refreshBlendCondition();
|
||||
});
|
||||
|
||||
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (inputsDeferred) {
|
||||
inputsDeferred = false;
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs()));
|
||||
}
|
||||
if (blendDeferred) {
|
||||
blendDeferred = false;
|
||||
refreshBlendCondition();
|
||||
}
|
||||
if (programRestoreDeferred) {
|
||||
programRestoreDeferred = false;
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
|
||||
@@ -64,6 +64,7 @@ public enum Mixins implements IMixins {
|
||||
, "angelica.ffp.MixinTessellator_CoreProfile"
|
||||
, "angelica.glsm.MixinSplashProgressCaching"
|
||||
, "angelica.gui.MixinGuiOptions"
|
||||
, "angelica.optimizations.MixinRenderBiped_ArmorResource"
|
||||
, "angelica.optimizations.MixinRendererLivingEntity"
|
||||
, "angelica.rendering.MixinRenderGlobal_SelectionBox"
|
||||
, "angelica.gui.MixinGuiIngameForge_ModernF3"
|
||||
@@ -170,15 +171,29 @@ public enum Mixins implements IMixins {
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableEntityBatching)
|
||||
.addClientMixins(
|
||||
"angelica.entity.MixinItemRenderer_Instanced",
|
||||
"angelica.entity.MixinModelRenderer",
|
||||
"angelica.entity.MixinRenderGlobal_EntityBatch",
|
||||
"angelica.entity.MixinRenderItem_Instanced",
|
||||
"angelica.entity.MixinRenderManager_BatchEligibility",
|
||||
"angelica.entity.MixinRendererLivingEntity_EquippedDraws",
|
||||
"angelica.entity.MixinRendererLivingEntity_GlintClock",
|
||||
"angelica.entity.MixinRendererLivingEntity_ModelPassDraws",
|
||||
"angelica.entity.MixinRender_BatchEligibility",
|
||||
"angelica.entity.MixinRender_ShadowBatch",
|
||||
"angelica.entity.MixinTextureManager",
|
||||
"angelica.tesr.MixinTileEntitySpecialRenderer_BatchEligibility"
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_CUBE_INSTANCING(new MixinBuilder("Capture cuboid parameters so model cubes can be drawn from a shared unit cube")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(AngelicaConfig::cubeInstancingEnabled)
|
||||
.addClientMixins(
|
||||
"angelica.entity.MixinModelBox_CubeParams"
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_SKIP_END_FRAME_FLUSH(new MixinBuilder("Skip the end-of-frame glFlush before the buffer swap")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.skipEndOfFrameFlush)
|
||||
@@ -298,9 +313,9 @@ public enum Mixins implements IMixins {
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins("angelica.optimizations.MixinGLAllocation")),
|
||||
|
||||
ANGELICA_DEFERRED_TESSELLATOR_BATCH(new MixinBuilder("Deferred tessellator batching for particles to reduce draw calls")
|
||||
ANGELICA_PARTICLE_BATCH(new MixinBuilder("Particle instancing, with deferred tessellator batching as the fallback")
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins("angelica.particles.MixinEffectRenderer_DeferredBatch")),
|
||||
.addClientMixins("angelica.particles.MixinEffectRenderer_ParticleBatch")),
|
||||
|
||||
// Not compatible with the lwjgl debug callbacks, so disable if that's enabled
|
||||
ARCHAIC_SPLASH(new MixinBuilder()
|
||||
@@ -785,6 +800,11 @@ public enum Mixins implements IMixins {
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer")
|
||||
),
|
||||
DRAGONAPI_PARTICLE_BATCH(new MixinBuilder("Particle instancing for DragonAPI's replacement particle renderer")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.DRAGON_API)
|
||||
.addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer_ParticleBatch")
|
||||
),
|
||||
MCPATCHER_FORGE(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableMCPatcherForgeFeatures)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
|
||||
public interface ModelBoxData {
|
||||
|
||||
CubeParams angelica$cubeParams();
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import net.minecraft.client.Minecraft;
|
||||
public final class BailClassCounts {
|
||||
public static final BailClassCounts MATERIAL = new BailClassCounts("tesr.bailCls.mat.", "BailMat");
|
||||
public static final BailClassCounts TEMPLATE = new BailClassCounts("tesr.bailCls.tmpl.", "BailTmpl");
|
||||
public static final BailClassCounts PARTICLE_SPILL = new BailClassCounts("particles.spillCls.", "PartSpill");
|
||||
|
||||
private final String plotPrefix;
|
||||
private final String label;
|
||||
|
||||
@@ -14,6 +14,8 @@ import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.TerrainDrawStats;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
|
||||
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
|
||||
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.particles.ParticleInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
@@ -24,31 +26,21 @@ import org.embeddedt.embeddium.impl.render.chunk.SharedQuadIndexBuffer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegionManager;
|
||||
|
||||
public final class TracyFramePlots {
|
||||
private static long lastChunkUpdates;
|
||||
private static long lastDrawCalls, lastTexBindMisses, lastProgramSwitches, lastListPlaybacks;
|
||||
private static long lastStreamedBytes, lastStreamDraws, lastOrphanFallbacks, lastStreamContiguous;
|
||||
private static long lastRingWraps, lastVariantSwitches;
|
||||
private static long lastTesrRebuilds, lastTesrRetainedDraws, lastTesrInstDraws, lastTesrInstInstances;
|
||||
private static long lastTesrStreamedInstances, lastTesrPromotions, lastTesrCacheHits, lastTesrCacheMisses;
|
||||
private static long lastBatcherParts, lastBatcherLiveFallbacks;
|
||||
private static final long[] lastBails = new long[ModelPartBatcher.BailReason.VALUES.length];
|
||||
private static long lastSectionsUploaded, lastBytesUploaded, lastIndexBufferGrowths;
|
||||
|
||||
private static final long P_ALLOC_RATE = Tracy.plotHandle("allocRate", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_HEAP_USED = Tracy.plotHandle("heapUsed", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_CHUNK_UPDATES = Tracy.plotHandle("chunkUpdates");
|
||||
private static final PlotDelta P_CHUNK_UPDATES = new PlotDelta("chunkUpdates");
|
||||
private static final long P_ENTITIES_RENDERED = Tracy.plotHandle("entitiesRendered");
|
||||
private static final long P_ENTITIES_TOTAL = Tracy.plotHandle("entitiesTotal");
|
||||
|
||||
private static final long P_GL_DRAW_CALLS = Tracy.plotHandle("gl.drawCalls");
|
||||
private static final long P_GL_TEX_BIND_MISSES = Tracy.plotHandle("gl.texBindMisses");
|
||||
private static final long P_GL_PROGRAM_SWITCHES = Tracy.plotHandle("gl.programSwitches");
|
||||
private static final long P_GL_LIST_PLAYBACKS = Tracy.plotHandle("gl.listPlaybacks");
|
||||
private static final long P_GL_STREAMED_BYTES = Tracy.plotHandle("gl.streamedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_GL_STREAM_DRAWS = Tracy.plotHandle("gl.streamDraws");
|
||||
private static final long P_GL_ORPHAN_FALLBACKS = Tracy.plotHandle("gl.orphanFallbacks");
|
||||
private static final long P_GL_STREAM_CONTIGUOUS = Tracy.plotHandle("gl.streamContiguous");
|
||||
private static final long P_GL_RING_WRAPS = Tracy.plotHandle("gl.ringWraps");
|
||||
private static final PlotDelta P_GL_DRAW_CALLS = new PlotDelta("gl.drawCalls");
|
||||
private static final PlotDelta P_GL_TEX_BIND_MISSES = new PlotDelta("gl.texBindMisses");
|
||||
private static final PlotDelta P_GL_PROGRAM_SWITCHES = new PlotDelta("gl.programSwitches");
|
||||
private static final PlotDelta P_GL_LIST_PLAYBACKS = new PlotDelta("gl.listPlaybacks");
|
||||
private static final PlotDelta P_GL_STREAMED_BYTES = new PlotDelta("gl.streamedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final PlotDelta P_GL_STREAM_DRAWS = new PlotDelta("gl.streamDraws");
|
||||
private static final PlotDelta P_GL_ORPHAN_FALLBACKS = new PlotDelta("gl.orphanFallbacks");
|
||||
private static final PlotDelta P_GL_STREAM_CONTIGUOUS = new PlotDelta("gl.streamContiguous");
|
||||
private static final PlotDelta P_GL_RING_WRAPS = new PlotDelta("gl.ringWraps");
|
||||
private static final long P_GL_SECTION_META_BYTES = Tracy.plotHandle("gl.sectionMetaBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
|
||||
private static final long P_SHADOW_RELAY_FRAMES = Tracy.plotHandle("shadow.relayFrames");
|
||||
@@ -59,7 +51,7 @@ public final class TracyFramePlots {
|
||||
private static final long P_VOX_DISPATCHES = Tracy.plotHandle("voxelization.dispatches");
|
||||
private static final long P_VOX_REGIONS = Tracy.plotHandle("voxelization.regions");
|
||||
private static final long P_DRAW_BATCH_REBUILDS = Tracy.plotHandle("draw.batchRebuilds");
|
||||
private static final long P_DRAW_INDEX_BUFFER_GROWTHS = Tracy.plotHandle("draw.indexBufferGrowths");
|
||||
private static final PlotDelta P_DRAW_INDEX_BUFFER_GROWTHS = new PlotDelta("draw.indexBufferGrowths");
|
||||
|
||||
private static final long P_CULL_COMMANDS_SUBMITTED = Tracy.plotHandle("cull.commandsSubmitted");
|
||||
private static final long P_CULL_REGIONS_DRAWN = Tracy.plotHandle("cull.regionsDrawn");
|
||||
@@ -79,24 +71,37 @@ public final class TracyFramePlots {
|
||||
private static final long P_FFP_STAGED_CLIP_PLANES = Tracy.plotHandle("ffp.stagedClipPlanes");
|
||||
private static final long P_FFP_STAGED_MISC = Tracy.plotHandle("ffp.stagedMisc");
|
||||
private static final long P_FFP_PROGRAMS = Tracy.plotHandle("ffp.programs");
|
||||
private static final long P_FFP_VARIANT_SWITCHES = Tracy.plotHandle("ffp.variantSwitches");
|
||||
private static final PlotDelta P_FFP_VARIANT_SWITCHES = new PlotDelta("ffp.variantSwitches");
|
||||
|
||||
private static final long P_TESR_REBUILDS = Tracy.plotHandle("tesr.rebuilds");
|
||||
private static final long P_TESR_RETAINED_DRAWS = Tracy.plotHandle("tesr.retainedDraws");
|
||||
private static final long P_TESR_INSTANCED_DRAWS = Tracy.plotHandle("tesr.instancedDraws");
|
||||
private static final long P_TESR_INSTANCED_INSTANCES = Tracy.plotHandle("tesr.instancedInstances");
|
||||
private static final long P_TESR_STREAMED_INSTANCES = Tracy.plotHandle("tesr.streamedInstances");
|
||||
private static final long P_TESR_VOLATILE_PROMOTIONS = Tracy.plotHandle("tesr.volatilePromotions");
|
||||
private static final PlotDelta P_TESR_REBUILDS = new PlotDelta("tesr.rebuilds");
|
||||
private static final PlotDelta P_TESR_RETAINED_DRAWS = new PlotDelta("tesr.retainedDraws");
|
||||
private static final PlotDelta P_TESR_INSTANCED_DRAWS = new PlotDelta("tesr.instancedDraws");
|
||||
private static final PlotDelta P_TESR_INSTANCED_INSTANCES = new PlotDelta("tesr.instancedInstances");
|
||||
private static final PlotDelta P_TESR_CUBE_INSTANCES = new PlotDelta("tesr.cubeInstances");
|
||||
private static final PlotDelta P_TESR_TEXMAT_RUNS = new PlotDelta("tesr.texMatrixRuns");
|
||||
private static final PlotDelta P_TESR_STREAMED_INSTANCES = new PlotDelta("tesr.streamedInstances");
|
||||
private static final PlotDelta P_TESR_VOLATILE_PROMOTIONS = new PlotDelta("tesr.volatilePromotions");
|
||||
private static final long P_TESR_RETAINED_BYTES = Tracy.plotHandle("tesr.retainedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_TESR_BUFFER_SOURCE_BYTES = Tracy.plotHandle("tesr.bufferSourceBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_TESR_CACHE_HITS = Tracy.plotHandle("tesr.cacheHits");
|
||||
private static final long P_TESR_CACHE_MISSES = Tracy.plotHandle("tesr.cacheMisses");
|
||||
private static final long P_TESR_MODEL_PARTS = Tracy.plotHandle("tesr.modelParts");
|
||||
private static final long P_TESR_LIVE_FALLBACKS = Tracy.plotHandle("tesr.liveFallbacks");
|
||||
private static final long[] P_TESR_BAILS = bailHandles();
|
||||
private static final PlotDelta P_TESR_CACHE_HITS = new PlotDelta("tesr.cacheHits");
|
||||
private static final PlotDelta P_TESR_CACHE_MISSES = new PlotDelta("tesr.cacheMisses");
|
||||
private static final PlotDelta P_TESR_MODEL_PARTS = new PlotDelta("tesr.modelParts");
|
||||
private static final PlotDelta P_TESR_LIVE_FALLBACKS = new PlotDelta("tesr.liveFallbacks");
|
||||
private static final PlotDelta P_ITEMS_INSTANCED = new PlotDelta("items.instanced");
|
||||
private static final PlotDelta P_ITEMS_FALLBACK = new PlotDelta("items.fallback");
|
||||
private static final PlotDelta P_ITEMS_GLINT_INSTANCED = new PlotDelta("items.glintInstanced");
|
||||
private static final PlotDelta P_PARTICLES_DIRECT = new PlotDelta("particles.direct");
|
||||
private static final PlotDelta P_PARTICLES_CAPTURED = new PlotDelta("particles.captured");
|
||||
private static final PlotDelta P_PARTICLES_SPILLED = new PlotDelta("particles.spilled");
|
||||
private static final PlotDelta P_PARTICLES_UNDECODABLE = new PlotDelta("particles.undecodable");
|
||||
private static final PlotDelta P_PARTICLES_DRAWS = new PlotDelta("particles.draws");
|
||||
private static final PlotDelta[] TESR_BAILS = bailHandles();
|
||||
private static final PlotDelta[] ITEM_BAILS = itemBailHandles();
|
||||
private static final PlotDelta P_ENTITY_SHADOW_QUADS = new PlotDelta("entity.shadowQuads");
|
||||
private static final PlotDelta P_ENTITY_SHADOW_DRAWS = new PlotDelta("entity.shadowDraws");
|
||||
|
||||
private static final long P_MESH_UPLOADED_SECTIONS = Tracy.plotHandle("mesh.uploadedSections");
|
||||
private static final long P_MESH_UPLOADED_BYTES = Tracy.plotHandle("mesh.uploadedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final PlotDelta P_MESH_UPLOADED_SECTIONS = new PlotDelta("mesh.uploadedSections");
|
||||
private static final PlotDelta P_MESH_UPLOADED_BYTES = new PlotDelta("mesh.uploadedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_MESH_META_SLOTS = Tracy.plotHandle("mesh.metaSlots");
|
||||
private static final long P_MT_QUEUE_DEPTH = Tracy.plotHandle("mtQueue.depth");
|
||||
private static final long P_MT_QUEUE_TASKS = Tracy.plotHandle("mtQueue.tasksRan");
|
||||
@@ -110,10 +115,17 @@ public final class TracyFramePlots {
|
||||
private static final long P_DYN_UPDATES = Tracy.plotHandle("dyn.updates");
|
||||
private static final long P_DYN_PENDING_REBUILDS = Tracy.plotHandle("dyn.pendingRebuilds");
|
||||
|
||||
private static long[] bailHandles() {
|
||||
private static PlotDelta[] bailHandles() {
|
||||
final ModelPartBatcher.BailReason[] reasons = ModelPartBatcher.BailReason.VALUES;
|
||||
final long[] out = new long[reasons.length];
|
||||
for (int i = 0; i < reasons.length; i++) out[i] = Tracy.plotHandle(reasons[i].plotName);
|
||||
final PlotDelta[] out = new PlotDelta[reasons.length];
|
||||
for (int i = 0; i < reasons.length; i++) out[i] = new PlotDelta(reasons[i].plotName);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static PlotDelta[] itemBailHandles() {
|
||||
final DroppedItemInstancer.BailReason[] reasons = DroppedItemInstancer.BailReason.VALUES;
|
||||
final PlotDelta[] out = new PlotDelta[reasons.length];
|
||||
for (int i = 0; i < reasons.length; i++) out[i] = new PlotDelta(reasons[i].plotName);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -153,9 +165,7 @@ public final class TracyFramePlots {
|
||||
Tracy.plotInt(P_PACER_LOCKED, FramePacer.locked() ? 1 : 0);
|
||||
Tracy.plotInt(P_PACER_PROBING, FramePacer.probing() ? 1 : 0);
|
||||
|
||||
final int chunksUpdated = WorldRenderer.chunksUpdated;
|
||||
Tracy.plotInt(P_CHUNK_UPDATES, chunksUpdated - lastChunkUpdates);
|
||||
lastChunkUpdates = chunksUpdated;
|
||||
P_CHUNK_UPDATES.plot(WorldRenderer.chunksUpdated);
|
||||
if (mc.renderGlobal != null) {
|
||||
Tracy.plotInt(P_ENTITIES_RENDERED, mc.renderGlobal.countEntitiesRendered);
|
||||
}
|
||||
@@ -163,15 +173,15 @@ public final class TracyFramePlots {
|
||||
Tracy.plotInt(P_ENTITIES_TOTAL, mc.theWorld.loadedEntityList.size());
|
||||
}
|
||||
|
||||
lastDrawCalls = delta(P_GL_DRAW_CALLS, GLStateManager.drawCalls, lastDrawCalls);
|
||||
lastTexBindMisses = delta(P_GL_TEX_BIND_MISSES, GLStateManager.texBindMisses, lastTexBindMisses);
|
||||
lastProgramSwitches = delta(P_GL_PROGRAM_SWITCHES, GLStateManager.programSwitches, lastProgramSwitches);
|
||||
lastListPlaybacks = delta(P_GL_LIST_PLAYBACKS, DisplayListManager.listPlaybacks, lastListPlaybacks);
|
||||
lastStreamedBytes = delta(P_GL_STREAMED_BYTES, TessellatorStreamingDrawer.streamedBytes, lastStreamedBytes);
|
||||
lastStreamDraws = delta(P_GL_STREAM_DRAWS, TessellatorStreamingDrawer.streamDraws, lastStreamDraws);
|
||||
lastOrphanFallbacks = delta(P_GL_ORPHAN_FALLBACKS, TessellatorStreamingDrawer.orphanFallbacks, lastOrphanFallbacks);
|
||||
lastStreamContiguous = delta(P_GL_STREAM_CONTIGUOUS, TessellatorStreamingDrawer.streamContiguous, lastStreamContiguous);
|
||||
lastRingWraps = delta(P_GL_RING_WRAPS, TessellatorStreamingDrawer.ringWraps(), lastRingWraps);
|
||||
P_GL_DRAW_CALLS.plot(GLStateManager.drawCalls);
|
||||
P_GL_TEX_BIND_MISSES.plot(GLStateManager.texBindMisses);
|
||||
P_GL_PROGRAM_SWITCHES.plot(GLStateManager.programSwitches);
|
||||
P_GL_LIST_PLAYBACKS.plot(DisplayListManager.listPlaybacks);
|
||||
P_GL_STREAMED_BYTES.plot(TessellatorStreamingDrawer.streamedBytes);
|
||||
P_GL_STREAM_DRAWS.plot(TessellatorStreamingDrawer.streamDraws);
|
||||
P_GL_ORPHAN_FALLBACKS.plot(TessellatorStreamingDrawer.orphanFallbacks);
|
||||
P_GL_STREAM_CONTIGUOUS.plot(TessellatorStreamingDrawer.streamContiguous);
|
||||
P_GL_RING_WRAPS.plot(TessellatorStreamingDrawer.ringWraps());
|
||||
Tracy.plotInt(P_GL_SECTION_META_BYTES, TerrainDrawStats.takeSectionMetaBytes());
|
||||
Tracy.plotInt(P_SHADOW_RELAY_FRAMES, ShadowRenderer.SHADOW_TERRAIN_RELAID ? 1 : 0);
|
||||
Tracy.plotInt(P_TERRAIN_DRAW_COMMANDS, TerrainDrawStats.takeCommandsSubmitted());
|
||||
@@ -199,33 +209,47 @@ public final class TracyFramePlots {
|
||||
Tracy.plotInt(P_FFP_STAGED_CLIP_PLANES, ffp.statLastFrameStagedClipPlanes());
|
||||
Tracy.plotInt(P_FFP_STAGED_MISC, ffp.statLastFrameStagedMisc());
|
||||
Tracy.plotInt(P_FFP_PROGRAMS, ffp.statProgramCount());
|
||||
lastVariantSwitches = delta(P_FFP_VARIANT_SWITCHES, ShaderManager.variantSwitches, lastVariantSwitches);
|
||||
P_FFP_VARIANT_SWITCHES.plot(ShaderManager.variantSwitches);
|
||||
|
||||
final TesrBatchRenderer tesr = TesrBatchRenderer.INSTANCE;
|
||||
final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE;
|
||||
|
||||
lastTesrRebuilds = delta(P_TESR_REBUILDS, tesr.statRebuilds(), lastTesrRebuilds);
|
||||
lastTesrRetainedDraws = delta(P_TESR_RETAINED_DRAWS, tesr.statRetainedDraws(), lastTesrRetainedDraws);
|
||||
lastTesrInstDraws = delta(P_TESR_INSTANCED_DRAWS, tesr.statInstancedDraws(), lastTesrInstDraws);
|
||||
lastTesrInstInstances = delta(P_TESR_INSTANCED_INSTANCES, tesr.statInstancedInstances(), lastTesrInstInstances);
|
||||
lastTesrStreamedInstances = delta(P_TESR_STREAMED_INSTANCES, tesr.statStreamedInstances(), lastTesrStreamedInstances);
|
||||
lastTesrPromotions = delta(P_TESR_VOLATILE_PROMOTIONS, tesr.statVolatilePromotions(), lastTesrPromotions);
|
||||
P_TESR_REBUILDS.plot(tesr.statRebuilds());
|
||||
P_TESR_RETAINED_DRAWS.plot(tesr.statRetainedDraws());
|
||||
P_TESR_INSTANCED_DRAWS.plot(tesr.statInstancedDraws() + batcher.statInstancedDraws());
|
||||
P_TESR_INSTANCED_INSTANCES.plot(tesr.statInstancedInstances() + batcher.statInstancedInstances());
|
||||
P_TESR_CUBE_INSTANCES.plot(batcher.statCubeInstances());
|
||||
P_TESR_TEXMAT_RUNS.plot(batcher.statTexMatrixRuns());
|
||||
P_TESR_STREAMED_INSTANCES.plot(tesr.statStreamedInstances());
|
||||
P_TESR_VOLATILE_PROMOTIONS.plot(tesr.statVolatilePromotions());
|
||||
if (plots) {
|
||||
Tracy.plotInt(P_TESR_RETAINED_BYTES, tesr.statRetainedBytes());
|
||||
Tracy.plotInt(P_TESR_BUFFER_SOURCE_BYTES, tesr.statBufferSourceBytes());
|
||||
}
|
||||
lastTesrCacheHits = delta(P_TESR_CACHE_HITS, AngelicaTesrMeshCache.cacheHits, lastTesrCacheHits);
|
||||
lastTesrCacheMisses = delta(P_TESR_CACHE_MISSES, AngelicaTesrMeshCache.cacheMisses, lastTesrCacheMisses);
|
||||
lastBatcherParts = delta(P_TESR_MODEL_PARTS, batcher.statParts(), lastBatcherParts);
|
||||
lastBatcherLiveFallbacks = delta(P_TESR_LIVE_FALLBACKS, batcher.statLiveFallbacks(), lastBatcherLiveFallbacks);
|
||||
P_TESR_CACHE_HITS.plot(AngelicaTesrMeshCache.cacheHits);
|
||||
P_TESR_CACHE_MISSES.plot(AngelicaTesrMeshCache.cacheMisses);
|
||||
P_TESR_MODEL_PARTS.plot(batcher.statParts());
|
||||
P_TESR_LIVE_FALLBACKS.plot(batcher.statLiveFallbacks());
|
||||
P_ITEMS_INSTANCED.plot(DroppedItemInstancer.statInstanced());
|
||||
P_ITEMS_FALLBACK.plot(DroppedItemInstancer.statFallback());
|
||||
P_ITEMS_GLINT_INSTANCED.plot(DroppedItemInstancer.statGlintInstanced());
|
||||
for (final DroppedItemInstancer.BailReason reason : DroppedItemInstancer.BailReason.VALUES) {
|
||||
ITEM_BAILS[reason.ordinal()].plot(DroppedItemInstancer.statBail(reason));
|
||||
}
|
||||
P_PARTICLES_DIRECT.plot(ParticleInstancer.getDirect());
|
||||
P_PARTICLES_CAPTURED.plot(ParticleInstancer.getCaptured());
|
||||
P_PARTICLES_SPILLED.plot(ParticleInstancer.getSpilled());
|
||||
P_PARTICLES_UNDECODABLE.plot(ParticleInstancer.getUndecodable());
|
||||
P_PARTICLES_DRAWS.plot(ParticleInstancer.getDraws());
|
||||
P_ENTITY_SHADOW_QUADS.plot(batcher.statShadowQuads());
|
||||
P_ENTITY_SHADOW_DRAWS.plot(batcher.statShadowDraws());
|
||||
for (final ModelPartBatcher.BailReason reason : ModelPartBatcher.BailReason.VALUES) {
|
||||
final int i = reason.ordinal();
|
||||
lastBails[i] = delta(P_TESR_BAILS[i], batcher.statBail(reason), lastBails[i]);
|
||||
TESR_BAILS[reason.ordinal()].plot(batcher.statBail(reason));
|
||||
}
|
||||
|
||||
lastSectionsUploaded = delta(P_MESH_UPLOADED_SECTIONS, RenderRegionManager.getSectionsUploaded(), lastSectionsUploaded);
|
||||
lastBytesUploaded = delta(P_MESH_UPLOADED_BYTES, RenderRegionManager.getBytesUploaded(), lastBytesUploaded);
|
||||
lastIndexBufferGrowths = delta(P_DRAW_INDEX_BUFFER_GROWTHS, SharedQuadIndexBuffer.getGrowths(), lastIndexBufferGrowths);
|
||||
P_MESH_UPLOADED_SECTIONS.plot(RenderRegionManager.getSectionsUploaded());
|
||||
P_MESH_UPLOADED_BYTES.plot(RenderRegionManager.getBytesUploaded());
|
||||
P_DRAW_INDEX_BUFFER_GROWTHS.plot(SharedQuadIndexBuffer.getGrowths());
|
||||
Tracy.plotInt(P_MESH_META_SLOTS, GpuCulling.sectionMeta().getHighWaterMark());
|
||||
Tracy.plotInt(P_MT_QUEUE_DEPTH, AngelicaRenderQueue.getQueueDepth());
|
||||
Tracy.plotInt(P_MT_QUEUE_TASKS, AngelicaRenderQueue.getLastFrameTasksRan());
|
||||
@@ -253,10 +277,24 @@ public final class TracyFramePlots {
|
||||
RenderClassTimings.TESR.flushFrame(plots);
|
||||
BailClassCounts.MATERIAL.flushFrame(plots);
|
||||
BailClassCounts.TEMPLATE.flushFrame(plots);
|
||||
BailClassCounts.PARTICLE_SPILL.flushFrame(plots);
|
||||
}
|
||||
|
||||
private static long delta(long handle, long now, long last) {
|
||||
Tracy.plotInt(handle, now - last);
|
||||
return now;
|
||||
private static final class PlotDelta {
|
||||
private final long handle;
|
||||
private long last;
|
||||
|
||||
PlotDelta(String name) {
|
||||
this(name, TracyBackend.PLOT_FORMAT_NUMBER);
|
||||
}
|
||||
|
||||
PlotDelta(String name, int format) {
|
||||
handle = Tracy.plotHandle(name, format);
|
||||
}
|
||||
|
||||
void plot(long now) {
|
||||
Tracy.plotInt(handle, now - last);
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ArmorTexturePaths {
|
||||
private static final int STRIDE = 5;
|
||||
private static String[] table = new String[0];
|
||||
|
||||
public static String path(String prefix, int renderIndex, int slot, String type) {
|
||||
if (prefix == null || (type != null && !"overlay".equals(type))) return format(prefix, slot, type);
|
||||
String[] t = table;
|
||||
final int base = renderIndex * STRIDE;
|
||||
if (base >= t.length) {
|
||||
t = Arrays.copyOf(t, base + STRIDE);
|
||||
table = t;
|
||||
}
|
||||
if (t[base] != prefix) {
|
||||
t[base + 1] = null;
|
||||
t[base + 2] = null;
|
||||
t[base + 3] = null;
|
||||
t[base + 4] = null;
|
||||
t[base] = prefix;
|
||||
}
|
||||
final int k = base + 1 + (slot == 2 ? 1 : 0) + (type == null ? 0 : 2);
|
||||
String p = t[k];
|
||||
if (p == null) {
|
||||
p = format(prefix, slot, type);
|
||||
t[k] = p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private static String format(String prefix, int slot, String type) {
|
||||
return "textures/models/armor/" + prefix + (slot == 2 ? "_layer_2" : "_layer_1") + (type == null ? ".png" : "_" + type + ".png");
|
||||
}
|
||||
|
||||
private ArmorTexturePaths() {}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public final class GlintClock {
|
||||
|
||||
private static final double SPEED0 = 0.001;
|
||||
private static final double SPEED1 = 0.001 + 0.003;
|
||||
private static final double SIN0 = Math.sin(Math.toRadians(30.0));
|
||||
private static final double COS0 = Math.cos(Math.toRadians(30.0));
|
||||
private static final double SIN1 = Math.sin(Math.toRadians(-30.0));
|
||||
private static final double COS1 = Math.cos(Math.toRadians(-30.0));
|
||||
|
||||
private static long millis;
|
||||
private static float armorU0, armorV0, armorU1, armorV1;
|
||||
|
||||
public static void beginFrame(long nowMillis) {
|
||||
millis = nowMillis;
|
||||
final double ticks = nowMillis / 50.0;
|
||||
|
||||
final double f0 = ticks * SPEED0 * 20.0;
|
||||
final double du0 = frac(-f0 * SIN0 / 3.0);
|
||||
final double dv0 = frac(f0 * COS0 / 3.0);
|
||||
armorU0 = (float) (3.0 * (COS0 * du0 + SIN0 * dv0));
|
||||
armorV0 = (float) (3.0 * (-SIN0 * du0 + COS0 * dv0));
|
||||
|
||||
final double f1 = ticks * SPEED1 * 20.0;
|
||||
final double du1 = frac(-f1 * SIN1 / 3.0);
|
||||
final double dv1 = frac(f1 * COS1 / 3.0);
|
||||
armorU1 = (float) (3.0 * (COS1 * du1 + SIN1 * dv1));
|
||||
armorV1 = (float) (3.0 * (-SIN1 * du1 + COS1 * dv1));
|
||||
}
|
||||
|
||||
public static long millis() {
|
||||
return millis;
|
||||
}
|
||||
|
||||
static boolean secondArmorLayer(Matrix4fc textureMatrix) {
|
||||
return textureMatrix.m01() < 0f;
|
||||
}
|
||||
|
||||
public static void translateArmorGlint() {
|
||||
if (secondArmorLayer(GLStateManager.getMatrixStack())) {
|
||||
GLStateManager.glTranslatef(armorU1, armorV1, 0f);
|
||||
} else {
|
||||
GLStateManager.glTranslatef(armorU0, armorV0, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private static double frac(double x) {
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
private GlintClock() {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
public final class OperationArgs {
|
||||
|
||||
private OperationArgs() {}
|
||||
|
||||
public static Object boxed(Object prev, float v) {
|
||||
return prev instanceof Float f && Float.floatToRawIntBits(f) == Float.floatToRawIntBits(v) ? prev : Float.valueOf(v);
|
||||
}
|
||||
|
||||
public static Object boxed(Object prev, int v) {
|
||||
return prev instanceof Integer i && i == v ? prev : Integer.valueOf(v);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,10 @@ public final class ParticleRunSplitter {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean currentRunTranslucent() {
|
||||
return runTranslucent;
|
||||
}
|
||||
|
||||
public static boolean isTranslucent(EntityFX particle) {
|
||||
if (particle.particleIcon instanceof TextureAtlasSprite sprite) {
|
||||
return ((SpriteExtension) sprite).celeritas$getTransparencyLevel() == SpriteTransparencyLevel.TRANSLUCENT;
|
||||
|
||||
+1
@@ -88,6 +88,7 @@ public class CeleritasDebugScreenHandler {
|
||||
addIfPresent(lines, RenderClassTimings.SHADOW_ENTITY.debugLine());
|
||||
addIfPresent(lines, BailClassCounts.MATERIAL.debugLine());
|
||||
addIfPresent(lines, BailClassCounts.TEMPLATE.debugLine());
|
||||
addIfPresent(lines, BailClassCounts.PARTICLE_SPILL.debugLine());
|
||||
}
|
||||
}
|
||||
lines.add("");
|
||||
|
||||
+2
@@ -54,6 +54,7 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
|
||||
import com.gtnewhorizons.angelica.rendering.particles.ParticleInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
@@ -168,6 +169,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
ShadowRenderer.globalTileEntities.clear();
|
||||
TesrBatchRenderer.INSTANCE.clearRetained();
|
||||
ModelPartBatcher.INSTANCE.clear();
|
||||
ParticleInstancer.clear();
|
||||
GpuCulling.onWorldUnload();
|
||||
FfpExtendedAttribs.reset();
|
||||
BopFogBlend.invalidate();
|
||||
|
||||
+3
-3
@@ -80,9 +80,9 @@ public class BlockRenderListManager {
|
||||
}
|
||||
|
||||
|
||||
private static final class BlockMeta {
|
||||
private Block block;
|
||||
private int meta;
|
||||
static final class BlockMeta {
|
||||
Block block;
|
||||
int meta;
|
||||
|
||||
public BlockMeta() {
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package com.gtnewhorizons.angelica.rendering.items;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.rendering.OperationArgs;
|
||||
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager.BlockMeta;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BakedTransformCapture;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public final class DroppedItemInstancer {
|
||||
|
||||
private static final int CAPTURE_BYTES = 64 * 1024;
|
||||
|
||||
private static final ItemPropCache<IconMesh> icons = new ItemPropCache<>(() -> AngelicaConfig.itemRendererCacheSize, IconMesh::new, null);
|
||||
private static final Object2ObjectOpenHashMap<BlockMeta, BlockMesh> blocks = new Object2ObjectOpenHashMap<>();
|
||||
private static final BlockMeta blockKey = new BlockMeta();
|
||||
private static final AngelicaTesrMeshCache.GtnhMeshBackend iconBackend = new AngelicaTesrMeshCache.GtnhMeshBackend();
|
||||
private static final Object[] ICON_ARGS = new Object[8];
|
||||
private static final Object[] GLINT_ARGS = new Object[8];
|
||||
private static final Object[] BLOCK_ARGS = new Object[4];
|
||||
private static BakedTransformCapture blockBackend;
|
||||
private static boolean basePart;
|
||||
|
||||
private static long instanced;
|
||||
private static long glintInstanced;
|
||||
private static long fallback;
|
||||
|
||||
public enum BailReason {
|
||||
INELIGIBLE("items.bail.ineligible"),
|
||||
NO_MATERIAL("items.bail.material"),
|
||||
ISBRH("items.bail.isbrh"),
|
||||
BLOCK_STATE("items.bail.blockState"),
|
||||
NOT_ALLOWED("items.bail.notAllowed"),
|
||||
TEMPLATE("items.bail.template"),
|
||||
QUEUE("items.bail.queue");
|
||||
|
||||
public static final BailReason[] VALUES = values();
|
||||
public final String plotName;
|
||||
|
||||
BailReason(String plotName) {
|
||||
this.plotName = plotName;
|
||||
}
|
||||
}
|
||||
|
||||
private static final long[] bails = new long[BailReason.VALUES.length];
|
||||
|
||||
private DroppedItemInstancer() {}
|
||||
|
||||
public static long statInstanced() {
|
||||
return instanced;
|
||||
}
|
||||
|
||||
public static long statGlintInstanced() {
|
||||
return glintInstanced;
|
||||
}
|
||||
|
||||
public static long statFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static long statBail(BailReason reason) {
|
||||
return bails[reason.ordinal()];
|
||||
}
|
||||
|
||||
private static void bail(BailReason reason) {
|
||||
bails[reason.ordinal()]++;
|
||||
}
|
||||
|
||||
private static boolean glintEligible(ItemStack stack) {
|
||||
return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList() && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance);
|
||||
}
|
||||
|
||||
static TesrMaterial material(ItemStack stack, boolean unfilteredAtlas) {
|
||||
if (stack == null) return null;
|
||||
if (!glintEligible(stack)) {
|
||||
fallback++;
|
||||
bail(BailReason.INELIGIBLE);
|
||||
return null;
|
||||
}
|
||||
final TesrMaterial material = EntityMaterials.itemFromCurrentState(unfilteredAtlas);
|
||||
if (material == null) {
|
||||
fallback++;
|
||||
bail(BailReason.NO_MATERIAL);
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
public static void icon(ItemStack stack, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
final TesrMaterial material = material(stack, true);
|
||||
if (material == null) {
|
||||
basePart = false;
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
return;
|
||||
}
|
||||
basePart = batchIcon(material, t, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
}
|
||||
|
||||
static boolean batchIcon(TesrMaterial material, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (BatchEligibility.batchingAllowed()) {
|
||||
final TemplateBuffer template = iconTemplate(ICON_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
if (template == null) {
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
fallback++;
|
||||
bail(BailReason.TEMPLATE);
|
||||
return false;
|
||||
}
|
||||
if (ModelPartBatcher.INSTANCE.queueTemplate(template, material)) {
|
||||
instanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return true;
|
||||
}
|
||||
fallback++;
|
||||
bail(BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
if (!basePart) {
|
||||
callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
return;
|
||||
}
|
||||
final boolean allowed = BatchEligibility.batchingAllowed();
|
||||
if (allowed && ModelPartBatcher.INSTANCE.isShadowPass()) {
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
}
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (allowed) {
|
||||
final TemplateBuffer template = iconTemplate(GLINT_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
if (template != null && ModelPartBatcher.INSTANCE.queueTemplate(template, EntityMaterials.GLINT)) {
|
||||
glintInstanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
}
|
||||
fallback++;
|
||||
bail(template == null ? BailReason.TEMPLATE : BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
public static void block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas, Operation<Void> original) {
|
||||
final TesrMaterial material = material(stack, unfilteredAtlas);
|
||||
if (material == null) {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
return;
|
||||
}
|
||||
final BailReason stateBail = blockStateBail(rb, block, brightness);
|
||||
if (stateBail != null) {
|
||||
fallback++;
|
||||
bail(stateBail);
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
return;
|
||||
}
|
||||
batchBlock(material, rb, block, meta, brightness, original);
|
||||
}
|
||||
|
||||
static void batchBlock(TesrMaterial material, RenderBlocks rb, Block block, int meta, float brightness, Operation<Void> original) {
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (BatchEligibility.batchingAllowed()) {
|
||||
BlockMesh mesh = lookupBlock(block, meta);
|
||||
if (mesh == null) {
|
||||
mesh = captureBlock(rb, block, meta, brightness, original);
|
||||
} else if (mesh.template() != null) {
|
||||
GLStateManager.glColor4f(mesh.red(), mesh.green(), mesh.blue(), mesh.alpha());
|
||||
}
|
||||
if (mesh.template() == null) {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
fallback++;
|
||||
bail(BailReason.TEMPLATE);
|
||||
return;
|
||||
}
|
||||
if (ModelPartBatcher.INSTANCE.queueTemplate(mesh.template(), material)) {
|
||||
instanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
}
|
||||
fallback++;
|
||||
bail(BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
icons.clear();
|
||||
blocks.clear();
|
||||
blockKey.block = null;
|
||||
basePart = false;
|
||||
if (blockBackend != null) {
|
||||
blockBackend.delete();
|
||||
blockBackend = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void callIcon(Object[] args, Operation<Void> original, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
args[0] = t;
|
||||
args[1] = OperationArgs.boxed(args[1], maxU);
|
||||
args[2] = OperationArgs.boxed(args[2], minV);
|
||||
args[3] = OperationArgs.boxed(args[3], minU);
|
||||
args[4] = OperationArgs.boxed(args[4], maxV);
|
||||
args[5] = OperationArgs.boxed(args[5], width);
|
||||
args[6] = OperationArgs.boxed(args[6], height);
|
||||
args[7] = OperationArgs.boxed(args[7], thickness);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void callBlock(Operation<Void> original, RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final Object[] args = BLOCK_ARGS;
|
||||
args[0] = rb;
|
||||
args[1] = block;
|
||||
args[2] = OperationArgs.boxed(args[2], meta);
|
||||
args[3] = OperationArgs.boxed(args[3], brightness);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
args[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TemplateBuffer iconTemplate(Object[] args, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
final IconMesh hit = icons.get(maxU, minV, minU, maxV, width, height, thickness);
|
||||
if (hit != null) return hit.template;
|
||||
final Tessellator capture = iconBackend.beginCapture(DefaultVertexFormat.POSITION_TEXTURE_NORMAL);
|
||||
final TemplateBuffer template;
|
||||
try {
|
||||
callIcon(args, original, capture, maxU, minV, minU, maxV, width, height, thickness);
|
||||
} finally {
|
||||
template = iconBackend.endCaptureToTemplate();
|
||||
}
|
||||
if (template == null) return null;
|
||||
icons.insert().template = template;
|
||||
return template;
|
||||
}
|
||||
|
||||
private static BlockMesh captureBlock(RenderBlocks rb, Block block, int meta, float brightness, Operation<Void> original) {
|
||||
if (blockBackend == null) {
|
||||
blockBackend = new BakedTransformCapture(CAPTURE_BYTES);
|
||||
}
|
||||
final TemplateBuffer template;
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
blockBackend.begin();
|
||||
try {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
} finally {
|
||||
template = blockBackend.end();
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
final Color4 color = GLStateManager.getColor();
|
||||
final BlockMesh mesh = new BlockMesh(template, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
blocks.put(new BlockMeta(block, meta), mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static BlockMesh lookupBlock(Block block, int meta) {
|
||||
blockKey.block = block;
|
||||
blockKey.meta = meta;
|
||||
return blocks.get(blockKey);
|
||||
}
|
||||
|
||||
private static BailReason blockStateBail(RenderBlocks renderBlocks, Block block, float brightness) {
|
||||
if (BlockRenderListManager.isISBRH(block.getRenderType())) return BailReason.ISBRH;
|
||||
if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint || renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth | renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class IconMesh {
|
||||
TemplateBuffer template;
|
||||
}
|
||||
|
||||
private record BlockMesh(TemplateBuffer template, float red, float green, float blue, float alpha) {}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.gtnewhorizons.angelica.rendering.items;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ItemPropCache<V> {
|
||||
|
||||
private static final int EXPIRY_TICKS = 1_200;
|
||||
|
||||
private final Object2ObjectLinkedOpenHashMap<ItemProp, Slot<V>> cache = new Object2ObjectLinkedOpenHashMap<>(64);
|
||||
private final ItemProp key = new ItemProp();
|
||||
private final IntSupplier capacity;
|
||||
private final Supplier<V> factory;
|
||||
private final Consumer<V> disposer;
|
||||
private int smallestExpiry;
|
||||
|
||||
public ItemPropCache(IntSupplier capacity, Supplier<V> factory, Consumer<V> disposer) {
|
||||
this.capacity = capacity;
|
||||
this.factory = factory;
|
||||
this.disposer = disposer;
|
||||
}
|
||||
|
||||
public V get(float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness) {
|
||||
key.set(minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
if (cache.isEmpty()) return null;
|
||||
final Slot<V> slot = cache.getAndMoveToLast(key);
|
||||
if (slot == null) return null;
|
||||
final int time = elapsedTicks();
|
||||
slot.expiry = time + EXPIRY_TICKS;
|
||||
if (time > smallestExpiry && time > (smallestExpiry = cache.get(cache.firstKey()).expiry + 20)) {
|
||||
dispose(cache.removeFirst());
|
||||
if (!cache.isEmpty()) {
|
||||
smallestExpiry = cache.get(cache.firstKey()).expiry + 20;
|
||||
}
|
||||
}
|
||||
return slot.value;
|
||||
}
|
||||
|
||||
public V insert() {
|
||||
final Slot<V> slot;
|
||||
if (cache.size() >= capacity.getAsInt()) {
|
||||
final ItemProp oldest = cache.firstKey();
|
||||
slot = cache.removeFirst();
|
||||
oldest.set(key);
|
||||
cache.put(oldest, slot);
|
||||
} else {
|
||||
slot = new Slot<>();
|
||||
slot.value = factory.get();
|
||||
cache.put(new ItemProp(key), slot);
|
||||
}
|
||||
slot.expiry = elapsedTicks() + EXPIRY_TICKS;
|
||||
return slot.value;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (Slot<V> slot : cache.values()) {
|
||||
dispose(slot);
|
||||
}
|
||||
cache.clear();
|
||||
smallestExpiry = 0;
|
||||
}
|
||||
|
||||
private void dispose(Slot<V> slot) {
|
||||
if (disposer != null && slot.value != null) {
|
||||
disposer.accept(slot.value);
|
||||
}
|
||||
}
|
||||
|
||||
private static int elapsedTicks() {
|
||||
return Minecraft.getMinecraft().thePlayer.ticksExisted;
|
||||
}
|
||||
|
||||
private static final class Slot<V> {
|
||||
private V value;
|
||||
private int expiry;
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
static final class ItemProp {
|
||||
private float minU;
|
||||
private float minV;
|
||||
private float maxU;
|
||||
private float maxV;
|
||||
private int widthSubdivisions;
|
||||
private int heightSubdivisions;
|
||||
private float thickness;
|
||||
|
||||
ItemProp(ItemProp old) {
|
||||
set(old);
|
||||
}
|
||||
|
||||
void set(ItemProp other) {
|
||||
set(other.minU, other.minV, other.maxU, other.maxV, other.widthSubdivisions, other.heightSubdivisions, other.thickness);
|
||||
}
|
||||
|
||||
void set(float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness) {
|
||||
this.minU = minU;
|
||||
this.minV = minV;
|
||||
this.maxU = maxU;
|
||||
this.maxV = maxV;
|
||||
this.widthSubdivisions = widthSubdivisions;
|
||||
this.heightSubdivisions = heightSubdivisions;
|
||||
this.thickness = thickness;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-94
@@ -37,9 +37,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -52,52 +49,16 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
public class ItemRenderListManager {
|
||||
// Least used element is at position 0. This is in theory slightly faster.
|
||||
private static final Object2ObjectLinkedOpenHashMap<ItemProp, CachedVBO> vboCache = new Object2ObjectLinkedOpenHashMap<>(64);
|
||||
|
||||
// 1 minute
|
||||
private static final int EXPIRY_TICKS = 1_200;
|
||||
private static int smallestExpiry;
|
||||
|
||||
private static final ItemProp prop = new ItemProp();
|
||||
private static final ItemPropCache<CachedVBO> vboCache = new ItemPropCache<>(() -> AngelicaConfig.itemRendererCacheSize, CachedVBO::new, CachedVBO::delete);
|
||||
|
||||
public static CachedVBO pre(float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness) {
|
||||
prop.set(minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
|
||||
if (!vboCache.isEmpty()) {
|
||||
|
||||
final CachedVBO vbo = vboCache.getAndMoveToLast(prop);
|
||||
|
||||
if (vbo != null) {
|
||||
final int time = getElapsedTicks();
|
||||
vbo.render(time);
|
||||
|
||||
// Prevent constant map lookups by only storing the expiry of the least recently used entry
|
||||
if (time > smallestExpiry && time > (smallestExpiry = (vboCache.get(vboCache.firstKey()).expiry + 20))) {
|
||||
vboCache.removeFirst().delete();
|
||||
if (!vboCache.isEmpty()) {
|
||||
smallestExpiry = vboCache.get(vboCache.firstKey()).expiry + 20;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
final CachedVBO hit = vboCache.get(minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
if (hit != null) {
|
||||
hit.render();
|
||||
return null;
|
||||
}
|
||||
|
||||
final CachedVBO vbo;
|
||||
if (vboCache.size() >= AngelicaConfig.itemRendererCacheSize) {
|
||||
final ItemProp oldestProp = vboCache.firstKey();
|
||||
vbo = vboCache.removeFirst();
|
||||
oldestProp.set(prop);
|
||||
vboCache.put(oldestProp, vbo);
|
||||
} else {
|
||||
vbo = new CachedVBO();
|
||||
vboCache.put(new ItemProp(prop), vbo);
|
||||
}
|
||||
|
||||
vbo.expiry = getElapsedTicks() + EXPIRY_TICKS;
|
||||
|
||||
return vbo;
|
||||
return vboCache.insert();
|
||||
}
|
||||
|
||||
public static void post(DirectTessellator tessellator, CachedVBO vbo) {
|
||||
@@ -106,10 +67,6 @@ public class ItemRenderListManager {
|
||||
vbo.vertexBuffer.render();
|
||||
}
|
||||
|
||||
private static int getElapsedTicks() {
|
||||
return Minecraft.getMinecraft().thePlayer.ticksExisted;
|
||||
}
|
||||
|
||||
public static void registerReloadListener(){
|
||||
IReloadableResourceManager resourceManager = (IReloadableResourceManager) Minecraft.getMinecraft()
|
||||
.getResourceManager();
|
||||
@@ -117,16 +74,12 @@ public class ItemRenderListManager {
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
for (CachedVBO value : vboCache.values()) {
|
||||
value.delete();
|
||||
}
|
||||
vboCache.clear();
|
||||
}
|
||||
|
||||
public static final class CachedVBO {
|
||||
private final IVertexArrayObject vertexBuffer;
|
||||
private final IndexBuffer ebo;
|
||||
private int expiry;
|
||||
private int extVbo = 0;
|
||||
|
||||
public CachedVBO() {
|
||||
@@ -195,9 +148,8 @@ public class ItemRenderListManager {
|
||||
extVbo = 0;
|
||||
}
|
||||
|
||||
private void render(int elapsedTicks) {
|
||||
private void render() {
|
||||
vertexBuffer.render();
|
||||
expiry = elapsedTicks + EXPIRY_TICKS;
|
||||
}
|
||||
|
||||
private void delete() {
|
||||
@@ -210,43 +162,4 @@ public class ItemRenderListManager {
|
||||
}
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
private static final class ItemProp {
|
||||
private float minU;
|
||||
private float minV;
|
||||
private float maxU;
|
||||
private float maxV;
|
||||
private int widthSubdivisions;
|
||||
private int heightSubdivisions;
|
||||
private float thickness;
|
||||
|
||||
public ItemProp(ItemProp old) {
|
||||
set(
|
||||
old.minU, old.minV,
|
||||
old.maxU, old.maxV,
|
||||
old.widthSubdivisions, old.heightSubdivisions,
|
||||
old.thickness
|
||||
);
|
||||
}
|
||||
|
||||
public void set(ItemProp other) {
|
||||
set(
|
||||
other.minU, other.minV,
|
||||
other.maxU, other.maxV,
|
||||
other.widthSubdivisions, other.heightSubdivisions,
|
||||
other.thickness
|
||||
);
|
||||
}
|
||||
|
||||
public void set(float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness) {
|
||||
this.minU = minU;
|
||||
this.minV = minV;
|
||||
this.maxU = maxU;
|
||||
this.maxV = maxV;
|
||||
this.widthSubdivisions = widthSubdivisions;
|
||||
this.heightSubdivisions = heightSubdivisions;
|
||||
this.thickness = thickness;
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class BopParticleDescriptors {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final String PIXIE_TRAIL_CLASS = "biomesoplenty.client.particles.EntityPixieTrailFX";
|
||||
private static final ResourceLocation PIXIE_TRAIL_TEXTURE = new ResourceLocation("biomesoplenty:textures/particles/pixietrail.png");
|
||||
|
||||
private static final int UNRESOLVED = -1;
|
||||
|
||||
private static int pixieTrailTexture = UNRESOLVED;
|
||||
|
||||
public static final ParticleDescriptor PIXIE_TRAIL = (fx, partialTicks, out, state) -> {
|
||||
final int texture = pixieTrailTexture();
|
||||
if (texture == 0) return false;
|
||||
fx.particleScale = fx.particleScale * ParticleQuads.ramp(fx.particleAge, fx.particleMaxAge, partialTicks);
|
||||
ParticleQuads.vanillaQuad(fx, partialTicks, fx.particleScale, out);
|
||||
out.colorABGR = ParticleQuads.packColor(fx.particleRed, fx.particleGreen, fx.particleBlue, 1.0F);
|
||||
out.brightness = 175;
|
||||
state.texture = texture;
|
||||
state.blend = true;
|
||||
state.blendSrc = GL11.GL_SRC_ALPHA;
|
||||
state.blendDst = GL11.GL_ONE;
|
||||
state.blendSrcAlpha = GL11.GL_SRC_ALPHA;
|
||||
state.blendDstAlpha = GL11.GL_ONE;
|
||||
state.depthMask = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
private BopParticleDescriptors() {}
|
||||
|
||||
public static void registerAll() {
|
||||
ParticleDescriptorRegistry.register(PIXIE_TRAIL_CLASS, PIXIE_TRAIL);
|
||||
}
|
||||
|
||||
public static void invalidate() {
|
||||
pixieTrailTexture = UNRESOLVED;
|
||||
}
|
||||
|
||||
private static int pixieTrailTexture() {
|
||||
int id = pixieTrailTexture;
|
||||
if (id != UNRESOLVED) return id;
|
||||
final TextureManager manager = Minecraft.getMinecraft().getTextureManager();
|
||||
ITextureObject texture = manager.getTexture(PIXIE_TRAIL_TEXTURE);
|
||||
if (texture == null) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
manager.bindTexture(PIXIE_TRAIL_TEXTURE);
|
||||
texture = manager.getTexture(PIXIE_TRAIL_TEXTURE);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
}
|
||||
id = texture == null ? 0 : texture.getGlTextureId();
|
||||
if (id == 0) LOG.warn("Pixie trail texture {} is unavailable; those particles will not draw", PIXIE_TRAIL_TEXTURE);
|
||||
pixieTrailTexture = id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class ParticleCaptureTessellator extends Tessellator {
|
||||
|
||||
public static final int MAX_VERTICES = 4;
|
||||
|
||||
private final float[] verts = new float[MAX_VERTICES * 5];
|
||||
private final ParticleRenderState state = new ParticleRenderState();
|
||||
private final ParticleRenderState scratch = new ParticleRenderState();
|
||||
private int captured;
|
||||
private boolean spilled;
|
||||
private boolean uvSet;
|
||||
private float pendingU;
|
||||
private float pendingV;
|
||||
private boolean closed;
|
||||
private Matrix4fc layerModelView;
|
||||
private Tessellator run;
|
||||
|
||||
public void begin(int startBrightness, Tessellator runTessellator, Matrix4fc layerModelView) {
|
||||
this.layerModelView = layerModelView;
|
||||
this.run = runTessellator;
|
||||
this.captured = 0;
|
||||
this.spilled = false;
|
||||
this.closed = false;
|
||||
this.uvSet = false;
|
||||
this.pendingU = 0.0f;
|
||||
this.pendingV = 0.0f;
|
||||
this.brightness = startBrightness;
|
||||
this.hasBrightness = true;
|
||||
this.color = runTessellator.hasColor ? runTessellator.color : 0xFFFFFFFF;
|
||||
this.hasColor = runTessellator.hasColor;
|
||||
this.hasTexture = false;
|
||||
this.hasNormals = false;
|
||||
this.isColorDisabled = false;
|
||||
this.xOffset = 0.0D;
|
||||
this.yOffset = 0.0D;
|
||||
this.zOffset = 0.0D;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
run = null;
|
||||
layerModelView = null;
|
||||
captured = 0;
|
||||
}
|
||||
|
||||
public boolean spilled() {
|
||||
return spilled;
|
||||
}
|
||||
|
||||
public void propagateColor() {
|
||||
if (spilled || closed || !hasColor || run == null) return;
|
||||
run.color = color;
|
||||
run.hasColor = true;
|
||||
}
|
||||
|
||||
public int captured() {
|
||||
return captured;
|
||||
}
|
||||
|
||||
public float[] vertices() {
|
||||
return verts;
|
||||
}
|
||||
|
||||
public int capturedBrightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
public ParticleRenderState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public int capturedColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void replayInto(Tessellator t) {
|
||||
t.setBrightness(brightness);
|
||||
if (hasColor) {
|
||||
t.setColorRGBA(color & 255, (color >> 8) & 255, (color >> 16) & 255, (color >>> 24) & 255);
|
||||
}
|
||||
if (uvSet) {
|
||||
t.setTextureUV(pendingU, pendingV);
|
||||
}
|
||||
for (int i = 0, o = 0; i < captured; i++, o += 5) {
|
||||
t.addVertexWithUV(verts[o], verts[o + 1], verts[o + 2], verts[o + 3], verts[o + 4]);
|
||||
}
|
||||
}
|
||||
|
||||
private void spill() {
|
||||
if (spilled) return;
|
||||
spilled = true;
|
||||
if (closed) {
|
||||
scratch.sample();
|
||||
state.apply();
|
||||
final boolean wasDrawing = run.isDrawing;
|
||||
if (!wasDrawing) run.startDrawingQuads();
|
||||
replayInto(run);
|
||||
run.draw();
|
||||
if (wasDrawing) run.startDrawingQuads();
|
||||
scratch.apply();
|
||||
} else {
|
||||
replayInto(run);
|
||||
}
|
||||
captured = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextureUV(double u, double v) {
|
||||
if (spilled) {
|
||||
run.setTextureUV(u, v);
|
||||
return;
|
||||
}
|
||||
if (closed) {
|
||||
run.setTextureUV(u, v);
|
||||
pendingU = (float) u;
|
||||
pendingV = (float) v;
|
||||
return;
|
||||
}
|
||||
hasTexture = true;
|
||||
uvSet = true;
|
||||
pendingU = (float) u;
|
||||
pendingV = (float) v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVertexWithUV(double x, double y, double z, double u, double v) {
|
||||
setTextureUV(u, v);
|
||||
addVertex(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVertex(double x, double y, double z) {
|
||||
if (spilled) {
|
||||
run.addVertex(x, y, z);
|
||||
return;
|
||||
}
|
||||
boolean overflow = !uvSet || closed || captured == MAX_VERTICES;
|
||||
if (!overflow && captured == 0) {
|
||||
if (GLStateManager.getModelViewMatrix().equals(layerModelView)) state.sample();
|
||||
else overflow = true;
|
||||
}
|
||||
if (overflow) {
|
||||
spill();
|
||||
if (uvSet) run.addVertexWithUV(x, y, z, pendingU, pendingV);
|
||||
else run.addVertex(x, y, z);
|
||||
return;
|
||||
}
|
||||
final int o = captured * 5;
|
||||
verts[o] = (float) (x + xOffset);
|
||||
verts[o + 1] = (float) (y + yOffset);
|
||||
verts[o + 2] = (float) (z + zOffset);
|
||||
verts[o + 3] = pendingU;
|
||||
verts[o + 4] = pendingV;
|
||||
captured++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorRGBA(int red, int green, int blue, int alpha) {
|
||||
if (spilled || closed) {
|
||||
run.setColorRGBA(red, green, blue, alpha);
|
||||
return;
|
||||
}
|
||||
final int r = Math.clamp(red, 0, 255);
|
||||
final int g = Math.clamp(green, 0, 255);
|
||||
final int b = Math.clamp(blue, 0, 255);
|
||||
final int a = Math.clamp(alpha, 0, 255);
|
||||
final int packed = (a << 24) | (b << 16) | (g << 8) | r;
|
||||
if (hasColor && packed != color && captured > 0) {
|
||||
spill();
|
||||
run.setColorRGBA(red, green, blue, alpha);
|
||||
return;
|
||||
}
|
||||
hasColor = true;
|
||||
color = packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrightness(int value) {
|
||||
if (spilled || closed) {
|
||||
run.setBrightness(value);
|
||||
return;
|
||||
}
|
||||
hasBrightness = true;
|
||||
brightness = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNormal(float x, float y, float z) {
|
||||
if (!closed) spill();
|
||||
run.setNormal(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableColor() {
|
||||
if (!closed) spill();
|
||||
run.disableColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrawingQuads() {
|
||||
if (!spilled && !closed && captured != 0) spill();
|
||||
run.startDrawingQuads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrawing(int mode) {
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
startDrawingQuads();
|
||||
return;
|
||||
}
|
||||
spill();
|
||||
run.startDrawing(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int draw() {
|
||||
if (!spilled && !closed) {
|
||||
if (captured == MAX_VERTICES) {
|
||||
closed = true;
|
||||
} else if (captured != 0) {
|
||||
spill();
|
||||
}
|
||||
}
|
||||
return run.draw();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
|
||||
public final class ParticleCulling {
|
||||
|
||||
private ParticleCulling() {}
|
||||
|
||||
public static boolean visible(CeleritasWorldRenderer renderer, double x, double y, double z) {
|
||||
return renderer == null || renderer.isPointVisible(x, y, z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
|
||||
public interface ParticleDescriptor {
|
||||
|
||||
boolean describe(EntityFX fx, float partialTicks, ParticleParams out, ParticleRenderState state);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class ParticleDescriptorRegistry {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/Particles");
|
||||
|
||||
private static final String[] RENDER_PARTICLE_NAMES = { "renderParticle", "func_70539_a" };
|
||||
|
||||
public static final ParticleDescriptor CAPTURE = (fx, partialTicks, out, state) -> false;
|
||||
public static final ParticleDescriptor VANILLA = (fx, partialTicks, out, state) -> {
|
||||
ParticleQuads.vanillaQuad(fx, partialTicks, fx.particleScale, out);
|
||||
return true;
|
||||
};
|
||||
|
||||
private static final Object2ObjectOpenHashMap<String, ParticleDescriptor> BY_NAME = new Object2ObjectOpenHashMap<>();
|
||||
private static final Reference2ObjectOpenHashMap<Class<?>, ParticleDescriptor> BY_CLASS = new Reference2ObjectOpenHashMap<>();
|
||||
|
||||
private static Class<?> lastClass;
|
||||
private static ParticleDescriptor lastDescriptor;
|
||||
|
||||
static {
|
||||
VanillaParticleDescriptors.registerAll();
|
||||
BopParticleDescriptors.registerAll();
|
||||
}
|
||||
|
||||
private ParticleDescriptorRegistry() {}
|
||||
|
||||
public static void register(String className, ParticleDescriptor descriptor) {
|
||||
BY_NAME.put(className, descriptor);
|
||||
clearCache();
|
||||
}
|
||||
|
||||
public static ParticleDescriptor forClass(Class<?> cls) {
|
||||
if (cls == lastClass) return lastDescriptor;
|
||||
ParticleDescriptor d = BY_CLASS.get(cls);
|
||||
if (d == null) {
|
||||
d = resolve(cls);
|
||||
BY_CLASS.put(cls, d);
|
||||
}
|
||||
lastClass = cls;
|
||||
lastDescriptor = d;
|
||||
return d;
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
BY_CLASS.clear();
|
||||
lastClass = null;
|
||||
lastDescriptor = null;
|
||||
}
|
||||
|
||||
private static ParticleDescriptor resolve(Class<?> cls) {
|
||||
final ParticleDescriptor registered = BY_NAME.get(cls.getName());
|
||||
if (registered != null) return registered;
|
||||
|
||||
final Method render = renderParticleMethod(cls);
|
||||
if (render == null) {
|
||||
LOGGER.warn("Could not locate renderParticle on particle class {}, using the capture path", cls.getName());
|
||||
return CAPTURE;
|
||||
}
|
||||
return render.getDeclaringClass() == EntityFX.class ? VANILLA : CAPTURE;
|
||||
}
|
||||
|
||||
private static Method renderParticleMethod(Class<?> cls) {
|
||||
for (String name : RENDER_PARTICLE_NAMES) {
|
||||
try {
|
||||
return cls.getMethod(name, Tessellator.class, float.class, float.class, float.class, float.class, float.class, float.class);
|
||||
} catch (NoSuchMethodException ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.client.rendering.DeferredDrawBatcher;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleInstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleQuadMesh;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.profiling.BailClassCounts;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.OperationArgs;
|
||||
import com.gtnewhorizons.angelica.rendering.ParticleRunSplitter;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.InstanceRing;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.MeshBuffer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
|
||||
public final class ParticleInstancer {
|
||||
|
||||
private static final int STRIDE = ParticleInstancedAttribs.STRIDE;
|
||||
private static final int INITIAL_BYTES = 256 * STRIDE;
|
||||
|
||||
private static final ParticleCaptureTessellator CAPTURE = new ParticleCaptureTessellator();
|
||||
private static final ParticleParams SCRATCH = new ParticleParams();
|
||||
private static final ParticleRenderState STATE = new ParticleRenderState();
|
||||
private static final ParticleRenderState RESTORE = new ParticleRenderState();
|
||||
private static final Matrix4f LAYER_MV = new Matrix4f();
|
||||
private static final Object[] ORIGINAL_ARGS = new Object[8];
|
||||
|
||||
private static final ObjectArrayList<Group> GROUP_POOL = new ObjectArrayList<>();
|
||||
private static int groupCount;
|
||||
|
||||
private static final class Group {
|
||||
final ParticleRenderState state = new ParticleRenderState();
|
||||
ByteBuffer list;
|
||||
boolean translucent;
|
||||
}
|
||||
|
||||
private static boolean layerActive;
|
||||
private static boolean layerStarted;
|
||||
private static DeferredWorldRenderingPipeline unavailableFor;
|
||||
private static DeferredWorldRenderingPipeline deferred;
|
||||
private static Tessellator run;
|
||||
|
||||
private static float rotX;
|
||||
private static float rotXZ;
|
||||
private static float rotZ;
|
||||
private static float rotYZ;
|
||||
private static float rotXY;
|
||||
|
||||
private static long direct;
|
||||
private static long captured;
|
||||
private static long spilled;
|
||||
private static long undecodable;
|
||||
private static long draws;
|
||||
|
||||
private ParticleInstancer() {}
|
||||
|
||||
public static long getDirect() {
|
||||
return direct;
|
||||
}
|
||||
|
||||
public static long getCaptured() {
|
||||
return captured;
|
||||
}
|
||||
|
||||
public static long getDraws() {
|
||||
return draws;
|
||||
}
|
||||
|
||||
public static long getSpilled() {
|
||||
return spilled;
|
||||
}
|
||||
|
||||
public static long getUndecodable() {
|
||||
return undecodable;
|
||||
}
|
||||
|
||||
private static void beginLayer() {
|
||||
layerActive = false;
|
||||
layerStarted = true;
|
||||
final SodiumGameOptions options = ClientProxy.options();
|
||||
if (options == null || !options.advanced.enableDeferredBatching) return;
|
||||
|
||||
deferred = pipeline();
|
||||
if (available()) {
|
||||
layerActive = true;
|
||||
groupCount = 0;
|
||||
LAYER_MV.set(GLStateManager.getModelViewMatrix());
|
||||
} else {
|
||||
deferred = null;
|
||||
DeferredDrawBatcher.enter();
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderParticle(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
if (!layerStarted) beginLayer();
|
||||
if (!layerActive) {
|
||||
call(fx, runTessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
return;
|
||||
}
|
||||
renderActive(fx, runTessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
}
|
||||
|
||||
private static void renderActive(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
|
||||
run = runTessellator;
|
||||
rotX = rotationX;
|
||||
rotXZ = rotationXZ;
|
||||
rotZ = rotationZ;
|
||||
rotYZ = rotationYZ;
|
||||
rotXY = rotationXY;
|
||||
|
||||
final ParticleDescriptor descriptor = ParticleDescriptorRegistry.forClass(fx.getClass());
|
||||
if (descriptor != ParticleDescriptorRegistry.CAPTURE) {
|
||||
STATE.sample();
|
||||
SCRATCH.brightness = runTessellator.brightness;
|
||||
if (!descriptor.describe(fx, partialTicks, SCRATCH, STATE)) return;
|
||||
|
||||
runTessellator.color = SCRATCH.colorABGR;
|
||||
runTessellator.hasColor = true;
|
||||
emit(groupFor(STATE, deferred != null && ParticleRunSplitter.currentRunTranslucent()));
|
||||
direct++;
|
||||
return;
|
||||
}
|
||||
|
||||
CAPTURE.begin(runTessellator.brightness, runTessellator, LAYER_MV);
|
||||
call(fx, CAPTURE, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
CAPTURE.propagateColor();
|
||||
|
||||
if (CAPTURE.spilled()) {
|
||||
spilled++;
|
||||
if (Tracy.ENABLED) BailClassCounts.PARTICLE_SPILL.add(fx.getClass());
|
||||
return;
|
||||
}
|
||||
if (CAPTURE.captured() == 0) return;
|
||||
|
||||
if (!ParticleQuadDecoder.decode(CAPTURE.vertices(), CAPTURE.captured(), rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, SCRATCH)) {
|
||||
CAPTURE.replayInto(runTessellator);
|
||||
undecodable++;
|
||||
return;
|
||||
}
|
||||
|
||||
SCRATCH.colorABGR = CAPTURE.capturedColor();
|
||||
SCRATCH.brightness = CAPTURE.capturedBrightness();
|
||||
emit(groupFor(CAPTURE.state(), deferred != null && ParticleRunSplitter.currentRunTranslucent()));
|
||||
captured++;
|
||||
}
|
||||
|
||||
private static void emit(Group group) {
|
||||
final ByteBuffer list = MeshBuffer.ensureCapacity(group.list, STRIDE, true);
|
||||
group.list = list;
|
||||
ParticleInstancedAttribs.writeInstance(memAddress0(list) + list.position(), SCRATCH.centerX, SCRATCH.centerY, SCRATCH.centerZ, SCRATCH.half, SCRATCH.u0, SCRATCH.v0, SCRATCH.u1, SCRATCH.v1, SCRATCH.colorABGR, SCRATCH.brightness);
|
||||
list.position(list.position() + STRIDE);
|
||||
}
|
||||
|
||||
public static void endLayer() {
|
||||
layerStarted = false;
|
||||
if (!layerActive) {
|
||||
if (DeferredDrawBatcher.isActive()) DeferredDrawBatcher.exitAndFlush();
|
||||
return;
|
||||
}
|
||||
layerActive = false;
|
||||
|
||||
if (groupCount > 0) {
|
||||
RESTORE.sample();
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
final Group group = GROUP_POOL.get(i);
|
||||
if (!group.translucent) flush(group);
|
||||
}
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
final Group group = GROUP_POOL.get(i);
|
||||
if (group.translucent) flush(group);
|
||||
}
|
||||
RESTORE.apply();
|
||||
if (deferred != null) {
|
||||
GbufferPrograms.setTranslucencyDeclaration(ParticleRunSplitter.currentRunTranslucent());
|
||||
}
|
||||
ring().postDraw();
|
||||
groupCount = 0;
|
||||
}
|
||||
|
||||
deferred = null;
|
||||
run = null;
|
||||
}
|
||||
|
||||
private static Group groupFor(ParticleRenderState state, boolean translucent) {
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
final Group g = GROUP_POOL.get(i);
|
||||
if (g.translucent == translucent && g.state.matches(state)) return g;
|
||||
}
|
||||
if (groupCount == GROUP_POOL.size()) GROUP_POOL.add(new Group());
|
||||
final Group g = GROUP_POOL.get(groupCount++);
|
||||
g.state.set(state);
|
||||
g.translucent = translucent;
|
||||
if (g.list != null) g.list.clear();
|
||||
g.list = MeshBuffer.ensureCapacity(g.list, INITIAL_BYTES, true);
|
||||
return g;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
layerActive = false;
|
||||
layerStarted = false;
|
||||
deferred = null;
|
||||
run = null;
|
||||
unavailableFor = null;
|
||||
groupCount = 0;
|
||||
for (int i = 0; i < GROUP_POOL.size(); i++) {
|
||||
GROUP_POOL.get(i).list = null;
|
||||
}
|
||||
GROUP_POOL.clear();
|
||||
CAPTURE.release();
|
||||
ParticleQuadMesh.delete();
|
||||
ParticleDescriptorRegistry.clearCache();
|
||||
}
|
||||
|
||||
private static void call(EntityFX fx, Tessellator target, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
final Object[] args = ORIGINAL_ARGS;
|
||||
args[0] = fx;
|
||||
args[1] = target;
|
||||
args[2] = OperationArgs.boxed(args[2], partialTicks);
|
||||
args[3] = OperationArgs.boxed(args[3], rotationX);
|
||||
args[4] = OperationArgs.boxed(args[4], rotationXZ);
|
||||
args[5] = OperationArgs.boxed(args[5], rotationZ);
|
||||
args[6] = OperationArgs.boxed(args[6], rotationYZ);
|
||||
args[7] = OperationArgs.boxed(args[7], rotationXY);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
args[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean available() {
|
||||
if (deferred != null && deferred == unavailableFor) return false;
|
||||
if (!ShaderManager.getInstance().isEnabled()) return false;
|
||||
return deferred == null || deferred.supportsInstancing(Instancing.PARTICLE);
|
||||
}
|
||||
|
||||
private static DeferredWorldRenderingPipeline pipeline() {
|
||||
if (!Iris.enabled) return null;
|
||||
return Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline pipeline ? pipeline : null;
|
||||
}
|
||||
|
||||
private static InstanceRing ring() {
|
||||
return TesrBatchRenderer.INSTANCE.instanceRing();
|
||||
}
|
||||
|
||||
private static void flush(Group group) {
|
||||
final ByteBuffer list = group.list;
|
||||
final boolean listTranslucent = group.translucent;
|
||||
final int count = list.position() / STRIDE;
|
||||
if (count == 0) return;
|
||||
group.state.apply();
|
||||
list.flip();
|
||||
|
||||
if (deferred != null) {
|
||||
GbufferPrograms.setTranslucencyDeclaration(listTranslucent);
|
||||
deferred.rebindCurrentPass();
|
||||
if (!deferred.hasInstancedVariant(Instancing.PARTICLE)) {
|
||||
unavailableFor = deferred;
|
||||
drawInstancesCpu(group.state, list, count, listTranslucent);
|
||||
list.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final long base = ring().upload(list, STRIDE);
|
||||
list.clear();
|
||||
|
||||
if (deferred != null) {
|
||||
deferred.bindInstancedVariant(Instancing.PARTICLE);
|
||||
issueDraw(base, count, false);
|
||||
deferred.rebindCurrentPass();
|
||||
} else {
|
||||
issueDraw(base, count, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void issueDraw(long base, int count, boolean ffp) {
|
||||
ParticleQuadMesh.update(rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
|
||||
if (extHandler != null && extHandler.wantsExtended()) {
|
||||
FfpExtendedAttribs.setNeutralCurrentValues();
|
||||
}
|
||||
GLStateManager.glBindVertexArray(ParticleQuadMesh.vao());
|
||||
VAOManager.setCurrentVertexFlags(ParticleQuadMesh.VERTEX_FLAGS);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, ring().bufferId());
|
||||
ParticleInstancedAttribs.pointInstanceAttribs(base);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
if (ffp) GLStateManager.ffpInstancing = Instancing.PARTICLE;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, count);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
draws++;
|
||||
}
|
||||
|
||||
private static void drawInstancesCpu(ParticleRenderState state, ByteBuffer list, int count, boolean listTranslucent) {
|
||||
final Tessellator t = run;
|
||||
RESTORE.apply();
|
||||
GbufferPrograms.setTranslucencyDeclaration(ParticleRunSplitter.currentRunTranslucent());
|
||||
t.draw();
|
||||
t.startDrawingQuads();
|
||||
GbufferPrograms.setTranslucencyDeclaration(listTranslucent);
|
||||
state.apply();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int base = i * STRIDE;
|
||||
final float cx = list.getFloat(base);
|
||||
final float cy = list.getFloat(base + 4);
|
||||
final float cz = list.getFloat(base + 8);
|
||||
final float half = list.getFloat(base + 12);
|
||||
final float u0 = list.getFloat(base + ParticleInstancedAttribs.OFFSET_UV);
|
||||
final float v0 = list.getFloat(base + ParticleInstancedAttribs.OFFSET_UV + 4);
|
||||
final float u1 = list.getFloat(base + ParticleInstancedAttribs.OFFSET_UV + 8);
|
||||
final float v1 = list.getFloat(base + ParticleInstancedAttribs.OFFSET_UV + 12);
|
||||
final int color = list.getInt(base + ParticleInstancedAttribs.OFFSET_COLOR);
|
||||
final int light = (int) list.getFloat(base + ParticleInstancedAttribs.OFFSET_LIGHTMAP) | ((int) list.getFloat(base + ParticleInstancedAttribs.OFFSET_LIGHTMAP + 4) << 16);
|
||||
|
||||
t.setBrightness(light);
|
||||
t.setColorRGBA(color & 255, (color >> 8) & 255, (color >> 16) & 255, (color >>> 24) & 255);
|
||||
for (int c = 0; c < ParticleQuadMesh.VERTEX_COUNT; c++) {
|
||||
final float a = ParticleQuadMesh.cornerA(c);
|
||||
final float b = ParticleQuadMesh.cornerB(c);
|
||||
emitCorner(t, cx, cy, cz, half, a, b, a < 0.0f ? u0 : u1, b < 0.0f ? v0 : v1);
|
||||
}
|
||||
}
|
||||
t.draw();
|
||||
t.startDrawingQuads();
|
||||
}
|
||||
|
||||
private static void emitCorner(Tessellator t, float cx, float cy, float cz, float half, float a, float b, float u, float v) {
|
||||
final float ah = a * half;
|
||||
final float bh = b * half;
|
||||
t.addVertexWithUV(cx + ParticleQuadMesh.offsetX(ah, bh, rotX, rotYZ), cy + ParticleQuadMesh.offsetY(bh, rotXZ), cz + ParticleQuadMesh.offsetZ(ah, bh, rotZ, rotXY), u, v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
public final class ParticleParams {
|
||||
|
||||
public float centerX;
|
||||
public float centerY;
|
||||
public float centerZ;
|
||||
public float half;
|
||||
public float u0;
|
||||
public float v0;
|
||||
public float u1;
|
||||
public float v1;
|
||||
public int colorABGR;
|
||||
public int brightness;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleQuadMesh;
|
||||
|
||||
public final class ParticleQuadDecoder {
|
||||
|
||||
private ParticleQuadDecoder() {}
|
||||
|
||||
public static boolean decode(float[] verts, int count, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY, ParticleParams out) {
|
||||
if (count != 4) return false;
|
||||
|
||||
final float sx = rotX + rotYZ;
|
||||
final float sy = rotXZ;
|
||||
final float sz = rotZ + rotXY;
|
||||
final float denom = sx * sx + sy * sy + sz * sz;
|
||||
if (denom <= 1.0e-12f) return false;
|
||||
|
||||
final float cx = (verts[0] + verts[10]) * 0.5f;
|
||||
final float cy = (verts[1] + verts[11]) * 0.5f;
|
||||
final float cz = (verts[2] + verts[12]) * 0.5f;
|
||||
final float dx = verts[10] - verts[0];
|
||||
final float dy = verts[11] - verts[1];
|
||||
final float dz = verts[12] - verts[2];
|
||||
final float half = (dx * sx + dy * sy + dz * sz) / (2.0f * denom);
|
||||
|
||||
final float scale = Math.max(Math.abs(cx), Math.max(Math.abs(cy), Math.abs(cz)));
|
||||
final float tol = 1.0e-5f * (1.0f + Math.abs(half) + scale);
|
||||
for (int i = 0, o = 0; i < ParticleQuadMesh.VERTEX_COUNT; i++, o += 5) {
|
||||
final float a = ParticleQuadMesh.cornerA(i) * half;
|
||||
final float b = ParticleQuadMesh.cornerB(i) * half;
|
||||
if (Math.abs(verts[o] - (cx + ParticleQuadMesh.offsetX(a, b, rotX, rotYZ))) > tol) return false;
|
||||
if (Math.abs(verts[o + 1] - (cy + ParticleQuadMesh.offsetY(b, rotXZ))) > tol) return false;
|
||||
if (Math.abs(verts[o + 2] - (cz + ParticleQuadMesh.offsetZ(a, b, rotZ, rotXY))) > tol) return false;
|
||||
}
|
||||
|
||||
if (verts[3] != verts[8] || verts[9] != verts[14] || verts[18] != verts[13] || verts[19] != verts[4]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.centerX = cx;
|
||||
out.centerY = cy;
|
||||
out.centerZ = cz;
|
||||
out.half = half;
|
||||
out.u0 = verts[3];
|
||||
out.v0 = verts[4];
|
||||
out.u1 = verts[13];
|
||||
out.v1 = verts[14];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
|
||||
public final class ParticleQuads {
|
||||
|
||||
private ParticleQuads() {}
|
||||
|
||||
public static void vanillaQuad(EntityFX fx, float partialTicks, float scale, ParticleParams out) {
|
||||
out.half = 0.1F * scale;
|
||||
center(fx, partialTicks, out);
|
||||
iconUv(fx, out);
|
||||
out.colorABGR = packColor(fx.particleRed, fx.particleGreen, fx.particleBlue, fx.particleAlpha);
|
||||
}
|
||||
|
||||
static void center(EntityFX fx, float partialTicks, ParticleParams out) {
|
||||
out.centerX = (float) (fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - EntityFX.interpPosX);
|
||||
out.centerY = (float) (fx.prevPosY + (fx.posY - fx.prevPosY) * partialTicks - EntityFX.interpPosY);
|
||||
out.centerZ = (float) (fx.prevPosZ + (fx.posZ - fx.prevPosZ) * partialTicks - EntityFX.interpPosZ);
|
||||
}
|
||||
|
||||
static void iconUv(EntityFX fx, ParticleParams out) {
|
||||
float minU = fx.particleTextureIndexX / 16.0F;
|
||||
float maxU = minU + 0.0624375F;
|
||||
float minV = fx.particleTextureIndexY / 16.0F;
|
||||
float maxV = minV + 0.0624375F;
|
||||
if (fx.particleIcon != null) {
|
||||
minU = fx.particleIcon.getMinU();
|
||||
maxU = fx.particleIcon.getMaxU();
|
||||
minV = fx.particleIcon.getMinV();
|
||||
maxV = fx.particleIcon.getMaxV();
|
||||
}
|
||||
out.u0 = maxU;
|
||||
out.v0 = maxV;
|
||||
out.u1 = minU;
|
||||
out.v1 = minV;
|
||||
}
|
||||
|
||||
static float ramp(int age, int maxAge, float partialTicks) {
|
||||
return Math.clamp((age + partialTicks) / maxAge * 32.0F, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
public static int packColor(float r, float g, float b, float a) {
|
||||
return (Math.clamp((int) (a * 255.0F), 0, 255) << 24) | (Math.clamp((int) (b * 255.0F), 0, 255) << 16) | (Math.clamp((int) (g * 255.0F), 0, 255) << 8) | Math.clamp((int) (r * 255.0F), 0, 255);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class ParticleRenderState {
|
||||
|
||||
public int texture;
|
||||
public boolean blend;
|
||||
public int blendSrc;
|
||||
public int blendDst;
|
||||
public int blendSrcAlpha;
|
||||
public int blendDstAlpha;
|
||||
public boolean depthMask;
|
||||
|
||||
public void sample() {
|
||||
texture = GLStateManager.getBoundTextureForServerState();
|
||||
blend = GLStateManager.getBlendMode().isEnabled();
|
||||
final BlendStateStack blendState = GLStateManager.getBlendState();
|
||||
blendSrc = blendState.getSrcRgb();
|
||||
blendDst = blendState.getDstRgb();
|
||||
blendSrcAlpha = blendState.getSrcAlpha();
|
||||
blendDstAlpha = blendState.getDstAlpha();
|
||||
depthMask = GLStateManager.getDepthState().isEnabled();
|
||||
}
|
||||
|
||||
public void set(ParticleRenderState other) {
|
||||
texture = other.texture;
|
||||
blend = other.blend;
|
||||
blendSrc = other.blendSrc;
|
||||
blendDst = other.blendDst;
|
||||
blendSrcAlpha = other.blendSrcAlpha;
|
||||
blendDstAlpha = other.blendDstAlpha;
|
||||
depthMask = other.depthMask;
|
||||
}
|
||||
|
||||
public boolean matches(ParticleRenderState other) {
|
||||
return texture == other.texture && blend == other.blend && blendSrc == other.blendSrc && blendDst == other.blendDst && blendSrcAlpha == other.blendSrcAlpha && blendDstAlpha == other.blendDstAlpha && depthMask == other.depthMask;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
if (blend) {
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
GLStateManager.glBlendFuncSeparate(blendSrc, blendDst, blendSrcAlpha, blendDstAlpha);
|
||||
} else {
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
GLStateManager.glDepthMask(depthMask);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package com.gtnewhorizons.angelica.rendering.particles;
|
||||
|
||||
import net.minecraft.client.particle.EntityBlockDustFX;
|
||||
import net.minecraft.client.particle.EntityBreakingFX;
|
||||
import net.minecraft.client.particle.EntityCloudFX;
|
||||
import net.minecraft.client.particle.EntityCrit2FX;
|
||||
import net.minecraft.client.particle.EntityCritFX;
|
||||
import net.minecraft.client.particle.EntityDiggingFX;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.particle.EntityFireworkOverlayFX;
|
||||
import net.minecraft.client.particle.EntityFireworkSparkFX;
|
||||
import net.minecraft.client.particle.EntityFlameFX;
|
||||
import net.minecraft.client.particle.EntityHeartFX;
|
||||
import net.minecraft.client.particle.EntityHugeExplodeFX;
|
||||
import net.minecraft.client.particle.EntityLavaFX;
|
||||
import net.minecraft.client.particle.EntityNoteFX;
|
||||
import net.minecraft.client.particle.EntityPortalFX;
|
||||
import net.minecraft.client.particle.EntityReddustFX;
|
||||
import net.minecraft.client.particle.EntitySmokeFX;
|
||||
import net.minecraft.client.particle.EntitySnowShovelFX;
|
||||
import net.minecraft.client.particle.EntitySpellParticleFX;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
public final class VanillaParticleDescriptors {
|
||||
|
||||
private interface BaseScale {
|
||||
|
||||
float of(EntityFX fx);
|
||||
}
|
||||
|
||||
private interface ScaleFactor {
|
||||
|
||||
float factor(int age, int maxAge, float partialTicks);
|
||||
}
|
||||
|
||||
private static final ScaleFactor RAMP = ParticleQuads::ramp;
|
||||
|
||||
private static final ScaleFactor FLAME = (age, maxAge, partialTicks) -> {
|
||||
final float f = (age + partialTicks) / maxAge;
|
||||
return 1.0F - f * f * 0.5F;
|
||||
};
|
||||
|
||||
private static final ScaleFactor LAVA = (age, maxAge, partialTicks) -> {
|
||||
final float f = (age + partialTicks) / maxAge;
|
||||
return 1.0F - f * f;
|
||||
};
|
||||
|
||||
private static final ScaleFactor PORTAL = (age, maxAge, partialTicks) -> {
|
||||
float f = (age + partialTicks) / maxAge;
|
||||
f = 1.0F - f;
|
||||
f *= f;
|
||||
return 1.0F - f;
|
||||
};
|
||||
|
||||
private static final ParticleDescriptor EMPTY = (fx, partialTicks, out, state) -> false;
|
||||
|
||||
private static final ParticleDescriptor DIGGING = (fx, partialTicks, out, state) -> {
|
||||
out.half = 0.1F * fx.particleScale;
|
||||
ParticleQuads.center(fx, partialTicks, out);
|
||||
out.colorABGR = ParticleQuads.packColor(fx.particleRed, fx.particleGreen, fx.particleBlue, 1.0F);
|
||||
final float jitterU = fx.particleTextureJitterX;
|
||||
final float jitterV = fx.particleTextureJitterY;
|
||||
if (fx.particleIcon != null) {
|
||||
out.u0 = fx.particleIcon.getInterpolatedU(jitterU / 4.0F * 16.0F);
|
||||
out.u1 = fx.particleIcon.getInterpolatedU((jitterU + 1.0F) / 4.0F * 16.0F);
|
||||
out.v1 = fx.particleIcon.getInterpolatedV(jitterV / 4.0F * 16.0F);
|
||||
out.v0 = fx.particleIcon.getInterpolatedV((jitterV + 1.0F) / 4.0F * 16.0F);
|
||||
} else {
|
||||
out.u0 = (fx.particleTextureIndexX + jitterU / 4.0F) / 16.0F;
|
||||
out.u1 = out.u0 + 0.015609375F;
|
||||
out.v1 = (fx.particleTextureIndexY + jitterV / 4.0F) / 16.0F;
|
||||
out.v0 = out.v1 + 0.015609375F;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private static final ParticleDescriptor FIREWORK_OVERLAY = (fx, partialTicks, out, state) -> {
|
||||
final float t = fx.particleAge + partialTicks - 1.0F;
|
||||
fx.particleAlpha = 0.6F - t * 0.25F * 0.5F;
|
||||
ParticleQuads.center(fx, partialTicks, out);
|
||||
out.half = 7.1F * MathHelper.sin(t * 0.25F * (float) Math.PI);
|
||||
out.u0 = 0.25F + 0.25F;
|
||||
out.v0 = 0.125F + 0.25F;
|
||||
out.u1 = 0.25F;
|
||||
out.v1 = 0.125F;
|
||||
out.colorABGR = ParticleQuads.packColor(fx.particleRed, fx.particleGreen, fx.particleBlue, fx.particleAlpha);
|
||||
return true;
|
||||
};
|
||||
|
||||
private record BaseScaled(BaseScale base, ScaleFactor factor) implements ParticleDescriptor {
|
||||
|
||||
@Override
|
||||
public boolean describe(EntityFX fx, float partialTicks, ParticleParams out, ParticleRenderState state) {
|
||||
fx.particleScale = base.of(fx) * factor.factor(fx.particleAge, fx.particleMaxAge, partialTicks);
|
||||
ParticleQuads.vanillaQuad(fx, partialTicks, fx.particleScale, out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ParticleDescriptor FIREWORK_SPARK = (fx, partialTicks, out, state) -> {
|
||||
final int age = fx.particleAge;
|
||||
final int maxAge = fx.particleMaxAge;
|
||||
if (((EntityFireworkSparkFX) fx).field_92048_ay && age >= maxAge / 3 && (age + maxAge) / 3 % 2 != 0) return false;
|
||||
ParticleQuads.vanillaQuad(fx, partialTicks, fx.particleScale, out);
|
||||
return true;
|
||||
};
|
||||
|
||||
private VanillaParticleDescriptors() {}
|
||||
|
||||
public static void registerAll() {
|
||||
registerScaled(EntityFlameFX.class, fx -> ((EntityFlameFX) fx).flameScale, FLAME);
|
||||
registerScaled(EntityLavaFX.class, fx -> ((EntityLavaFX) fx).lavaParticleScale, LAVA);
|
||||
registerScaled(EntityPortalFX.class, fx -> ((EntityPortalFX) fx).portalParticleScale, PORTAL);
|
||||
registerScaled(EntitySmokeFX.class, fx -> ((EntitySmokeFX) fx).smokeParticleScale, RAMP);
|
||||
registerScaled(EntityCloudFX.class, fx -> ((EntityCloudFX) fx).field_70569_a, RAMP);
|
||||
registerScaled(EntityCritFX.class, fx -> ((EntityCritFX) fx).initialParticleScale, RAMP);
|
||||
registerScaled(EntityHeartFX.class, fx -> ((EntityHeartFX) fx).particleScaleOverTime, RAMP);
|
||||
registerScaled(EntityNoteFX.class, fx -> ((EntityNoteFX) fx).noteParticleScale, RAMP);
|
||||
registerScaled(EntityReddustFX.class, fx -> ((EntityReddustFX) fx).reddustParticleScale, RAMP);
|
||||
registerScaled(EntitySnowShovelFX.class, fx -> ((EntitySnowShovelFX) fx).snowDigParticleScale, RAMP);
|
||||
|
||||
ParticleDescriptorRegistry.register(EntitySpellParticleFX.class.getName(), ParticleDescriptorRegistry.VANILLA);
|
||||
ParticleDescriptorRegistry.register(EntityDiggingFX.class.getName(), DIGGING);
|
||||
ParticleDescriptorRegistry.register(EntityBlockDustFX.class.getName(), DIGGING);
|
||||
ParticleDescriptorRegistry.register(EntityBreakingFX.class.getName(), DIGGING);
|
||||
ParticleDescriptorRegistry.register(EntityFireworkOverlayFX.class.getName(), FIREWORK_OVERLAY);
|
||||
ParticleDescriptorRegistry.register(EntityCrit2FX.class.getName(), EMPTY);
|
||||
ParticleDescriptorRegistry.register(EntityHugeExplodeFX.class.getName(), EMPTY);
|
||||
ParticleDescriptorRegistry.register(EntityFireworkSparkFX.class.getName(), FIREWORK_SPARK);
|
||||
}
|
||||
|
||||
private static void registerScaled(Class<? extends EntityFX> cls, BaseScale base, ScaleFactor factor) {
|
||||
ParticleDescriptorRegistry.register(cls.getName(), new BaseScaled(base, factor));
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMeshProvider;
|
||||
import com.gtnewhorizons.angelica.api.tesr.TesrMeshSink;
|
||||
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.particles.BopParticleDescriptors;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.VertexTransform;
|
||||
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
@@ -22,13 +24,10 @@ import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
public final class AngelicaTesrMeshCache {
|
||||
|
||||
@@ -192,6 +191,9 @@ public final class AngelicaTesrMeshCache {
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchPost(TextureStitchEvent.Post event) {
|
||||
INSTANCE.clear();
|
||||
DroppedItemInstancer.clear();
|
||||
BopParticleDescriptors.invalidate();
|
||||
ShaderGlint.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +230,7 @@ public final class AngelicaTesrMeshCache {
|
||||
return entries.containsKey(key);
|
||||
}
|
||||
|
||||
static final class GtnhMeshBackend implements MeshBackend {
|
||||
public static final class GtnhMeshBackend implements MeshBackend {
|
||||
private DirectTessellator direct;
|
||||
|
||||
@Override
|
||||
@@ -241,20 +243,10 @@ public final class AngelicaTesrMeshCache {
|
||||
|
||||
@Override
|
||||
public TemplateBuffer endCaptureToTemplate() {
|
||||
final int vertexCount = direct.getVertexCount();
|
||||
if (vertexCount == 0) {
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
direct = null;
|
||||
return null;
|
||||
}
|
||||
final int drawMode = direct.getDrawMode();
|
||||
final VertexFormat format = direct.getVertexFormat();
|
||||
final ByteBuffer copy = direct.allocateBufferCopy();
|
||||
final int[] data = VertexTransform.decode(memAddress0(copy), format, vertexCount, drawMode);
|
||||
memFree(copy);
|
||||
final TemplateBuffer template = TemplateCapture.toTemplate(direct);
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
direct = null;
|
||||
return new TemplateBuffer(data, vertexCount, drawMode);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.ColorABGR;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.client.rendering.ModelViewDelta;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
|
||||
public final class BakedTransformCapture extends DirectTessellator {
|
||||
|
||||
private final ModelViewDelta delta = new ModelViewDelta();
|
||||
private final Matrix4f deltaMatrix = new Matrix4f();
|
||||
private final Vector3f scratch = new Vector3f();
|
||||
private Matrix4fc transform;
|
||||
private int firstColor;
|
||||
private boolean sawRun;
|
||||
private boolean colorUniform = true;
|
||||
|
||||
public BakedTransformCapture(int capacity) {
|
||||
super(memAlloc(capacity));
|
||||
}
|
||||
|
||||
public void begin() {
|
||||
TessellatorManager.startCapturingDirect(this);
|
||||
delta.snapshot();
|
||||
sawRun = false;
|
||||
colorUniform = true;
|
||||
}
|
||||
|
||||
public TemplateBuffer end() {
|
||||
final TemplateBuffer template = colorUniform ? TemplateCapture.toTemplate(this) : null;
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int interceptDraw(Tessellator tessellator) {
|
||||
final Color4 color = GLStateManager.getColor();
|
||||
final int packed = ColorABGR.pack(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
if (!sawRun) {
|
||||
sawRun = true;
|
||||
firstColor = packed;
|
||||
} else if (packed != firstColor) {
|
||||
colorUniform = false;
|
||||
}
|
||||
transform = delta.deltaOrNull(deltaMatrix);
|
||||
try {
|
||||
return super.interceptDraw(tessellator);
|
||||
} finally {
|
||||
transform = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long writeVertexData(VertexFormat format, int[] rawBuffer, int rawBufferIndex) {
|
||||
if (transform != null) {
|
||||
return format.writeToBuffer0(writePtr, rawBuffer, rawBufferIndex, transform, scratch);
|
||||
}
|
||||
return super.writeVertexData(format, rawBuffer, rawBufferIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
|
||||
/** Excludes renderers that mix batched model parts with their own draws, which reorders their geometry. */
|
||||
public final class BatchEligibility {
|
||||
@@ -13,6 +14,10 @@ public final class BatchEligibility {
|
||||
private static boolean allowed;
|
||||
private static long drawsAtStart;
|
||||
private static long expectedDraws;
|
||||
private static long foreignDraws;
|
||||
private static long bracketDrawsAtStart;
|
||||
private static long bracketExpectedAtStart;
|
||||
private static int bracketDepth;
|
||||
private static int parts;
|
||||
|
||||
private BatchEligibility() {}
|
||||
@@ -21,6 +26,7 @@ public final class BatchEligibility {
|
||||
if (depth++ > 0) return allowed;
|
||||
drawsAtStart = drawCount;
|
||||
expectedDraws = 0L;
|
||||
foreignDraws = 0L;
|
||||
parts = 0;
|
||||
allowed = state == SAFE;
|
||||
return allowed;
|
||||
@@ -29,9 +35,13 @@ public final class BatchEligibility {
|
||||
public static byte end(byte state, long drawCount) {
|
||||
if (--depth > 0) return state;
|
||||
allowed = false;
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
if (parts == 0) return state;
|
||||
final long foreign = (drawCount - drawsAtStart) - expectedDraws;
|
||||
if (foreign > 0L) return DENIED;
|
||||
if (foreign > 0L) {
|
||||
foreignDraws = foreign;
|
||||
return DENIED;
|
||||
}
|
||||
return state == UNKNOWN ? SAFE : state;
|
||||
}
|
||||
|
||||
@@ -50,9 +60,21 @@ public final class BatchEligibility {
|
||||
}
|
||||
}
|
||||
|
||||
public static void beginExpectedDraws(long drawCount) {
|
||||
if (bracketDepth++ > 0) return;
|
||||
bracketDrawsAtStart = drawCount;
|
||||
bracketExpectedAtStart = expectedDraws;
|
||||
}
|
||||
|
||||
public static void endExpectedDraws(long drawCount) {
|
||||
if (--bracketDepth > 0) return;
|
||||
final long alreadyExpected = expectedDraws - bracketExpectedAtStart;
|
||||
expectedDraws += (drawCount - bracketDrawsAtStart) - alreadyExpected;
|
||||
}
|
||||
|
||||
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);
|
||||
GLStateManager.warnOnce("tesr-mixed:" + name, "{} draws its own geometry alongside model parts ({} foreign draws) - excluding it from model part batching", name, foreignDraws);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
|
||||
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@@ -17,21 +17,43 @@ public final class EntityMaterials {
|
||||
static final TesrMaterial ADDITIVE_NO_DEPTH_WRITE = TesrMaterial.builder().additive().noDepthWrite().stream().build();
|
||||
static final TesrMaterial ADDITIVE_ALPHA = TesrMaterial.builder().additiveAlpha().stream().build();
|
||||
static final TesrMaterial ADDITIVE_ALPHA_NO_DEPTH_WRITE = TesrMaterial.builder().additiveAlpha().noDepthWrite().stream().build();
|
||||
static final TesrMaterial SHADOW = TesrMaterial.builder().translucent().cutout(0.1f).noDepthWrite().unlit().stream().build();
|
||||
public static final TesrMaterial DROPPED_ITEM_CUTOUT = TesrMaterial.builder().cutout(0.1f).stream().unfilteredAtlas().build();
|
||||
public static final TesrMaterial DROPPED_ITEM_TRANSLUCENT = TesrMaterial.builder().translucent().cutout(0.1f).unfilteredAtlas().stream().build();
|
||||
public static final TesrMaterial HELD_BLOCK_CUTOUT = TesrMaterial.builder().cutout(0.1f).stream().build();
|
||||
public static final TesrMaterial HELD_BLOCK_TRANSLUCENT = TesrMaterial.builder().translucent().cutout(0.1f).stream().build();
|
||||
static final TesrMaterial OVERLAY = TesrMaterial.builder().translucent().depthEqual().stream().build();
|
||||
static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream()
|
||||
public static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream()
|
||||
.special(TesrMaterial.SpecialRender.GLINT).build();
|
||||
|
||||
private static final BlendState effectiveBlend = new BlendState();
|
||||
private static final AlphaState effectiveAlpha = new AlphaState();
|
||||
|
||||
private EntityMaterials() {}
|
||||
|
||||
static TesrMaterial fromCurrentState() {
|
||||
static TesrMaterial fromCurrentState(boolean texIdentity) {
|
||||
final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
|
||||
final boolean texAnimated = textured && !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0));
|
||||
final BlendState blend = GLStateManager.getBlendState();
|
||||
return fromState(textured, texAnimated,
|
||||
GLStateManager.getBlendMode().isEnabled(), blend.getSrcRgb(), blend.getDstRgb(),
|
||||
GLStateManager.getAlphaTest().isEnabled(),
|
||||
GLStateManager.getAlphaState().getFunction(), GLStateManager.getAlphaState().getReference(),
|
||||
GLStateManager.getDepthState().getFunc(), GLStateManager.getDepthState().isEnabled());
|
||||
final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend);
|
||||
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
|
||||
return fromState(textured, textured && !texIdentity, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled());
|
||||
}
|
||||
|
||||
public static TesrMaterial itemFromCurrentState(boolean unfilteredAtlas) {
|
||||
final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend);
|
||||
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
|
||||
return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled());
|
||||
}
|
||||
|
||||
static TesrMaterial itemFromState(boolean unfilteredAtlas, boolean blend, int srcRgb, int dstRgb, boolean alphaTest, int alphaFunc, float alphaRef, int depthFunc, boolean depthMask) {
|
||||
if (depthFunc != GL11.GL_LEQUAL && depthFunc != GL11.GL_LESS) return null;
|
||||
if (!depthMask) return null;
|
||||
if (!alphaTest) return null;
|
||||
if (alphaFunc != GL11.GL_GREATER || Math.abs(alphaRef - 0.1f) > 1.0e-4f) return null;
|
||||
if (blend) {
|
||||
if (srcRgb != GL11.GL_SRC_ALPHA || dstRgb != GL11.GL_ONE_MINUS_SRC_ALPHA) return null;
|
||||
return unfilteredAtlas ? DROPPED_ITEM_TRANSLUCENT : HELD_BLOCK_TRANSLUCENT;
|
||||
}
|
||||
return unfilteredAtlas ? DROPPED_ITEM_CUTOUT : HELD_BLOCK_CUTOUT;
|
||||
}
|
||||
|
||||
static TesrMaterial fromState(boolean textured, boolean blend, int srcRgb, int dstRgb, boolean alphaTest, int alphaFunc, float alphaRef, int depthFunc, boolean depthMask) {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
|
||||
import net.coderbot.batchedentityrendering.impl.SegmentedBufferBuilder;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.PassOverride;
|
||||
import net.coderbot.iris.pipeline.PipelineManager;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.vertices.NormI8;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.World;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
final class EntityShadowBatcher {
|
||||
|
||||
private static final ResourceLocation SHADOW_TEXTURE = new ResourceLocation("textures/misc/shadow.png");
|
||||
private static final int PACKED_UP = NormI8.pack(0f, 1f, 0f, 0f);
|
||||
|
||||
private static final int INITIAL_QUADS = 256;
|
||||
private static final int INITIAL_MVS = 8;
|
||||
|
||||
private int[] records = new int[INITIAL_QUADS * ShadowQuadMath.RECORD_SIZE];
|
||||
private int[] recordMv = new int[INITIAL_QUADS];
|
||||
private int count;
|
||||
private Matrix4f[] mvs = new Matrix4f[INITIAL_MVS];
|
||||
private int mvCount;
|
||||
private final int[] packedScratch = new int[4 * ModelQuadUtil.VERTEX_SIZE];
|
||||
private final int[] ijk = new int[6];
|
||||
private final Matrix4f mvScratch = new Matrix4f();
|
||||
private final Vector3f scratch = new Vector3f();
|
||||
private int lastMvGeneration;
|
||||
private RenderLayer layer;
|
||||
|
||||
private long quads;
|
||||
private long draws;
|
||||
|
||||
long statQuads() { return quads; }
|
||||
|
||||
long statDraws() { return draws; }
|
||||
|
||||
boolean record(World world, Entity entity, double x, double y, double z, float shadowAlpha, float partialTicks, float shadowSize) {
|
||||
if (world == null || !ModelPartBatcher.INSTANCE.entityPassActive()) return false;
|
||||
if (Iris.enabled) {
|
||||
final PipelineManager manager = Iris.getPipelineManagerNullable();
|
||||
if (manager != null) {
|
||||
final WorldRenderingPipeline pipeline = manager.getPipelineNullable();
|
||||
if (pipeline != null && pipeline.shouldDisableVanillaEntityShadows()) return false;
|
||||
}
|
||||
}
|
||||
|
||||
float size = shadowSize;
|
||||
if (entity instanceof EntityLiving living) {
|
||||
size *= living.getRenderSizeModifier();
|
||||
if (living.isChild()) {
|
||||
size *= 0.5F;
|
||||
}
|
||||
}
|
||||
|
||||
final double ss = entity.getShadowSize();
|
||||
final double ex = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double) partialTicks;
|
||||
final double ey = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double) partialTicks + ss;
|
||||
final double ez = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double) partialTicks;
|
||||
final double ox = x - ex;
|
||||
final double oy = y - ey;
|
||||
final double oz = z - ez;
|
||||
|
||||
final int cells = ShadowQuadMath.box(ex, ey, ez, size, ijk);
|
||||
ensureCapacity(cells);
|
||||
final int mvIndex = currentMvIndex();
|
||||
|
||||
final int packedLight = GLSMConfig.packedLastBrightness();
|
||||
final float[] brightnessTable = world.provider.lightBrightnessTable;
|
||||
final double yParam = y + ss;
|
||||
final double oyParam = oy + ss;
|
||||
|
||||
for (int bx = ijk[0]; bx <= ijk[1]; ++bx) {
|
||||
for (int by = ijk[2]; by <= ijk[3]; ++by) {
|
||||
for (int bz = ijk[4]; bz <= ijk[5]; ++bz) {
|
||||
final Block block = world.getBlock(bx, by - 1, bz);
|
||||
if (block.getMaterial() == Material.air) continue;
|
||||
final int light = world.getBlockLightValue(bx, by, bz);
|
||||
if (light <= 3 || !block.renderAsNormalBlock()) continue;
|
||||
if (ShadowQuadMath.project(records, count * ShadowQuadMath.RECORD_SIZE, x, yParam, z, bx, by, bz, shadowAlpha, size, ox, oyParam, oz, block.getBlockBoundsMinX(), block.getBlockBoundsMaxX(), block.getBlockBoundsMinY(), block.getBlockBoundsMinZ(), block.getBlockBoundsMaxZ(), brightnessTable[light], packedLight)) {
|
||||
recordMv[count] = mvIndex;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDepthMask(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void flush(AngelicaBufferSource source) {
|
||||
if (count == 0) {
|
||||
mvCount = 0;
|
||||
return;
|
||||
}
|
||||
final SegmentedBufferBuilder builder = source.getBuffer(layer(), 0);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ShadowQuadMath.expand(packedScratch, records, i * ShadowQuadMath.RECORD_SIZE, PACKED_UP);
|
||||
builder.addPackedVertices(packedScratch, 4, mvs[recordMv[i]], scratch);
|
||||
}
|
||||
draws++;
|
||||
quads += count;
|
||||
count = 0;
|
||||
mvCount = 0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count = 0;
|
||||
mvCount = 0;
|
||||
records = new int[INITIAL_QUADS * ShadowQuadMath.RECORD_SIZE];
|
||||
recordMv = new int[INITIAL_QUADS];
|
||||
mvs = new Matrix4f[INITIAL_MVS];
|
||||
layer = null;
|
||||
}
|
||||
|
||||
private int currentMvIndex() {
|
||||
final int mvGen = GLStateManager.getMvGeneration();
|
||||
if (mvCount > 0 && mvGen == lastMvGeneration) return mvCount - 1;
|
||||
lastMvGeneration = mvGen;
|
||||
mvScratch.set(GLStateManager.getModelViewMatrix());
|
||||
if (mvCount > 0 && mvs[mvCount - 1].equals(mvScratch)) return mvCount - 1;
|
||||
if (mvCount == mvs.length) mvs = Arrays.copyOf(mvs, mvs.length * 2);
|
||||
Matrix4f slot = mvs[mvCount];
|
||||
if (slot == null) {
|
||||
slot = new Matrix4f();
|
||||
mvs[mvCount] = slot;
|
||||
}
|
||||
slot.set(mvScratch);
|
||||
return mvCount++;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int additionalQuads) {
|
||||
final int neededQuads = count + additionalQuads;
|
||||
if (neededQuads * ShadowQuadMath.RECORD_SIZE > records.length) {
|
||||
records = Arrays.copyOf(records, Math.max(neededQuads * ShadowQuadMath.RECORD_SIZE, records.length * 2));
|
||||
}
|
||||
if (neededQuads > recordMv.length) {
|
||||
recordMv = Arrays.copyOf(recordMv, Math.max(neededQuads, recordMv.length * 2));
|
||||
}
|
||||
}
|
||||
|
||||
private RenderLayer layer() {
|
||||
RenderLayer l = layer;
|
||||
if (l == null) {
|
||||
l = RenderLayer.tesr(SHADOW_TEXTURE, EntityMaterials.SHADOW, PassOverride.NONE, 0.0F, 0.0F);
|
||||
layer = l;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class InstanceRing {
|
||||
|
||||
private PersistentStreamingBuffer persistentRing;
|
||||
private OrphanStreamingBuffer orphanRing;
|
||||
private boolean initialized;
|
||||
private boolean usedThisFrame;
|
||||
private int bufferId;
|
||||
|
||||
public long upload(ByteBuffer data, int stride) {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
persistentRing = PersistentStreamingBuffer.createOrNull(PersistentStreamingBuffer.DEFAULT_CAPACITY);
|
||||
}
|
||||
usedThisFrame = true;
|
||||
if (persistentRing != null) {
|
||||
final int index = persistentRing.upload(data, stride);
|
||||
if (index >= 0) {
|
||||
bufferId = persistentRing.getBufferId();
|
||||
return (long) index * stride;
|
||||
}
|
||||
}
|
||||
if (orphanRing == null) orphanRing = new OrphanStreamingBuffer();
|
||||
orphanRing.upload(data);
|
||||
bufferId = orphanRing.getBufferId();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int bufferId() {
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
public void postDraw() {
|
||||
if (usedThisFrame && persistentRing != null) {
|
||||
persistentRing.postDraw();
|
||||
}
|
||||
usedThisFrame = false;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (persistentRing != null) {
|
||||
persistentRing.destroy();
|
||||
persistentRing = null;
|
||||
}
|
||||
if (orphanRing != null) {
|
||||
orphanRing.destroy();
|
||||
orphanRing = null;
|
||||
}
|
||||
initialized = false;
|
||||
usedThisFrame = false;
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
+96
-81
@@ -4,12 +4,15 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeInstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.UnitCubeMesh;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.InstanceColumns;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.TexRun;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
@@ -26,8 +29,6 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.CO
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.NORMAL_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.TEX_X_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.VERTEX_SIZE;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
final class InstancedTemplateRenderer {
|
||||
|
||||
@@ -36,6 +37,8 @@ final class InstancedTemplateRenderer {
|
||||
private static final int TEMPLATE_STRIDE = VERTEX_SIZE * 4;
|
||||
private static final int TEMPLATE_FLAGS = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
private static long bucketEpochSource;
|
||||
|
||||
private static final class TemplateMesh {
|
||||
int vao;
|
||||
int vbo;
|
||||
@@ -51,69 +54,58 @@ final class InstancedTemplateRenderer {
|
||||
}
|
||||
|
||||
private final Reference2ObjectOpenHashMap<TemplateBuffer, TemplateMesh> meshes = new Reference2ObjectOpenHashMap<>();
|
||||
private final Reference2ObjectOpenHashMap<TemplateBuffer, Bucket> buckets = new Reference2ObjectOpenHashMap<>();
|
||||
private final ObjectArrayList<Bucket> bucketPool = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<Bucket> liveBuckets = new ObjectArrayList<>();
|
||||
private final float[] matScratch = new float[16];
|
||||
|
||||
private PersistentStreamingBuffer persistentRing;
|
||||
private OrphanStreamingBuffer orphanRing;
|
||||
private boolean ringInitialized;
|
||||
private boolean ringUsedThisFrame;
|
||||
private int uploadBufferId;
|
||||
private final InstanceRing ring;
|
||||
private ByteBuffer staging;
|
||||
|
||||
int drawGroup(ObjectArrayList<TemplateBuffer> templates, FloatArrayList matrices, IntArrayList lights, IntArrayList colors, int count, long nowMs) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final TemplateBuffer template = templates.get(i);
|
||||
Bucket bucket = buckets.get(template);
|
||||
if (bucket == null) {
|
||||
bucket = bucketPool.isEmpty() ? new Bucket() : bucketPool.pop();
|
||||
bucket.mesh = meshFor(template, nowMs);
|
||||
buckets.put(template, bucket);
|
||||
liveBuckets.add(bucket);
|
||||
InstancedTemplateRenderer(InstanceRing ring) {
|
||||
this.ring = ring;
|
||||
}
|
||||
|
||||
int drawTemplates(InstanceColumns cols, TexRun run, long nowMs) {
|
||||
final long epoch = ++bucketEpochSource;
|
||||
for (TexRun seg = run; seg != null; seg = seg.next) {
|
||||
for (int i = seg.start, end = seg.end; i < end; i++) {
|
||||
final TemplateBuffer template = cols.templates.get(i);
|
||||
final Bucket bucket;
|
||||
if (template.bucketEpoch == epoch) {
|
||||
bucket = liveBuckets.get(template.bucketIndex);
|
||||
} else {
|
||||
bucket = bucketPool.isEmpty() ? new Bucket() : bucketPool.pop();
|
||||
bucket.mesh = meshFor(template, nowMs);
|
||||
template.bucketEpoch = epoch;
|
||||
template.bucketIndex = liveBuckets.size();
|
||||
liveBuckets.add(bucket);
|
||||
}
|
||||
bucket.indices.add(i);
|
||||
}
|
||||
bucket.indices.add(i);
|
||||
}
|
||||
|
||||
staging = MeshBuffer.ensureCapacity(staging, count * InstancedAttribs.STRIDE, false);
|
||||
staging = MeshBuffer.ensureCapacity(staging, run.parts * InstancedAttribs.STRIDE, false);
|
||||
final long base = memAddress0(staging);
|
||||
long ptr = base;
|
||||
for (int b = 0, n = liveBuckets.size(); b < n; b++) {
|
||||
final IntArrayList indices = liveBuckets.get(b).indices;
|
||||
for (int j = 0, m = indices.size(); j < m; j++) {
|
||||
final int i = indices.getInt(j);
|
||||
matrices.getElements(i * 16, matScratch, 0, 16);
|
||||
for (int k = 0; k < 16; k++) {
|
||||
memPutFloat(ptr + k * 4L, matScratch[k]);
|
||||
}
|
||||
memPutInt(ptr + InstancedAttribs.OFFSET_COLOR, colors.getInt(i));
|
||||
final int light = lights.getInt(i);
|
||||
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP, light & 0xFFFF);
|
||||
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP + 4, (light >>> 16) & 0xFFFF);
|
||||
InstancedAttribs.writeHead(ptr, cols.matrices, i * 16, cols.colors.getInt(i), cols.overlays.getInt(i), cols.infos.getLong(i));
|
||||
InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, cols.lights.getInt(i));
|
||||
ptr += InstancedAttribs.STRIDE;
|
||||
}
|
||||
}
|
||||
staging.position(0);
|
||||
staging.limit((int) (ptr - base));
|
||||
final long ringBase = uploadInstances(staging);
|
||||
staging.clear();
|
||||
final long ringBase = uploadStaging(base, ptr, InstancedAttribs.STRIDE);
|
||||
|
||||
int draws = 0;
|
||||
long recordOffset = ringBase;
|
||||
final int ringId = uploadBufferId;
|
||||
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
|
||||
if (extHandler != null && extHandler.wantsExtended()) {
|
||||
FfpExtendedAttribs.setNeutralCurrentValues();
|
||||
}
|
||||
GLStateManager.instancedFfpDrawActive = true;
|
||||
neutralizeExtendedAttribs();
|
||||
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
|
||||
for (int b = 0, n = liveBuckets.size(); b < n; b++) {
|
||||
final Bucket bucket = liveBuckets.get(b);
|
||||
final TemplateMesh mesh = bucket.mesh;
|
||||
GLStateManager.glBindVertexArray(mesh.vao);
|
||||
VAOManager.setCurrentVertexFlags(TEMPLATE_FLAGS);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, ringId);
|
||||
pointInstanceAttribs(recordOffset);
|
||||
bindRingTo(mesh.vao, TEMPLATE_FLAGS);
|
||||
InstancedAttribs.pointTemplate(recordOffset);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glDrawArraysInstanced(mesh.drawMode, 0, mesh.vertexCount, bucket.indices.size());
|
||||
draws++;
|
||||
@@ -122,41 +114,66 @@ final class InstancedTemplateRenderer {
|
||||
bucket.mesh = null;
|
||||
bucketPool.add(bucket);
|
||||
}
|
||||
GLStateManager.instancedFfpDrawActive = false;
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
liveBuckets.clear();
|
||||
buckets.clear();
|
||||
return draws;
|
||||
}
|
||||
|
||||
private static void pointInstanceAttribs(long base) {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_MATRIX_COL0 + c, 4, GL11.GL_FLOAT, false,
|
||||
InstancedAttribs.STRIDE, base + c * 16L);
|
||||
}
|
||||
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true,
|
||||
InstancedAttribs.STRIDE, base + InstancedAttribs.OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false,
|
||||
InstancedAttribs.STRIDE, base + InstancedAttribs.OFFSET_LIGHTMAP);
|
||||
}
|
||||
|
||||
private long uploadInstances(ByteBuffer data) {
|
||||
if (!ringInitialized) {
|
||||
ringInitialized = true;
|
||||
persistentRing = PersistentStreamingBuffer.createOrNull(PersistentStreamingBuffer.DEFAULT_CAPACITY);
|
||||
}
|
||||
ringUsedThisFrame = true;
|
||||
if (persistentRing != null) {
|
||||
final int index = persistentRing.upload(data, InstancedAttribs.STRIDE);
|
||||
if (index >= 0) {
|
||||
uploadBufferId = persistentRing.getBufferId();
|
||||
return (long) index * InstancedAttribs.STRIDE;
|
||||
void drawCubes(InstanceColumns cols, TexRun run) {
|
||||
staging = MeshBuffer.ensureCapacity(staging, run.instances * CubeInstancedAttribs.STRIDE, false);
|
||||
final long base = memAddress0(staging);
|
||||
long ptr = base;
|
||||
for (TexRun seg = run; seg != null; seg = seg.next) {
|
||||
for (int i = seg.start, end = seg.end; i < end; i++) {
|
||||
final int off = i * 16;
|
||||
final CubeParams[] partCubes = cols.cubes.get(i);
|
||||
final float scale = cols.scales.getFloat(i);
|
||||
final int color = cols.colors.getInt(i);
|
||||
final int light = cols.lights.getInt(i);
|
||||
final int overlay = cols.overlays.getInt(i);
|
||||
final long info = cols.infos.getLong(i);
|
||||
for (int p = 0, n = partCubes.length; p < n; p++) {
|
||||
final CubeParams cube = partCubes[p];
|
||||
cube.writeRows(ptr, cols.matrices, off, scale);
|
||||
InstancedAttribs.writeTail(ptr, color, overlay, info);
|
||||
InstancedAttribs.writeLightmap(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, light);
|
||||
cube.writeTexture(ptr);
|
||||
ptr += CubeInstancedAttribs.STRIDE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (orphanRing == null) orphanRing = new OrphanStreamingBuffer();
|
||||
orphanRing.upload(data);
|
||||
uploadBufferId = orphanRing.getBufferId();
|
||||
return 0;
|
||||
final long ringBase = uploadStaging(base, ptr, CubeInstancedAttribs.STRIDE);
|
||||
|
||||
neutralizeExtendedAttribs();
|
||||
bindRingTo(UnitCubeMesh.vao(), UnitCubeMesh.VERTEX_FLAGS);
|
||||
CubeInstancedAttribs.pointInstanceAttribs(ringBase);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.ffpInstancing = Instancing.CUBE;
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, run.instances);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private long uploadStaging(long base, long end, int stride) {
|
||||
staging.position(0);
|
||||
staging.limit((int) (end - base));
|
||||
final long ringBase = ring.upload(staging, stride);
|
||||
staging.clear();
|
||||
return ringBase;
|
||||
}
|
||||
|
||||
private void bindRingTo(int vao, int vertexFlags) {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, ring.bufferId());
|
||||
}
|
||||
|
||||
private static void neutralizeExtendedAttribs() {
|
||||
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
|
||||
if (extHandler != null && extHandler.wantsExtended()) {
|
||||
FfpExtendedAttribs.setNeutralCurrentValues();
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateMesh meshFor(TemplateBuffer template, long nowMs) {
|
||||
@@ -196,10 +213,7 @@ final class InstancedTemplateRenderer {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
for (int loc = InstancedAttribs.LOC_MATRIX_COL0; loc <= InstancedAttribs.LOC_LIGHTMAP; loc++) {
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
GLStateManager.glVertexAttribDivisor(loc, 1);
|
||||
}
|
||||
InstancedAttribs.enableHeadArrays();
|
||||
VAOManager.setCurrentVertexFlags(TEMPLATE_FLAGS);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
return mesh;
|
||||
@@ -225,10 +239,7 @@ final class InstancedTemplateRenderer {
|
||||
}
|
||||
|
||||
void endFrame() {
|
||||
if (ringUsedThisFrame && persistentRing != null) {
|
||||
persistentRing.postDraw();
|
||||
}
|
||||
ringUsedThisFrame = false;
|
||||
ring.postDraw();
|
||||
}
|
||||
|
||||
void sweep(long now) {
|
||||
@@ -247,6 +258,10 @@ final class InstancedTemplateRenderer {
|
||||
delete(mesh);
|
||||
}
|
||||
meshes.clear();
|
||||
liveBuckets.clear();
|
||||
bucketPool.clear();
|
||||
staging = null;
|
||||
UnitCubeMesh.delete();
|
||||
}
|
||||
|
||||
private static void delete(TemplateMesh mesh) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.UnitCubeMesh;
|
||||
import net.minecraft.client.model.PositionTextureVertex;
|
||||
import net.minecraft.client.model.TexturedQuad;
|
||||
|
||||
public final class ModelBoxCapture {
|
||||
|
||||
private static final float EPSILON = 1.0e-4f;
|
||||
|
||||
private ModelBoxCapture() {}
|
||||
|
||||
public static CubeParams capture(TexturedQuad[] quads, float texWidth, float texHeight, boolean mirror, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate) {
|
||||
if (quads == null || quads.length != UnitCubeMesh.FACE_COUNT) return null;
|
||||
final CubeParams params = CubeParams.of(texWidth, texHeight, mirror, texU, texV, x, y, z, sizeX, sizeY, sizeZ, inflate);
|
||||
if (params == null) return null;
|
||||
|
||||
final float lowX = mirror ? params.minX() + params.spanX() : params.minX();
|
||||
final float spanX = mirror ? -params.spanX() : params.spanX();
|
||||
for (int face = 0; face < UnitCubeMesh.FACE_COUNT; face++) {
|
||||
final TexturedQuad quad = quads[face];
|
||||
if (quad == null || quad.vertexPositions == null || quad.vertexPositions.length != 4) return null;
|
||||
final int[] corners = UnitCubeMesh.FACES[face].corners();
|
||||
final UnitCubeMesh.Net net = UnitCubeMesh.FACES[face].net();
|
||||
for (int corner = 0; corner < 4; corner++) {
|
||||
final PositionTextureVertex vertex = quad.vertexPositions[mirror ? 3 - corner : corner];
|
||||
if (vertex == null || vertex.vector3D == null) return null;
|
||||
final float[] unit = UnitCubeMesh.CORNERS[corners[corner]];
|
||||
if (!near((float) vertex.vector3D.xCoord, lowX + unit[0] * spanX) || !near((float) vertex.vector3D.yCoord, params.minY() + unit[1] * params.spanY()) || !near((float) vertex.vector3D.zCoord, params.minZ() + unit[2] * params.spanZ())) {
|
||||
return null;
|
||||
}
|
||||
final int u = texU + net.u(corner).texels(sizeX, sizeZ);
|
||||
final int v = texV + net.v(corner).texels(sizeY, sizeZ);
|
||||
if (!near(vertex.texturePositionX, u / texWidth) || !near(vertex.texturePositionY, v / texHeight)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private static boolean near(float a, float b) {
|
||||
return Math.abs(a - b) < EPSILON;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.ColorABGR;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.api.tesr.ModelPartMeshBuilder;
|
||||
@@ -11,12 +10,17 @@ import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ModelBoxData;
|
||||
import com.gtnewhorizons.angelica.profiling.BailClassCounts;
|
||||
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
@@ -31,7 +35,9 @@ import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.model.ModelBox;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.World;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@@ -50,7 +56,6 @@ public final class ModelPartBatcher {
|
||||
MATERIAL("tesr.bail.material"),
|
||||
TEXTURE("tesr.bail.texture"),
|
||||
TEMPLATE("tesr.bail.template"),
|
||||
HURT_FLASH("tesr.bail.hurtFlash"),
|
||||
FOREIGN_PROGRAM("tesr.bail.foreignProgram"),
|
||||
MIXED_RENDERER("tesr.bail.mixedRenderer");
|
||||
|
||||
@@ -64,7 +69,10 @@ public final class ModelPartBatcher {
|
||||
|
||||
private static final long SWEEP_INTERVAL_MS = 5_000L;
|
||||
|
||||
private static final Tracy.ZoneId Z_ENTITY_LAYER_LOOP = Tracy.zoneId("entityLayerLoop", Tracy.COLOR_CLIENT);
|
||||
|
||||
private final AngelicaBufferSource bufferSource = new AngelicaBufferSource();
|
||||
private final EntityShadowBatcher shadows = new EntityShadowBatcher();
|
||||
private final RetainedTesrGroups groups = new RetainedTesrGroups(bufferSource);
|
||||
private final RetainedTesrGroups shadowGroups = new RetainedTesrGroups(bufferSource);
|
||||
private RetainedTesrGroups activeGroups = groups;
|
||||
@@ -75,6 +83,9 @@ public final class ModelPartBatcher {
|
||||
private final Matrix4f texMatrixScratch = new Matrix4f();
|
||||
private int lastMvGeneration;
|
||||
private boolean mvCaptured;
|
||||
private int texGeneration;
|
||||
private boolean texCaptured;
|
||||
private boolean texIdentity;
|
||||
private boolean active;
|
||||
private Mode mode = Mode.BLOCK_ENTITIES;
|
||||
private boolean instancedThisCycle;
|
||||
@@ -88,7 +99,13 @@ public final class ModelPartBatcher {
|
||||
|
||||
public long statParts() { return parts; }
|
||||
public long statLiveFallbacks() { return liveFallbacks; }
|
||||
public long statInstancedDraws() { return groups.instancedDraws + shadowGroups.instancedDraws; }
|
||||
public long statInstancedInstances() { return groups.instancedInstances + shadowGroups.instancedInstances; }
|
||||
public long statCubeInstances() { return groups.cubeInstances + shadowGroups.cubeInstances; }
|
||||
public long statTexMatrixRuns() { return groups.texMatrixRuns + shadowGroups.texMatrixRuns; }
|
||||
public long statBail(BailReason reason) { return bails[reason.ordinal()]; }
|
||||
public long statShadowQuads() { return shadows.statQuads(); }
|
||||
public long statShadowDraws() { return shadows.statDraws(); }
|
||||
|
||||
private void bail(BailReason reason) {
|
||||
bails[reason.ordinal()]++;
|
||||
@@ -96,10 +113,10 @@ public final class ModelPartBatcher {
|
||||
|
||||
private static final class PartTemplate {
|
||||
TemplateBuffer template;
|
||||
CubeParams[] cubes;
|
||||
float scale;
|
||||
long lastUsedMs;
|
||||
boolean emptyVanillaPart;
|
||||
boolean forceStream;
|
||||
}
|
||||
|
||||
private final Reference2ObjectOpenHashMap<ModelRenderer, PartTemplate> templates = new Reference2ObjectOpenHashMap<>();
|
||||
@@ -110,13 +127,15 @@ public final class ModelPartBatcher {
|
||||
PassOverride pass;
|
||||
float offsetFactor;
|
||||
float offsetUnits;
|
||||
int glintSlot;
|
||||
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
|
||||
this.texture = texture;
|
||||
this.material = material;
|
||||
this.pass = pass;
|
||||
this.offsetFactor = offsetFactor;
|
||||
this.offsetUnits = offsetUnits;
|
||||
this.glintSlot = glintSlot;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -126,7 +145,8 @@ public final class ModelPartBatcher {
|
||||
return Objects.equals(texture, other.texture) && Objects.equals(material, other.material)
|
||||
&& Objects.equals(pass, other.pass)
|
||||
&& Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor)
|
||||
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits);
|
||||
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits)
|
||||
&& glintSlot == other.glintSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,6 +154,7 @@ public final class ModelPartBatcher {
|
||||
int h = (Objects.hashCode(texture) * 31 + Objects.hashCode(material)) * 31 + Objects.hashCode(pass);
|
||||
h = h * 31 + Float.floatToIntBits(offsetFactor);
|
||||
h = h * 31 + Float.floatToIntBits(offsetUnits);
|
||||
h = h * 31 + glintSlot;
|
||||
return h;
|
||||
}
|
||||
}
|
||||
@@ -145,16 +166,24 @@ public final class ModelPartBatcher {
|
||||
private PassOverride lastLayerPass;
|
||||
private float lastLayerOffsetFactor;
|
||||
private float lastLayerOffsetUnits;
|
||||
private int lastLayerGlintSlot;
|
||||
private RenderLayer lastLayer;
|
||||
|
||||
private static ResourceLocation lastBoundLocation;
|
||||
private static int lastBoundGlId = -1;
|
||||
private static int lastBoundGlintSlot = ShaderGlint.NO_TINT;
|
||||
|
||||
private ModelPartBatcher() {}
|
||||
|
||||
public static void onTextureSwap(int glId, int glintSlot) {
|
||||
lastBoundGlId = glId;
|
||||
lastBoundGlintSlot = glintSlot;
|
||||
}
|
||||
|
||||
public static void onTextureBind(ResourceLocation location, int glId) {
|
||||
lastBoundLocation = location;
|
||||
lastBoundGlId = glId;
|
||||
lastBoundGlintSlot = ShaderGlint.NO_TINT;
|
||||
}
|
||||
|
||||
public void begin(Mode mode) {
|
||||
@@ -172,16 +201,17 @@ public final class ModelPartBatcher {
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
final long nowMs = System.currentTimeMillis();
|
||||
mvCaptured = false;
|
||||
texCaptured = false;
|
||||
activeGroups = shadow ? shadowGroups : groups;
|
||||
bufferSource.setIdKind(mode == Mode.ENTITIES ? AngelicaBufferSource.GroupIdKind.ENTITY : AngelicaBufferSource.GroupIdKind.BLOCK_ENTITY);
|
||||
activeGroups.beginPass(identity, 0, 0, 0, instancedThisCycle ? TesrBatchRenderer.INSTANCE.instancedRenderer : null, shaderPipeline);
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastSweepMs >= SWEEP_INTERVAL_MS) {
|
||||
lastSweepMs = now;
|
||||
groups.sweep(now);
|
||||
shadowGroups.sweep(now);
|
||||
sweepTemplates(now);
|
||||
if (nowMs - lastSweepMs >= SWEEP_INTERVAL_MS) {
|
||||
lastSweepMs = nowMs;
|
||||
groups.sweep(nowMs);
|
||||
shadowGroups.sweep(nowMs);
|
||||
sweepTemplates(nowMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,25 +219,18 @@ public final class ModelPartBatcher {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
if (shadow) {
|
||||
bufferSource.endBatchWithType(TransparencyType.OPAQUE, activeGroups);
|
||||
bufferSource.endBatch(activeGroups);
|
||||
if (instancedThisCycle) {
|
||||
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
|
||||
drawBatches();
|
||||
} else {
|
||||
final boolean inEntityLoop = mode == Mode.ENTITIES && Iris.enabled && GbufferPrograms.isEntityLoopActive();
|
||||
final boolean wrapEntities = mode == Mode.ENTITIES && Iris.enabled && !inEntityLoop;
|
||||
if (wrapEntities) GbufferPrograms.beginEntities();
|
||||
else if (inEntityLoop) GbufferPrograms.onEntityRenderBoundary();
|
||||
try {
|
||||
shadows.flush(bufferSource);
|
||||
drawBatches();
|
||||
} finally {
|
||||
if (wrapEntities) GbufferPrograms.endEntities();
|
||||
}
|
||||
if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) {
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
}
|
||||
return;
|
||||
}
|
||||
final boolean inEntityLoop = mode == Mode.ENTITIES && Iris.enabled && GbufferPrograms.isEntityLoopActive();
|
||||
final boolean wrapEntities = mode == Mode.ENTITIES && Iris.enabled && !inEntityLoop;
|
||||
if (wrapEntities) GbufferPrograms.beginEntities();
|
||||
else if (inEntityLoop) GbufferPrograms.onEntityRenderBoundary();
|
||||
try {
|
||||
bufferSource.endBatchWithType(TransparencyType.OPAQUE, activeGroups);
|
||||
bufferSource.endBatch(activeGroups);
|
||||
} finally {
|
||||
if (wrapEntities) GbufferPrograms.endEntities();
|
||||
}
|
||||
if (instancedThisCycle) {
|
||||
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
|
||||
@@ -217,10 +240,32 @@ public final class ModelPartBatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private void drawBatches() {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_ENTITY_LAYER_LOOP);
|
||||
try {
|
||||
bufferSource.endBatchWithType(TransparencyType.OPAQUE, activeGroups);
|
||||
bufferSource.endBatch(activeGroups);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public boolean entityPassActive() {
|
||||
return active && mode == Mode.ENTITIES && !shadow;
|
||||
}
|
||||
|
||||
public boolean isShadowPass() {
|
||||
return active && shadow;
|
||||
}
|
||||
|
||||
public static boolean recordShadow(World world, Entity entity, double x, double y, double z, float shadowAlpha, float partialTicks, float shadowSize) {
|
||||
return INSTANCE.shadows.record(world, entity, x, y, z, shadowAlpha, partialTicks, shadowSize);
|
||||
}
|
||||
|
||||
public static boolean partDraw(ModelRenderer part, float scale, ModelPartMeshBuilder builder) {
|
||||
final ModelPartBatcher r = INSTANCE;
|
||||
if (!r.active) {
|
||||
@@ -244,15 +289,6 @@ public final class ModelPartBatcher {
|
||||
}
|
||||
|
||||
private boolean queuePart(ModelRenderer part, float scale, ModelPartMeshBuilder builder) {
|
||||
if (shaderPipeline != null) {
|
||||
if (GLStateManager.getActiveProgram() != shaderPipeline.getActivePassProgramId()) {
|
||||
bail(BailReason.FOREIGN_PROGRAM);
|
||||
return false;
|
||||
}
|
||||
} else if (GLStateManager.getOverlayA() != 0.0f) {
|
||||
bail(BailReason.HURT_FLASH);
|
||||
return false;
|
||||
}
|
||||
final PartTemplate entry = templateFor(part, scale, builder);
|
||||
if (entry.emptyVanillaPart) {
|
||||
return true;
|
||||
@@ -260,7 +296,19 @@ public final class ModelPartBatcher {
|
||||
if (shadow && isDepthEqualDecalState()) {
|
||||
return true;
|
||||
}
|
||||
final TesrMaterial material = EntityMaterials.fromCurrentState();
|
||||
return queueTemplate(entry.template, entry.cubes, scale, null);
|
||||
}
|
||||
|
||||
public boolean queueTemplate(TemplateBuffer template, TesrMaterial material) {
|
||||
return queueTemplate(template, null, 1.0f, material);
|
||||
}
|
||||
|
||||
private boolean queueTemplate(TemplateBuffer template, CubeParams[] cubes, float scale, TesrMaterial explicitMaterial) {
|
||||
if (shaderPipeline != null && GLStateManager.getActiveProgram() != shaderPipeline.getActivePassProgramId()) {
|
||||
bail(BailReason.FOREIGN_PROGRAM);
|
||||
return false;
|
||||
}
|
||||
final TesrMaterial material = explicitMaterial != null ? explicitMaterial : EntityMaterials.fromCurrentState(unitTexIdentity());
|
||||
if (material == null) {
|
||||
bail(BailReason.MATERIAL);
|
||||
if (Tracy.ENABLED) {
|
||||
@@ -283,35 +331,26 @@ public final class ModelPartBatcher {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final TemplateBuffer template = entry.template;
|
||||
if (template == null) {
|
||||
bail(BailReason.TEMPLATE);
|
||||
if (Tracy.ENABLED) BailClassCounts.TEMPLATE.add(TesrAttribution.currentRenderable);
|
||||
return false;
|
||||
}
|
||||
|
||||
Matrix4f texMatrix = null;
|
||||
if (texture != null) {
|
||||
final Matrix4f unitMatrix = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
if (!MatrixHelper.isIdentity(unitMatrix)) {
|
||||
texMatrix = texMatrixScratch.set(unitMatrix);
|
||||
if (material.isDepthEqual()) {
|
||||
entry.forceStream = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
final Matrix4f texMatrix = texture != null && !unitTexIdentity() ? texMatrixScratch : null;
|
||||
final PassOverride pass = shaderPipeline != null ? PassOverride.capture() : PassOverride.NONE;
|
||||
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
final PolygonState polygon = GLStateManager.getPolygonState();
|
||||
final float offsetFactor = offset ? polygon.getOffsetFactor() : 0.0f;
|
||||
final float offsetUnits = offset ? polygon.getOffsetUnits() : 0.0f;
|
||||
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits);
|
||||
final int glintSlot = material.special() == TesrMaterial.SpecialRender.GLINT ? lastBoundGlintSlot : ShaderGlint.NO_TINT;
|
||||
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot);
|
||||
// Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id
|
||||
final int entityId = mode == Mode.ENTITIES || pass.isEntityPhase()
|
||||
? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity())
|
||||
: CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
final int entityId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
|
||||
final Color4 color = GLStateManager.getColor();
|
||||
final int colorABGR = ColorABGR.pack(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
final int colorABGR = AngelicaBufferSource.packAbgr(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
final int overlayABGR = shaderPipeline != null ? AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor()) : AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
|
||||
final long entityInfo = shaderPipeline != null ? InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedItem()) : 0L;
|
||||
final int packedLight = GLSMConfig.packedLastBrightness();
|
||||
final int mvGen = GLStateManager.getMvGeneration();
|
||||
if (!mvCaptured || mvGen != lastMvGeneration) {
|
||||
@@ -319,29 +358,43 @@ public final class ModelPartBatcher {
|
||||
lastMvGeneration = mvGen;
|
||||
mvCaptured = true;
|
||||
}
|
||||
activeGroups.queue(template, layer, material, modelView, packedLight, colorABGR, entityId, texMatrix, entry.forceStream);
|
||||
activeGroups.queue(template, cubes, scale, layer, material, modelView, packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix);
|
||||
parts++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private final ReferenceOpenHashSet<Class<?>> loggedMaterialBails = new ReferenceOpenHashSet<>();
|
||||
private final BlendState bailBlend = new BlendState();
|
||||
private final AlphaState bailAlpha = new AlphaState();
|
||||
|
||||
private void logMaterialBail() {
|
||||
final Class<?> cls = TesrAttribution.currentRenderable;
|
||||
if (cls == null || !loggedMaterialBails.add(cls)) return;
|
||||
final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
|
||||
final BlendState blendState = GLStateManager.getBlendState();
|
||||
AngelicaMod.LOGGER.info("TESR MATERIAL bail: cls={} shadow={} textured={} texAnimated={} blend={} src={} dst={} alphaTest={} alphaFunc={} alphaRef={} depthFunc={} depthMask={}",
|
||||
cls.getName(), shadow, textured,
|
||||
textured && !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0)),
|
||||
GLStateManager.getBlendMode().isEnabled(), blendState.getSrcRgb(), blendState.getDstRgb(),
|
||||
GLStateManager.getAlphaTest().isEnabled(),
|
||||
GLStateManager.getAlphaState().getFunction(), GLStateManager.getAlphaState().getReference(),
|
||||
GLStateManager.getDepthState().getFunc(), GLStateManager.getDepthState().isEnabled());
|
||||
final BlendState blendState = GLStateManager.getEffectiveBlendState(bailBlend);
|
||||
final AlphaState alphaState = GLStateManager.getEffectiveAlphaState(bailAlpha);
|
||||
AngelicaMod.LOGGER.info("TESR MATERIAL bail: cls={} shadow={} textured={} texAnimated={} blend={} src={} dst={} alphaTest={} alphaFunc={} alphaRef={} depthFunc={} depthMask={}", cls.getName(), shadow, textured, textured && !unitTexIdentity(), GLStateManager.isEffectiveBlendEnabled(), blendState.getSrcRgb(), blendState.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alphaState.getFunction(), alphaState.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled());
|
||||
}
|
||||
|
||||
static int groupId(boolean piped, boolean entityPhase, int entityId, int blockEntityId) {
|
||||
if (!piped) return 0;
|
||||
return entityPhase ? entityId : blockEntityId;
|
||||
}
|
||||
|
||||
private boolean unitTexIdentity() {
|
||||
final int generation = GLStateManager.getTexMatrixGeneration();
|
||||
if (!texCaptured || generation != texGeneration) {
|
||||
final Matrix4f unitMatrix = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
texIdentity = MatrixHelper.isIdentity(unitMatrix);
|
||||
if (!texIdentity) texMatrixScratch.set(unitMatrix);
|
||||
texGeneration = generation;
|
||||
texCaptured = true;
|
||||
}
|
||||
return texIdentity;
|
||||
}
|
||||
|
||||
private static boolean isDepthEqualDecalState() {
|
||||
return GLStateManager.getDepthState().getFunc() == GL11.GL_EQUAL && !GLStateManager.getDepthState().isEnabled();
|
||||
return GLStateManager.getDepthState().getFunc() == GL11.GL_EQUAL && !GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
}
|
||||
|
||||
private PartTemplate templateFor(ModelRenderer part, float scale, ModelPartMeshBuilder builder) {
|
||||
@@ -357,6 +410,7 @@ public final class ModelPartBatcher {
|
||||
final PartTemplate entry = reuse != null ? reuse : new PartTemplate();
|
||||
entry.scale = scale;
|
||||
entry.template = null;
|
||||
entry.cubes = null;
|
||||
entry.emptyVanillaPart = false;
|
||||
final List<ModelBox> boxes = part.cubeList;
|
||||
if (builder != null) {
|
||||
@@ -372,6 +426,7 @@ public final class ModelPartBatcher {
|
||||
boxes.get(i).render(tess, scale);
|
||||
}
|
||||
entry.template = captureBackend.endCaptureToTemplate();
|
||||
captureCubes(entry, boxes);
|
||||
} else {
|
||||
entry.emptyVanillaPart = part.getClass() == ModelRenderer.class;
|
||||
}
|
||||
@@ -379,21 +434,35 @@ public final class ModelPartBatcher {
|
||||
return entry;
|
||||
}
|
||||
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
private static void captureCubes(PartTemplate entry, List<ModelBox> boxes) {
|
||||
final int n = boxes.size();
|
||||
CubeParams[] cubes = null;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!(boxes.get(i) instanceof ModelBoxData data)) return;
|
||||
final CubeParams params = data.angelica$cubeParams();
|
||||
if (params == null) return;
|
||||
if (cubes == null) cubes = new CubeParams[n];
|
||||
cubes[i] = params;
|
||||
}
|
||||
entry.cubes = cubes;
|
||||
}
|
||||
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
|
||||
if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass)
|
||||
&& offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits) {
|
||||
&& offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits && glintSlot == lastLayerGlintSlot) {
|
||||
return lastLayer;
|
||||
}
|
||||
RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits));
|
||||
RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits, glintSlot));
|
||||
if (layer == null) {
|
||||
layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits);
|
||||
layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits), layer);
|
||||
layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits, glintSlot);
|
||||
layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits, glintSlot), layer);
|
||||
}
|
||||
lastLayerTexture = texture;
|
||||
lastLayerMaterial = material;
|
||||
lastLayerPass = pass;
|
||||
lastLayerOffsetFactor = offsetFactor;
|
||||
lastLayerOffsetUnits = offsetUnits;
|
||||
lastLayerGlintSlot = glintSlot;
|
||||
lastLayer = layer;
|
||||
return layer;
|
||||
}
|
||||
@@ -409,11 +478,21 @@ public final class ModelPartBatcher {
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
shadows.reset();
|
||||
groups.clear();
|
||||
shadowGroups.clear();
|
||||
templates.clear();
|
||||
bufferSource.freeBuffers();
|
||||
active = false;
|
||||
shaderPipeline = null;
|
||||
layers.clear();
|
||||
scratchKey.set(null, null, null, 0.0f, 0.0f, ShaderGlint.NO_TINT);
|
||||
lastLayerTexture = null;
|
||||
lastLayerMaterial = null;
|
||||
lastLayerPass = null;
|
||||
lastLayer = null;
|
||||
loggedMaterialBails.clear();
|
||||
lastBoundLocation = null;
|
||||
}
|
||||
|
||||
public String getDebugString() {
|
||||
|
||||
@@ -4,21 +4,24 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
|
||||
import net.coderbot.batchedentityrendering.impl.SegmentedBufferBuilder;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
@@ -29,6 +32,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
static final int DEMOTE_AFTER_STABLE_FRAMES = 4;
|
||||
static final long IDLE_RESET_FRAMES = 100;
|
||||
static final long GROUP_TTL_MS = AngelicaTesrMeshCache.LRU_TIMEOUT_MS;
|
||||
static final int MERGE_SCAN = 4;
|
||||
|
||||
private static final Tracy.ZoneId Z_TESR_REBUILD = Tracy.zoneId("tesrRebuild", Tracy.COLOR_CLIENT);
|
||||
|
||||
@@ -42,6 +46,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
private final Matrix4f drawMV = new Matrix4f();
|
||||
private final Matrix4f savedMV = new Matrix4f();
|
||||
private boolean mvSaved;
|
||||
private final Matrix4f savedTexMatrix = new Matrix4f();
|
||||
private boolean texApplied;
|
||||
private final float[] matScratch = new float[16];
|
||||
private double camX, camY, camZ;
|
||||
private long frameMark;
|
||||
@@ -52,19 +58,75 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
long streamedInstances;
|
||||
long instancedDraws;
|
||||
long instancedInstances;
|
||||
long cubeInstances;
|
||||
long texMatrixRuns;
|
||||
|
||||
private InstancedTemplateRenderer instanced;
|
||||
private boolean instancedActive;
|
||||
private DeferredWorldRenderingPipeline deferred;
|
||||
private TesrInstancingPipeline deferred;
|
||||
private final MeshBuffer fallbackMesh = new MeshBuffer();
|
||||
private ByteBuffer fallbackScratch;
|
||||
|
||||
static final class TexRun {
|
||||
final Matrix4f matrix = new Matrix4f();
|
||||
boolean identity;
|
||||
int start, end;
|
||||
int parts, instances;
|
||||
TexRun next, last;
|
||||
}
|
||||
|
||||
static final class InstanceColumns {
|
||||
final ObjectArrayList<TemplateBuffer> templates = new ObjectArrayList<>();
|
||||
final ObjectArrayList<CubeParams[]> cubes = new ObjectArrayList<>();
|
||||
final FloatArrayList scales = new FloatArrayList();
|
||||
final IntArrayList lights = new IntArrayList();
|
||||
final IntArrayList colors = new IntArrayList();
|
||||
final IntArrayList overlays = new IntArrayList();
|
||||
final LongArrayList infos = new LongArrayList();
|
||||
final ObjectArrayList<TexRun> runs = new ObjectArrayList<>();
|
||||
float[] matrices = new float[16 * 64];
|
||||
int size;
|
||||
|
||||
void add(TemplateBuffer templateOrNull, Matrix4fc mv, int light, int color, int overlay, long info) {
|
||||
final int base = size * 16;
|
||||
if (base + 16 > matrices.length) {
|
||||
matrices = Arrays.copyOf(matrices, matrices.length << 1);
|
||||
}
|
||||
mv.get(matrices, base);
|
||||
if (templateOrNull != null) templates.add(templateOrNull);
|
||||
lights.add(light);
|
||||
colors.add(color);
|
||||
overlays.add(overlay);
|
||||
infos.add(info);
|
||||
size++;
|
||||
}
|
||||
|
||||
void addCubes(TemplateBuffer templateOrNull, CubeParams[] cubeParams, float scale, Matrix4fc mv, int light, int color, int overlay, long info) {
|
||||
cubes.add(cubeParams);
|
||||
scales.add(scale);
|
||||
add(templateOrNull, mv, light, color, overlay, info);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
templates.clear();
|
||||
cubes.clear();
|
||||
scales.clear();
|
||||
lights.clear();
|
||||
colors.clear();
|
||||
overlays.clear();
|
||||
infos.clear();
|
||||
runs.clear();
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Group {
|
||||
final RenderLayer layer;
|
||||
final TesrMaterial material;
|
||||
final int blockEntityId;
|
||||
final boolean opaque;
|
||||
final boolean stream;
|
||||
final boolean mergeRuns;
|
||||
Class<?> renderableClass;
|
||||
int rebuildsWindow;
|
||||
long anchorX, anchorY, anchorZ;
|
||||
@@ -73,15 +135,14 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
long lastUsedMs;
|
||||
int entityColor;
|
||||
|
||||
final ObjectArrayList<TemplateBuffer> instTemplates = new ObjectArrayList<>();
|
||||
final FloatArrayList instMatrices = new FloatArrayList();
|
||||
final IntArrayList instLights = new IntArrayList();
|
||||
final IntArrayList instColors = new IntArrayList();
|
||||
final InstanceColumns templateColumns = new InstanceColumns();
|
||||
final InstanceColumns cubeColumns = new InstanceColumns();
|
||||
final ObjectArrayList<Matrix4f> instTexMatrices = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<Matrix4f> texMatrixPool = new ObjectArrayList<>();
|
||||
private int texMatrixPoolUsed;
|
||||
private final ObjectArrayList<TexRun> runPool = new ObjectArrayList<>();
|
||||
private int runPoolUsed;
|
||||
long hashAcc;
|
||||
int count;
|
||||
|
||||
long builtHash;
|
||||
int builtCount = -1;
|
||||
@@ -106,13 +167,49 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
instTexMatrices.add(texMatrixPool.get(texMatrixPoolUsed++).set(src));
|
||||
}
|
||||
|
||||
TexRun openOrExtendRun(InstanceColumns cols, Matrix4f texMatrix) {
|
||||
final ObjectArrayList<TexRun> list = cols.runs;
|
||||
final int part = cols.size;
|
||||
final boolean identity = texMatrix == null;
|
||||
final int size = list.size();
|
||||
final int floor = Math.max(0, mergeRuns ? size - MERGE_SCAN : size - 1);
|
||||
for (int i = size - 1; i >= floor; i--) {
|
||||
final TexRun head = list.get(i);
|
||||
if (head.identity != identity || (!identity && !head.matrix.equals(texMatrix))) continue;
|
||||
if (head.last.end != part) {
|
||||
final TexRun segment = pooledRun(part);
|
||||
head.last.next = segment;
|
||||
head.last = segment;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
final TexRun head = pooledRun(part);
|
||||
head.identity = identity;
|
||||
if (!identity) head.matrix.set(texMatrix);
|
||||
list.add(head);
|
||||
return head;
|
||||
}
|
||||
|
||||
private TexRun pooledRun(int part) {
|
||||
if (runPoolUsed == runPool.size()) {
|
||||
runPool.add(new TexRun());
|
||||
}
|
||||
final TexRun run = runPool.get(runPoolUsed++);
|
||||
run.start = part;
|
||||
run.end = part;
|
||||
run.parts = 0;
|
||||
run.instances = 0;
|
||||
run.next = null;
|
||||
run.last = run;
|
||||
return run;
|
||||
}
|
||||
|
||||
void clearInstances() {
|
||||
instTemplates.clear();
|
||||
instMatrices.clear();
|
||||
instLights.clear();
|
||||
instColors.clear();
|
||||
templateColumns.clear();
|
||||
cubeColumns.clear();
|
||||
instTexMatrices.clear();
|
||||
texMatrixPoolUsed = 0;
|
||||
runPoolUsed = 0;
|
||||
}
|
||||
|
||||
Group(RenderLayer layer, TesrMaterial material, int blockEntityId) {
|
||||
@@ -121,12 +218,14 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
this.blockEntityId = blockEntityId;
|
||||
this.opaque = material.transparency() == TesrMaterial.Transparency.OPAQUE;
|
||||
this.stream = material.isStream();
|
||||
this.mergeRuns = material.isDepthEqual() && material.isNoDepthWrite();
|
||||
this.streaming = stream;
|
||||
}
|
||||
}
|
||||
|
||||
private final Reference2ObjectOpenHashMap<RenderLayer, Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>>> groups = new Reference2ObjectOpenHashMap<>();
|
||||
private final Reference2ObjectOpenHashMap<RenderLayer, ObjectArrayList<Group>> byLayer = new Reference2ObjectOpenHashMap<>();
|
||||
private Group lastGroup;
|
||||
|
||||
RetainedTesrGroups(AngelicaBufferSource source) {
|
||||
this(source, System::currentTimeMillis);
|
||||
@@ -145,7 +244,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
beginPass(base, camX, camY, camZ, instancedRenderer, null);
|
||||
}
|
||||
|
||||
void beginPass(Matrix4f base, double camX, double camY, double camZ, InstancedTemplateRenderer instancedRenderer, DeferredWorldRenderingPipeline deferred) {
|
||||
void beginPass(Matrix4f base, double camX, double camY, double camZ, InstancedTemplateRenderer instancedRenderer, TesrInstancingPipeline deferred) {
|
||||
baseMV.set(base);
|
||||
baseMVInv.set(base).invert();
|
||||
this.camX = camX;
|
||||
@@ -158,23 +257,28 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
nowMs = clock.getAsLong();
|
||||
}
|
||||
|
||||
private Group groupFor(RenderLayer layer, TesrMaterial material, int blockEntityId) {
|
||||
Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>> byMaterial = groups.get(layer);
|
||||
if (byMaterial == null) {
|
||||
byMaterial = new Reference2ObjectOpenHashMap<>();
|
||||
groups.put(layer, byMaterial);
|
||||
}
|
||||
Int2ObjectOpenHashMap<Group> byId = byMaterial.get(material);
|
||||
if (byId == null) {
|
||||
byId = new Int2ObjectOpenHashMap<>();
|
||||
byMaterial.put(material, byId);
|
||||
}
|
||||
Group group = byId.get(blockEntityId);
|
||||
if (group == null) {
|
||||
group = new Group(layer, material, blockEntityId);
|
||||
group.lastUsedMs = nowMs;
|
||||
byId.put(blockEntityId, group);
|
||||
byLayer.computeIfAbsent(layer, l -> new ObjectArrayList<>()).add(group);
|
||||
private Group groupFor(RenderLayer layer, TesrMaterial material, int id) {
|
||||
final int key = instanceableMaterial(material) ? 0 : id;
|
||||
Group group = lastGroup;
|
||||
if (group == null || group.layer != layer || group.material != material || group.blockEntityId != key) {
|
||||
Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>> byMaterial = groups.get(layer);
|
||||
if (byMaterial == null) {
|
||||
byMaterial = new Reference2ObjectOpenHashMap<>();
|
||||
groups.put(layer, byMaterial);
|
||||
}
|
||||
Int2ObjectOpenHashMap<Group> byId = byMaterial.get(material);
|
||||
if (byId == null) {
|
||||
byId = new Int2ObjectOpenHashMap<>();
|
||||
byMaterial.put(material, byId);
|
||||
}
|
||||
group = byId.get(key);
|
||||
if (group == null) {
|
||||
group = new Group(layer, material, key);
|
||||
group.lastUsedMs = nowMs;
|
||||
byId.put(key, group);
|
||||
byLayer.computeIfAbsent(layer, l -> new ObjectArrayList<>()).add(group);
|
||||
}
|
||||
lastGroup = group;
|
||||
}
|
||||
if (group.renderableClass == null) {
|
||||
group.renderableClass = TesrAttribution.currentRenderable;
|
||||
@@ -186,8 +290,12 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
promote(groupFor(layer, material, blockEntityId));
|
||||
}
|
||||
|
||||
void queue(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int blockEntityId, Matrix4f texMatrix, boolean forceStream) {
|
||||
final Group group = groupFor(layer, material, blockEntityId);
|
||||
void queue(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix) {
|
||||
queue(template, null, 1.0f, layer, material, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, id, texMatrix);
|
||||
}
|
||||
|
||||
void queue(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix) {
|
||||
final Group group = groupFor(layer, material, id);
|
||||
if (group.frameMark != frameMark) {
|
||||
if (frameMark - group.frameMark > IDLE_RESET_FRAMES) {
|
||||
group.streaming = group.stream;
|
||||
@@ -210,8 +318,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
}
|
||||
group.frameMark = frameMark;
|
||||
group.lastUsedMs = nowMs;
|
||||
group.entityColor = SegmentedBufferBuilder.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor());
|
||||
group.count = 0;
|
||||
group.entityColor = AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor());
|
||||
group.hashAcc = 0;
|
||||
group.clearInstances();
|
||||
if (!group.anchored || TesrAnchorMath.shouldReanchor(camX, camY, camZ, group.anchorX, group.anchorY, group.anchorZ)) {
|
||||
@@ -224,18 +331,25 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
source.declareUse(layer);
|
||||
}
|
||||
if (group.stream) {
|
||||
final boolean instanceable = !forceStream && instancedActive && (deferred == null || material.shader() == null);
|
||||
if (instanceable && (texMatrix == null || template.drawMode != layer.getDrawMode())) {
|
||||
group.count++;
|
||||
group.instTemplates.add(template);
|
||||
currentMV.get(matScratch);
|
||||
group.instMatrices.addElements(group.instMatrices.size(), matScratch);
|
||||
group.instLights.add(packedLight);
|
||||
group.instColors.add(colorABGR);
|
||||
final boolean instanceable = instanceable(group);
|
||||
if (instanceable && cubes != null) {
|
||||
final TexRun run = group.openOrExtendRun(group.cubeColumns, texMatrix);
|
||||
group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo);
|
||||
run.last.end = group.cubeColumns.size;
|
||||
run.parts++;
|
||||
run.instances += cubes.length;
|
||||
return;
|
||||
}
|
||||
if (instanceable) {
|
||||
final TexRun run = group.openOrExtendRun(group.templateColumns, texMatrix);
|
||||
group.templateColumns.add(template, currentMV, packedLight, colorABGR, overlayABGR, entityInfo);
|
||||
run.last.end = group.templateColumns.size;
|
||||
run.parts++;
|
||||
run.instances++;
|
||||
return;
|
||||
}
|
||||
if (template.drawMode == layer.getDrawMode()) {
|
||||
source.getBuffer(layer, blockEntityId).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix);
|
||||
source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix);
|
||||
streamedInstances++;
|
||||
return;
|
||||
}
|
||||
@@ -248,17 +362,12 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
}
|
||||
if (group.streaming && template.drawMode == layer.getDrawMode()) {
|
||||
group.frameHash += hash;
|
||||
source.getBuffer(layer, blockEntityId).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix);
|
||||
source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix);
|
||||
streamedInstances++;
|
||||
return;
|
||||
}
|
||||
group.hashAcc += hash;
|
||||
group.count++;
|
||||
group.instTemplates.add(template);
|
||||
scratchMat.get(matScratch);
|
||||
group.instMatrices.addElements(group.instMatrices.size(), matScratch);
|
||||
group.instLights.add(packedLight);
|
||||
group.instColors.add(colorABGR);
|
||||
group.templateColumns.add(template, scratchMat, packedLight, colorABGR, overlayABGR, entityInfo);
|
||||
group.addTexMatrix(texMatrix);
|
||||
}
|
||||
|
||||
@@ -267,8 +376,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
final ObjectArrayList<Group> list = byLayer.get(layer);
|
||||
if (list == null) return false;
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (group.frameMark == frameMark && group.count > 0) return true;
|
||||
if (live(list.get(i))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -277,101 +385,222 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
public void drawLayer(RenderLayer layer) {
|
||||
final ObjectArrayList<Group> list = byLayer.get(layer);
|
||||
if (list == null) return;
|
||||
boolean variantResolved = false;
|
||||
boolean variantAvailable = false;
|
||||
boolean variantBound = false;
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (group.frameMark != frameMark || group.count == 0) continue;
|
||||
if (group.stream && instancedActive && (deferred == null || group.material.shader() == null)) {
|
||||
restoreMV();
|
||||
if (deferred == null) {
|
||||
instancedDraws += instanced.drawGroup(group.instTemplates, group.instMatrices, group.instLights, group.instColors, group.count, nowMs);
|
||||
instancedInstances += group.count;
|
||||
continue;
|
||||
}
|
||||
if (!variantResolved) {
|
||||
variantResolved = true;
|
||||
variantAvailable = deferred.hasTesrInstancedVariant();
|
||||
}
|
||||
if (variantAvailable) {
|
||||
applyGroupRenderState(group);
|
||||
if (!variantBound) {
|
||||
variantBound = true;
|
||||
deferred.rebindCurrentPass();
|
||||
deferred.bindTesrInstancedVariant();
|
||||
}
|
||||
instancedDraws += instanced.drawGroup(group.instTemplates, group.instMatrices, group.instLights, group.instColors, group.count, nowMs);
|
||||
instancedInstances += group.count;
|
||||
} else {
|
||||
drawGroupCpuFallback(group);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
variantBound = false;
|
||||
if (group.builtCount != group.count || group.builtHash != group.hashAcc || !group.mesh.isUploaded()) {
|
||||
if (group.mesh.isUploaded()) {
|
||||
group.consecutiveRebuilds++;
|
||||
}
|
||||
rebuild(group);
|
||||
} else {
|
||||
group.consecutiveRebuilds = 0;
|
||||
}
|
||||
draw(group);
|
||||
if (group.consecutiveRebuilds >= PROMOTE_AFTER_REBUILDS && !group.streaming && group.opaque) {
|
||||
promote(group);
|
||||
}
|
||||
boolean matrixAvailable = true;
|
||||
boolean cubeAvailable = true;
|
||||
if (deferred != null && anyInstanceable(list)) {
|
||||
deferred.rebindCurrentPass();
|
||||
matrixAvailable = deferred.hasInstancedVariant(Instancing.TEMPLATE);
|
||||
cubeAvailable = deferred.hasInstancedVariant(Instancing.CUBE);
|
||||
}
|
||||
final int need = drawUninstanced(list, matrixAvailable, cubeAvailable);
|
||||
restoreMV();
|
||||
if (variantBound) {
|
||||
if ((need & NEED_MATRIX) != 0) {
|
||||
if (deferred != null) deferred.bindInstancedVariant(Instancing.TEMPLATE);
|
||||
drawMatrixVariant(list, cubeAvailable);
|
||||
}
|
||||
if ((need & NEED_CUBE) != 0) {
|
||||
if (deferred != null) deferred.bindInstancedVariant(Instancing.CUBE);
|
||||
drawCubeVariant(list);
|
||||
}
|
||||
if (need != 0 && deferred != null) {
|
||||
deferred.rebindCurrentPass();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyGroupRenderState(Group group) {
|
||||
AngelicaBufferSource.setEntityColor(group.entityColor);
|
||||
source.applyIdNoRebind(group.blockEntityId);
|
||||
private static final int NEED_MATRIX = 1;
|
||||
private static final int NEED_CUBE = 2;
|
||||
|
||||
private boolean live(Group g) {
|
||||
return g.frameMark == frameMark && (g.templateColumns.size > 0 || g.cubeColumns.size > 0);
|
||||
}
|
||||
|
||||
private boolean anyInstanceable(ObjectArrayList<Group> list) {
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (live(group) && instanceable(group)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int drawUninstanced(ObjectArrayList<Group> list, boolean matrixAvailable, boolean cubeAvailable) {
|
||||
int need = 0;
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (!live(group)) continue;
|
||||
if (!instanceable(group)) {
|
||||
drawRetained(group);
|
||||
continue;
|
||||
}
|
||||
if (group.templateColumns.size > 0) {
|
||||
if (matrixAvailable) {
|
||||
need |= NEED_MATRIX;
|
||||
} else {
|
||||
drawInstancesCpu(group, group.templateColumns);
|
||||
}
|
||||
}
|
||||
if (group.cubeColumns.size > 0) {
|
||||
if (cubeAvailable) {
|
||||
need |= NEED_CUBE;
|
||||
} else if (matrixAvailable) {
|
||||
need |= NEED_MATRIX;
|
||||
} else {
|
||||
drawInstancesCpu(group, group.cubeColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
private void drawMatrixVariant(ObjectArrayList<Group> list, boolean cubeAvailable) {
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (!live(group) || !instanceable(group)) continue;
|
||||
if (group.templateColumns.size > 0) {
|
||||
drawTemplateRuns(group.templateColumns);
|
||||
}
|
||||
if (!cubeAvailable && group.cubeColumns.size > 0) {
|
||||
drawTemplateRuns(group.cubeColumns);
|
||||
}
|
||||
}
|
||||
restoreTexMatrix();
|
||||
}
|
||||
|
||||
private void drawCubeVariant(ObjectArrayList<Group> list) {
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
final Group group = list.get(i);
|
||||
if (!live(group) || !instanceable(group) || group.cubeColumns.size == 0) continue;
|
||||
drawCubeInstances(group);
|
||||
}
|
||||
restoreTexMatrix();
|
||||
}
|
||||
|
||||
private boolean instanceable(Group group) {
|
||||
return instanceableMaterial(group.material);
|
||||
}
|
||||
|
||||
private boolean instanceableMaterial(TesrMaterial material) {
|
||||
return material.isStream() && instancedActive && (deferred == null || material.shader() == null);
|
||||
}
|
||||
|
||||
private void drawRetained(Group group) {
|
||||
if (group.builtCount != group.templateColumns.size || group.builtHash != group.hashAcc || !group.mesh.isUploaded()) {
|
||||
if (group.mesh.isUploaded()) {
|
||||
group.consecutiveRebuilds++;
|
||||
}
|
||||
rebuild(group);
|
||||
} else {
|
||||
group.consecutiveRebuilds = 0;
|
||||
}
|
||||
draw(group);
|
||||
if (group.consecutiveRebuilds >= PROMOTE_AFTER_REBUILDS && !group.streaming && group.opaque) {
|
||||
promote(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTemplateRuns(InstanceColumns cols) {
|
||||
for (int r = 0, n = cols.runs.size(); r < n; r++) {
|
||||
final TexRun run = cols.runs.get(r);
|
||||
applyTexMatrix(run);
|
||||
instancedDraws += instanced.drawTemplates(cols, run, nowMs);
|
||||
instancedInstances += run.parts;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCubeInstances(Group group) {
|
||||
final InstanceColumns cols = group.cubeColumns;
|
||||
for (int r = 0, n = cols.runs.size(); r < n; r++) {
|
||||
final TexRun run = cols.runs.get(r);
|
||||
applyTexMatrix(run);
|
||||
instanced.drawCubes(cols, run);
|
||||
instancedDraws++;
|
||||
cubeInstances += run.instances;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTexMatrix(TexRun run) {
|
||||
if (run.identity && !texApplied) return;
|
||||
if (!texApplied) {
|
||||
texApplied = true;
|
||||
savedTexMatrix.set(GLStateManager.getTextures().getTextureUnitMatrix(0));
|
||||
}
|
||||
GLStateManager.setTextureMatrix(0, run.identity ? IDENTITY : run.matrix);
|
||||
if (!run.identity) texMatrixRuns++;
|
||||
}
|
||||
|
||||
private void restoreTexMatrix() {
|
||||
if (!texApplied) return;
|
||||
texApplied = false;
|
||||
GLStateManager.setTextureMatrix(0, savedTexMatrix);
|
||||
}
|
||||
|
||||
private static final Matrix4f IDENTITY = new Matrix4f();
|
||||
|
||||
private void drawGroupCpuFallback(Group group) {
|
||||
AngelicaBufferSource.setEntityColor(group.entityColor);
|
||||
source.applyIdAndRebind(group.blockEntityId);
|
||||
static int entityFromInfo(long info) {
|
||||
return (short) info;
|
||||
}
|
||||
|
||||
static int blockEntityFromInfo(long info) {
|
||||
return (short) (info >>> 16);
|
||||
}
|
||||
|
||||
private int idFromInfo(long info) {
|
||||
return source.effectiveIdKind() == AngelicaBufferSource.GroupIdKind.ENTITY ? entityFromInfo(info) : blockEntityFromInfo(info);
|
||||
}
|
||||
|
||||
private void drawInstancesCpu(Group group, InstanceColumns cols) {
|
||||
saveMV();
|
||||
GLStateManager.setModelViewMatrix(IDENTITY);
|
||||
final VertexFormat format = group.layer.getVertexFormat();
|
||||
int start = 0;
|
||||
while (start < group.count) {
|
||||
final int drawMode = group.instTemplates.get(start).drawMode;
|
||||
int end = start;
|
||||
int totalVerts = 0;
|
||||
while (end < group.count && group.instTemplates.get(end).drawMode == drawMode) {
|
||||
totalVerts += group.instTemplates.get(end).vertexCount;
|
||||
end++;
|
||||
final ObjectArrayList<TemplateBuffer> templates = cols.templates;
|
||||
for (int r = 0, rn = cols.runs.size(); r < rn; r++) {
|
||||
final TexRun head = cols.runs.get(r);
|
||||
final Matrix4f texMatrix = head.identity ? null : head.matrix;
|
||||
for (TexRun seg = head; seg != null; seg = seg.next) {
|
||||
final int runEnd = seg.end;
|
||||
int sub = seg.start;
|
||||
while (sub < runEnd) {
|
||||
final long info = cols.infos.getLong(sub);
|
||||
final int overlay = cols.overlays.getInt(sub);
|
||||
int subEnd = sub;
|
||||
while (subEnd < runEnd && cols.infos.getLong(subEnd) == info && cols.overlays.getInt(subEnd) == overlay) {
|
||||
subEnd++;
|
||||
}
|
||||
AngelicaBufferSource.setEntityColor(overlay);
|
||||
source.applyIdAndRebind(idFromInfo(info));
|
||||
int start = sub;
|
||||
while (start < subEnd) {
|
||||
final int drawMode = templates.get(start).drawMode;
|
||||
int end = start;
|
||||
int totalVerts = 0;
|
||||
while (end < subEnd && templates.get(end).drawMode == drawMode) {
|
||||
totalVerts += templates.get(end).vertexCount;
|
||||
end++;
|
||||
}
|
||||
fallbackScratch = MeshBuffer.ensureCapacity(fallbackScratch, format.getVertexSize() * totalVerts, false);
|
||||
fallbackScratch.clear();
|
||||
final long base = memAddress0(fallbackScratch);
|
||||
long ptr = base;
|
||||
for (int i = start; i < end; i++) {
|
||||
System.arraycopy(cols.matrices, i * 16, matScratch, 0, 16);
|
||||
scratchMat.set(matScratch);
|
||||
ptr = VertexTransform.writeInstance(ptr, format, templates.get(i), scratchMat, scratchVec, cols.colors.getInt(i), cols.lights.getInt(i), texMatrix);
|
||||
}
|
||||
fallbackScratch.position((int) (ptr - base));
|
||||
fallbackScratch.flip();
|
||||
fallbackMesh.upload(format, drawMode, fallbackScratch, totalVerts);
|
||||
fallbackMesh.render();
|
||||
streamedInstances += end - start;
|
||||
start = end;
|
||||
}
|
||||
sub = subEnd;
|
||||
}
|
||||
}
|
||||
fallbackScratch = MeshBuffer.ensureCapacity(fallbackScratch, format.getVertexSize() * totalVerts, false);
|
||||
fallbackScratch.clear();
|
||||
final long base = memAddress0(fallbackScratch);
|
||||
long ptr = base;
|
||||
for (int i = start; i < end; i++) {
|
||||
group.instMatrices.getElements(i * 16, matScratch, 0, 16);
|
||||
scratchMat.set(matScratch);
|
||||
ptr = VertexTransform.writeInstance(ptr, format, group.instTemplates.get(i), scratchMat, scratchVec, group.instColors.getInt(i), group.instLights.getInt(i), null);
|
||||
}
|
||||
fallbackScratch.position((int) (ptr - base));
|
||||
fallbackScratch.flip();
|
||||
fallbackMesh.upload(format, drawMode, fallbackScratch, totalVerts);
|
||||
fallbackMesh.render();
|
||||
streamedInstances += end - start;
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
private void promote(Group group) {
|
||||
group.streaming = true;
|
||||
streamPromotions++;
|
||||
group.count = 0;
|
||||
group.hashAcc = 0;
|
||||
group.frameHash = 0;
|
||||
group.prevFrameHash = 0;
|
||||
@@ -383,6 +612,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
}
|
||||
|
||||
void sweep(long now) {
|
||||
lastGroup = null;
|
||||
for (final ObjectArrayList<Group> list : byLayer.values()) {
|
||||
for (int i = list.size() - 1; i >= 0; i--) {
|
||||
final Group group = list.get(i);
|
||||
@@ -408,8 +638,13 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
}
|
||||
byLayer.clear();
|
||||
groups.clear();
|
||||
lastGroup = null;
|
||||
fallbackMesh.delete();
|
||||
fallbackScratch = null;
|
||||
texApplied = false;
|
||||
instanced = null;
|
||||
instancedActive = false;
|
||||
deferred = null;
|
||||
}
|
||||
|
||||
int groupCount() {
|
||||
@@ -483,22 +718,20 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
|
||||
private void rebuildInner(Group group) {
|
||||
final VertexFormat format = group.layer.getVertexFormat();
|
||||
final InstanceColumns cols = group.templateColumns;
|
||||
int totalVerts = 0;
|
||||
for (int i = 0, n = group.instTemplates.size(); i < n; i++) {
|
||||
totalVerts += group.instTemplates.get(i).vertexCount;
|
||||
for (int i = 0, n = cols.templates.size(); i < n; i++) {
|
||||
totalVerts += cols.templates.get(i).vertexCount;
|
||||
}
|
||||
final int bytes = format.getVertexSize() * totalVerts;
|
||||
group.scratch = MeshBuffer.ensureCapacity(group.scratch, bytes, false);
|
||||
group.scratch.clear();
|
||||
final long base = memAddress0(group.scratch);
|
||||
long ptr = base;
|
||||
for (int i = 0, n = group.count; i < n; i++) {
|
||||
group.instMatrices.getElements(i * 16, matScratch, 0, 16);
|
||||
for (int i = 0, n = cols.size; i < n; i++) {
|
||||
System.arraycopy(cols.matrices, i * 16, matScratch, 0, 16);
|
||||
scratchMat.set(matScratch);
|
||||
final int light = group.instLights.getInt(i);
|
||||
final int color = group.instColors.getInt(i);
|
||||
final TemplateBuffer template = group.instTemplates.get(i);
|
||||
ptr = VertexTransform.writeInstance(ptr, format, template, scratchMat, scratchVec, color, light, group.instTexMatrices.get(i));
|
||||
ptr = VertexTransform.writeInstance(ptr, format, cols.templates.get(i), scratchMat, scratchVec, cols.colors.getInt(i), cols.lights.getInt(i), group.instTexMatrices.get(i));
|
||||
}
|
||||
group.scratch.position((int) (ptr - base));
|
||||
group.scratch.flip();
|
||||
@@ -506,7 +739,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
|
||||
group.mesh.upload(format, group.layer.getDrawMode(), group.scratch, group.builtVertexCount, true);
|
||||
group.meshBytes = bytes;
|
||||
group.builtHash = group.hashAcc;
|
||||
group.builtCount = group.count;
|
||||
group.builtCount = cols.size;
|
||||
}
|
||||
|
||||
private void draw(Group group) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.COLOR_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.LIGHT_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.NORMAL_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.TEX_X_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.TEX_Y_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.VERTEX_SIZE;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.X_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.Y_INDEX;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.Z_INDEX;
|
||||
|
||||
public final class ShadowQuadMath {
|
||||
|
||||
public static final int RECORD_SIZE = 11;
|
||||
|
||||
public static final int MIN_X = 0, Y = 1, MIN_Z = 2, MAX_X = 3, MAX_Z = 4, U_MIN_X = 5, V_MIN_Z = 6, U_MAX_X = 7, V_MAX_Z = 8, COLOR = 9, LIGHT = 10;
|
||||
|
||||
private ShadowQuadMath() {}
|
||||
|
||||
public static int box(double ex, double ey, double ez, float size, int[] outIJK) {
|
||||
final int i = MathHelper.floor_double(ex - size);
|
||||
final int j = MathHelper.floor_double(ex + size);
|
||||
final int k = MathHelper.floor_double(ey - size);
|
||||
final int l = MathHelper.floor_double(ey);
|
||||
final int i1 = MathHelper.floor_double(ez - size);
|
||||
final int j1 = MathHelper.floor_double(ez + size);
|
||||
outIJK[0] = i;
|
||||
outIJK[1] = j;
|
||||
outIJK[2] = k;
|
||||
outIJK[3] = l;
|
||||
outIJK[4] = i1;
|
||||
outIJK[5] = j1;
|
||||
return (j - i + 1) * (l - k + 1) * (j1 - i1 + 1);
|
||||
}
|
||||
|
||||
public static boolean project(int[] out, int off, double x, double y, double z, int bx, int by, int bz, float shadowAlpha, float size, double ox, double oy, double oz, double minX, double maxX, double minY, double minZ, double maxZ, float brightness, int packedLight) {
|
||||
double alpha = ((double) shadowAlpha - (y - ((double) by + oy)) / 2.0D) * 0.5D * (double) brightness;
|
||||
if (alpha < 0.0D) return false;
|
||||
if (alpha > 1.0D) alpha = 1.0D;
|
||||
|
||||
final double qMinX = (double) bx + minX + ox;
|
||||
final double qMaxX = (double) bx + maxX + ox;
|
||||
final double qY = (double) by + minY + oy + 0.015625D;
|
||||
final double qMinZ = (double) bz + minZ + oz;
|
||||
final double qMaxZ = (double) bz + maxZ + oz;
|
||||
final float uMinX = (float) ((x - qMinX) / 2.0D / (double) size + 0.5D);
|
||||
final float uMaxX = (float) ((x - qMaxX) / 2.0D / (double) size + 0.5D);
|
||||
final float vMinZ = (float) ((z - qMinZ) / 2.0D / (double) size + 0.5D);
|
||||
final float vMaxZ = (float) ((z - qMaxZ) / 2.0D / (double) size + 0.5D);
|
||||
|
||||
out[off + MIN_X] = Float.floatToRawIntBits((float) qMinX);
|
||||
out[off + Y] = Float.floatToRawIntBits((float) qY);
|
||||
out[off + MIN_Z] = Float.floatToRawIntBits((float) qMinZ);
|
||||
out[off + MAX_X] = Float.floatToRawIntBits((float) qMaxX);
|
||||
out[off + MAX_Z] = Float.floatToRawIntBits((float) qMaxZ);
|
||||
out[off + U_MIN_X] = Float.floatToRawIntBits(uMinX);
|
||||
out[off + V_MIN_Z] = Float.floatToRawIntBits(vMinZ);
|
||||
out[off + U_MAX_X] = Float.floatToRawIntBits(uMaxX);
|
||||
out[off + V_MAX_Z] = Float.floatToRawIntBits(vMaxZ);
|
||||
out[off + COLOR] = 0xFFFFFF | ((int) ((float) alpha * 255.0F)) << 24;
|
||||
out[off + LIGHT] = packedLight;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void expand(int[] packed32, int[] records, int off, int normalPacked) {
|
||||
final int minX = records[off + MIN_X];
|
||||
final int y = records[off + Y];
|
||||
final int minZ = records[off + MIN_Z];
|
||||
final int maxX = records[off + MAX_X];
|
||||
final int maxZ = records[off + MAX_Z];
|
||||
final int uMinX = records[off + U_MIN_X];
|
||||
final int vMinZ = records[off + V_MIN_Z];
|
||||
final int uMaxX = records[off + U_MAX_X];
|
||||
final int vMaxZ = records[off + V_MAX_Z];
|
||||
final int color = records[off + COLOR];
|
||||
final int light = records[off + LIGHT];
|
||||
|
||||
write(packed32, 0, minX, y, minZ, uMinX, vMinZ, color, light, normalPacked);
|
||||
write(packed32, 1, minX, y, maxZ, uMinX, vMaxZ, color, light, normalPacked);
|
||||
write(packed32, 2, maxX, y, maxZ, uMaxX, vMaxZ, color, light, normalPacked);
|
||||
write(packed32, 3, maxX, y, minZ, uMaxX, vMinZ, color, light, normalPacked);
|
||||
}
|
||||
|
||||
private static void write(int[] packed32, int vertex, int x, int y, int z, int u, int v, int color, int light, int normal) {
|
||||
final int base = vertex * VERTEX_SIZE;
|
||||
packed32[base | X_INDEX] = x;
|
||||
packed32[base | Y_INDEX] = y;
|
||||
packed32[base | Z_INDEX] = z;
|
||||
packed32[base | TEX_X_INDEX] = u;
|
||||
packed32[base | TEX_Y_INDEX] = v;
|
||||
packed32[base | COLOR_INDEX] = color;
|
||||
packed32[base | LIGHT_INDEX] = light;
|
||||
packed32[base | NORMAL_INDEX] = normal;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ public final class TemplateBuffer {
|
||||
final int[] work;
|
||||
public final int vertexCount;
|
||||
public final int drawMode;
|
||||
long bucketEpoch;
|
||||
int bucketIndex;
|
||||
|
||||
public TemplateBuffer(int[] data, int vertexCount, int drawMode) {
|
||||
this.data = data;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
public final class TemplateCapture {
|
||||
|
||||
private TemplateCapture() {}
|
||||
|
||||
public static TemplateBuffer toTemplate(DirectTessellator direct) {
|
||||
final int vertexCount = direct.getVertexCount();
|
||||
if (vertexCount == 0) return null;
|
||||
final int drawMode = direct.getDrawMode();
|
||||
final VertexFormat format = direct.getVertexFormat();
|
||||
final ByteBuffer copy = direct.allocateBufferCopy();
|
||||
final int[] data = VertexTransform.decode(memAddress0(copy), format, vertexCount, drawMode);
|
||||
memFree(copy);
|
||||
return new TemplateBuffer(data, vertexCount, drawMode);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user