Fix terrain disappearing at screen edges with compute culling (#2212)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-04 13:05:14 -07:00
1 parent 6dfb0312b1
commit fcf8ad064a
8 files changed
+168 -98

No files matched your search

@@ -63,6 +63,7 @@ public final class SystemProperties {
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);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
@@ -131,6 +132,12 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyCamera {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT
}
public enum FlybyWeather {
CLEAR,
RAIN,
+1
View File
@@ -9,6 +9,7 @@
# route - straight | pan | circuit | static
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
#
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
@@ -84,6 +84,8 @@ public final class FlybyRunner {
private boolean vsyncOverridden;
private boolean showDebugInfoSaved;
private boolean showDebugInfoOverridden;
private int cameraSaved;
private boolean cameraOverridden;
private int shotEvery;
private int nextShot;
private int shot;
@@ -375,6 +377,11 @@ public final class FlybyRunner {
this.showDebugInfoOverridden = true;
mc.gameSettings.showDebugInfo = true;
}
if (!this.cameraOverridden) {
this.cameraSaved = mc.gameSettings.thirdPersonView;
this.cameraOverridden = true;
mc.gameSettings.thirdPersonView = SystemProperties.FLYBY_CAMERA.ordinal();
}
if ("isbrh".equals(SystemProperties.FLYBY_CRASH_TEST)) {
IsbrhTestRenderer.armCrashTest();
}
@@ -535,7 +542,7 @@ public final class FlybyRunner {
this.nextShot = this.shotEvery / 2;
FramePacer.beginStats();
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null);
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null) + " camera=" + SystemProperties.FLYBY_CAMERA;
if (Tracy.ENABLED) {
Tracy.message("flyby start route=" + this.route.id() + " length=" + this.runLength + this.route.lengthUnit() + " speed=" + this.route.speedOr(this.speed) + "b/t ticks=" + this.runTicks + " " + config);
}
@@ -757,6 +764,9 @@ public final class FlybyRunner {
final String summary = this.summarise(elapsedNs, player);
LOGGER.info(summary);
LOGGER.info(FramePacer.endStats());
if (mc.gameSettings.thirdPersonView != SystemProperties.FLYBY_CAMERA.ordinal()) {
LOGGER.warn("Flyby: camera left {} during the run (thirdPersonView={}), screenshots are not comparable", SystemProperties.FLYBY_CAMERA, mc.gameSettings.thirdPersonView);
}
if (mc.thePlayer != null) {
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + summary));
}
@@ -787,6 +797,7 @@ public final class FlybyRunner {
this.returnToOrigin(player);
this.restorePauseOnLostFocus(mc);
this.restoreShowDebugInfo(mc);
this.restoreCamera(mc);
this.armed = false;
sceneGuarded = false;
this.sceneClearRequested = this.sceneCommands.length > 0;
@@ -819,6 +830,12 @@ public final class FlybyRunner {
this.showDebugInfoOverridden = false;
}
private void restoreCamera(Minecraft mc) {
if (!this.cameraOverridden) return;
mc.gameSettings.thirdPersonView = this.cameraSaved;
this.cameraOverridden = false;
}
private void returnToOrigin(EntityClientPlayerMP player) {
player.motionX = 0.0D;
player.motionY = 0.0D;
@@ -969,6 +986,7 @@ public final class FlybyRunner {
final Minecraft mc = Minecraft.getMinecraft();
this.restorePauseOnLostFocus(mc);
this.restoreShowDebugInfo(mc);
this.restoreCamera(mc);
this.armed = false;
this.stopRecording(mc);
this.pendingRequest.set(null);
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.rendering.culling;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.nio.ByteBuffer;
@@ -10,9 +12,11 @@ public final class FrustumExtractor {
public static final int UBO_SIZE_BYTES = 160;
public static final int PLANE_COUNT = 6;
private static final float SECTION_HALF_EXTENT = 8.0f + 1.0f + 0.125f;
private static final float SECTION_CENTER = 8.0f;
private static final int PLANE_STRIDE = 16;
private static final int CONTROL_OFFSET = 96;
private static final int CAMERA_WORLD_OFFSET = 112;
private static final int CAMERA_BLOCK_OFFSET = 112;
private static final int BATCH_OFFSET = 128;
private static final int PYR_OFFSET = 144;
@@ -22,24 +26,26 @@ public final class FrustumExtractor {
return ByteBuffer.allocateDirect(UBO_SIZE_BYTES).order(ByteOrder.nativeOrder());
}
public static void writeStd140(Matrix4fc mvp, int visibleCount, int indexPointerMask, ByteBuffer out) {
public static void writeFrustum(Matrix4fc projection, Matrix4fc modelView, CameraTransform camera, Matrix4f mvp, ByteBuffer out) {
projection.mul(modelView, mvp);
final float r0x = mvp.m00(), r0y = mvp.m10(), r0z = mvp.m20(), r0w = mvp.m30();
final float r1x = mvp.m01(), r1y = mvp.m11(), r1z = mvp.m21(), r1w = mvp.m31();
final float r2x = mvp.m02(), r2y = mvp.m12(), r2z = mvp.m22(), r2w = mvp.m32();
final float r3x = mvp.m03(), r3y = mvp.m13(), r3z = mvp.m23(), r3w = mvp.m33();
final float cx = SECTION_CENTER - camera.fracX;
final float cy = SECTION_CENTER - camera.fracY;
final float cz = SECTION_CENTER - camera.fracZ;
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w);
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w);
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w);
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w);
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w);
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w);
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w, cx, cy, cz);
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w, cx, cy, cz);
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w, cx, cy, cz);
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w, cx, cy, cz);
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w, cx, cy, cz);
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w, cx, cy, cz);
out.putInt(CONTROL_OFFSET + 0, visibleCount);
out.putInt(CONTROL_OFFSET + 4, indexPointerMask);
out.putInt(CONTROL_OFFSET + 8, 0);
out.putInt(CONTROL_OFFSET + 12, 0);
out.putInt(BATCH_OFFSET, 0);
out.putInt(CAMERA_BLOCK_OFFSET + 0, camera.intX);
out.putInt(CAMERA_BLOCK_OFFSET + 4, camera.intY);
out.putInt(CAMERA_BLOCK_OFFSET + 8, camera.intZ);
}
public static void patchBatchEntryBase(int entryBase, ByteBuffer out) {
@@ -61,17 +67,10 @@ public final class FrustumExtractor {
out.putInt(CONTROL_OFFSET + 8, bypass ? 1 : 0);
}
public static void patchCameraWorld(float x, float y, float z, ByteBuffer out) {
out.putFloat(CAMERA_WORLD_OFFSET + 0, x);
out.putFloat(CAMERA_WORLD_OFFSET + 4, y);
out.putFloat(CAMERA_WORLD_OFFSET + 8, z);
}
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d) {
final float invLen = 1.0f / (float) Math.sqrt(a * a + b * b + c * c);
out.putFloat(offset + 0, a * invLen);
out.putFloat(offset + 4, b * invLen);
out.putFloat(offset + 8, c * invLen);
out.putFloat(offset + 12, d * invLen);
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d, float cx, float cy, float cz) {
out.putFloat(offset + 0, a);
out.putFloat(offset + 4, b);
out.putFloat(offset + 8, c);
out.putFloat(offset + 12, d + SECTION_HALF_EXTENT * (Math.abs(a) + Math.abs(b) + Math.abs(c)) + a * cx + b * cy + c * cz);
}
}
@@ -28,7 +28,6 @@ import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL40;
@@ -132,7 +131,6 @@ public final class GpuTerrainCuller {
private ByteBuffer uboBytes;
private Matrix4f mvp;
private Matrix4f mvNoTranslation;
private int passOutputBase;
private ChunkPrimitiveType walkPrimitiveType;
@@ -399,17 +397,8 @@ public final class GpuTerrainCuller {
uboBytes = FrustumExtractor.allocateUboByteBuffer();
}
final boolean shadow = ShadowRenderer.ACTIVE;
final Matrix4fc proj = matrices.projection();
final Matrix4fc mv = matrices.modelView();
if (mvp == null) mvp = new Matrix4f();
if (mvNoTranslation == null) mvNoTranslation = new Matrix4f();
mvNoTranslation.set(mv).setTranslation(0f, 0f, 0f);
proj.mul(mvNoTranslation, mvp);
final float camX = (float) camera.intX + camera.fracX;
final float camY = (float) camera.intY + camera.fracY;
final float camZ = (float) camera.intZ + camera.fracZ;
FrustumExtractor.writeStd140(mvp, 0, 0, uboBytes);
FrustumExtractor.patchCameraWorld(camX, camY, camZ, uboBytes);
FrustumExtractor.writeFrustum(matrices.projection(), matrices.modelView(), camera, mvp, uboBytes);
FrustumExtractor.patchBypassFrustum(shadow, uboBytes);
frustumUboBytes = uboBytes;
@@ -459,7 +448,7 @@ public final class GpuTerrainCuller {
}
if (computeActiveThisPass) {
prepareFrustumUbo(matrices, camera);
if (!chained) prepareFrustumUbo(matrices, camera);
syncSectionMetaIfDirty();
walkPass(renderLists, AngelicaRenderPassConfiguration.TRANSLUCENT_PASS, occlusionCamera, useBlockFaceCulling);
@@ -34,30 +34,24 @@ layout(std430, binding = 2) writeonly buffer IndirectCmds {
} indirect;
layout(std140, binding = 1) uniform Frustum {
vec4 planes[6]; // camera-relative planes from `proj * mv` (no translate baked in)
vec4 planes[6]; // planes from `proj * mv`; .w has the padded section box and camera fraction folded in
// control.x = visibleCount (iteration bound)
// control.y = indexPointerMask (0 non-sorted -> firstIndex masked to 0; 0xFFFFFFFF sorted)
// control.z = bypassFrustum (shadow passes; renderLists already carry the caster set)
uvec4 control;
// cameraWorld.xyz = current-frame camera (subtract from world AABB for frustum test)
vec4 cameraWorld;
// cameraBlock.xyz = integer part of the current-frame camera (subtract from section origin for frustum test)
ivec4 cameraBlock;
// batch.x = visible-list entryBase for this dispatch (batched culls)
uvec4 batch;
// pyr.zw = vertices and index elements per primitive, from ChunkPrimitiveType (exact as floats)
vec4 pyr;
} frustum;
bool insideFrustum(vec3 minP, vec3 maxP) {
// Planes are camera-relative; reduce world AABB to camera-relative before the n-vertex test.
vec3 cam = frustum.cameraWorld.xyz;
vec3 minR = minP - cam;
vec3 maxR = maxP - cam;
bool insideFrustum(ivec3 origin) {
vec3 rel = vec3(origin - frustum.cameraBlock.xyz);
for (int i = 0; i < 6; i++) {
vec4 p = frustum.planes[i];
vec3 ext = vec3(p.x > 0.0 ? maxR.x : minR.x,
p.y > 0.0 ? maxR.y : minR.y,
p.z > 0.0 ? maxR.z : minR.z);
if (dot(p.xyz, ext) + p.w < 0.0) return false;
if (dot(p.xyz, rel) + p.w < 0.0) return false;
}
return true;
}
@@ -74,10 +68,8 @@ void main() {
uint outputBase = packedEntry >> 8;
Section s = meta.sections[slot];
vec3 minP = vec3(s.origin.xyz);
vec3 maxP = minP + vec3(16.0);
bool inside = (frustum.control.z != 0u) || insideFrustum(minP, maxP);
bool inside = (frustum.control.z != 0u) || insideFrustum(s.origin.xyz);
uint indexMask = frustum.control.y;
uint vertsPerPrim = uint(frustum.pyr.z);
@@ -1,7 +1,8 @@
package com.gtnewhorizons.angelica.rendering.culling;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
@@ -11,6 +12,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class FrustumExtractorTest {
private static final float EPS = 1e-4f;
private static final Matrix4f IDENTITY = new Matrix4f();
private static final CameraTransform ORIGIN = new CameraTransform(0.0, 0.0, 0.0);
private static final float EYE_HEIGHT = 0.12f;
private static final double SECTION_PAD = 1.125;
private static final float HALF_EXTENT = 8f + (float) SECTION_PAD;
private static final double BOUNDARY_SKIP = 1e-3;
private static final int SWEEP_SECTIONS = 6;
@Test
void uboSizeIs160() {
@@ -19,73 +27,129 @@ class FrustumExtractorTest {
}
@Test
void patchBatchEntryBase_writesAtOffset128_andWriteStd140Resets() {
void patchBatchEntryBase_writesAtOffset128() {
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
FrustumExtractor.patchBatchEntryBase(777, out);
assertEquals(777, out.getInt(128));
FrustumExtractor.writeStd140(new Matrix4f(), 0, 0, out);
assertEquals(0, out.getInt(128), "writeStd140 must reset entryBase");
}
@Test
void identityMatrix_planesAreUnitNormalsAtUnitDistance() {
final Matrix4f identity = new Matrix4f();
void cameraFractionFoldsIntoPlaneConstant() {
final CameraTransform camera = new CameraTransform(-3.25, 70.5, 12.75);
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
FrustumExtractor.writeStd140(identity, 0, 0, out);
FrustumExtractor.writeFrustum(IDENTITY, IDENTITY, camera, new Matrix4f(), out);
assertPlane(out, 0, 1f, 0f, 0f, 1f);
assertPlane(out, 16, -1f, 0f, 0f, 1f);
assertPlane(out, 32, 0f, 1f, 0f, 1f);
assertPlane(out, 48, 0f, -1f, 0f, 1f);
assertPlane(out, 64, 0f, 0f, 1f, 1f);
assertPlane(out, 80, 0f, 0f, -1f, 1f);
assertEquals(1f + HALF_EXTENT + (8f + 0.25f), out.getFloat(12), EPS);
assertEquals(1f + HALF_EXTENT - (8f + 0.25f), out.getFloat(28), EPS);
assertEquals(1f + HALF_EXTENT + (8f - 0.5f), out.getFloat(44), EPS);
assertEquals(1f + HALF_EXTENT - (8f - 0.5f), out.getFloat(60), EPS);
assertEquals(1f + HALF_EXTENT + (8f - 0.75f), out.getFloat(76), EPS);
assertEquals(1f + HALF_EXTENT - (8f - 0.75f), out.getFloat(92), EPS);
assertEquals(-3, out.getInt(112));
assertEquals(70, out.getInt(116));
assertEquals(12, out.getInt(120));
assertEquals(0, out.getInt(124));
}
@Test
void identityMatrix_left_correct() {
final Matrix4f identity = new Matrix4f();
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
FrustumExtractor.writeStd140(identity, 0, 0, out);
assertPlane(out, 0, 1f, 0f, 0f, 1f);
}
@Test
void perspectiveAxisAlignedPoint_insideFrustum() {
void perspectiveAxisAlignedSection_insideFrustum() {
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 16f / 9f, 0.1f, 1000.0f);
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
FrustumExtractor.writeStd140(proj, 0, 0, out);
FrustumExtractor.writeFrustum(proj, IDENTITY, ORIGIN, new Matrix4f(), out);
assertTrue(insideAllPlanes(out, new Vector3f(0f, 0f, -10f)), "point ahead should be inside");
assertTrue(!insideAllPlanes(out, new Vector3f(0f, 0f, 10f)), "point behind should be outside");
assertTrue(shaderInsideFrustum(out, -8, -8, -32), "section ahead should be inside");
assertTrue(!shaderInsideFrustum(out, -8, -8, 16), "section behind should be outside");
}
@Test
void visibleCountStored() {
final Matrix4f mvp = new Matrix4f();
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
FrustumExtractor.writeStd140(mvp, 4242, 0, out);
FrustumExtractor.patchControl(4242, 0, out);
assertEquals(4242, out.getInt(96), "visibleCount at offset 96");
assertEquals(0, out.getInt(100));
assertEquals(0, out.getInt(104));
assertEquals(0, out.getInt(108));
}
private static void assertPlane(ByteBuffer out, int offset, float a, float b, float c, float d) {
final float len = (float) Math.sqrt(a * a + b * b + c * c);
final float inv = len == 0.0f ? 1.0f : 1.0f / len;
assertEquals(a * inv, out.getFloat(offset + 0), EPS, "a @" + offset);
assertEquals(b * inv, out.getFloat(offset + 4), EPS, "b @" + offset);
assertEquals(c * inv, out.getFloat(offset + 8), EPS, "c @" + offset);
assertEquals(d * inv, out.getFloat(offset + 12), EPS, "d @" + offset);
@Test
void matchesCpuFrustumForVanillaCameras() {
final float[] distances = { 0.1f, 4.0f };
final double[][] cameras = { { 8.3, 70.6, 8.7 }, { -1234.4, 65.2, -987.6 }, { 3000000.37, 80.5, -2999999.63 } };
final float[] yaws = { 0f, 37f, 90f, 163f, 271f };
final float[] pitches = { -60f, 0f, 25f, 80f };
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(69.0), 16f / 9f, 0.05f, 512.0f);
final Matrix4f mvFull = new Matrix4f();
final Matrix4f refMvp = new Matrix4f();
final Matrix4f mvp = new Matrix4f();
final FrustumIntersection reference = new FrustumIntersection();
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
final StringBuilder report = new StringBuilder();
int mismatches = 0;
for (final float d : distances) {
for (final double[] cam : cameras) {
final CameraTransform camera = new CameraTransform(cam[0], cam[1], cam[2]);
final int baseX = Math.floorDiv((int) Math.floor(cam[0]), 16);
final int baseY = Math.floorDiv((int) Math.floor(cam[1]), 16);
final int baseZ = Math.floorDiv((int) Math.floor(cam[2]), 16);
int tested = 0;
int wronglyCulled = 0;
int wronglyKept = 0;
for (final float yaw : yaws) {
for (final float pitch : pitches) {
mvFull.identity().translate(0f, 0f, -d).rotateX((float) Math.toRadians(pitch)).rotateY((float) Math.toRadians(yaw + 180.0f)).translate(0f, EYE_HEIGHT, 0f);
reference.set(proj.mul(mvFull, refMvp));
FrustumExtractor.writeFrustum(proj, mvFull, camera, mvp, out);
for (int sx = -SWEEP_SECTIONS; sx <= SWEEP_SECTIONS; sx++) {
for (int sy = -SWEEP_SECTIONS; sy <= SWEEP_SECTIONS; sy++) {
for (int sz = -SWEEP_SECTIONS; sz <= SWEEP_SECTIONS; sz++) {
final int ox = (baseX + sx) * 16;
final int oy = (baseY + sy) * 16;
final int oz = (baseZ + sz) * 16;
final boolean tight = referenceVisible(reference, camera, ox, oy, oz, SECTION_PAD - BOUNDARY_SKIP);
final boolean loose = referenceVisible(reference, camera, ox, oy, oz, SECTION_PAD + BOUNDARY_SKIP);
if (tight != loose) continue;
final boolean expected = tight;
final boolean actual = shaderInsideFrustum(out, ox, oy, oz);
tested++;
if (expected && !actual) wronglyCulled++;
if (!expected && actual) wronglyKept++;
}
}
}
}
}
mismatches += wronglyCulled + wronglyKept;
report.append("\n d=").append(d).append(" camera=(").append(cam[0]).append(", ").append(cam[1]).append(", ").append(cam[2]).append("): tested=").append(tested).append(" wronglyCulled=").append(wronglyCulled).append(" wronglyKept=").append(wronglyKept);
}
}
assertEquals(0, mismatches, "shader frustum test disagrees with the CPU frustum:" + report);
}
private static boolean insideAllPlanes(ByteBuffer out, Vector3f p) {
private static boolean referenceVisible(FrustumIntersection reference, CameraTransform camera, int ox, int oy, int oz, double pad) {
final float minX = (float) (ox - pad - camera.x);
final float minY = (float) (oy - pad - camera.y);
final float minZ = (float) (oz - pad - camera.z);
final float maxX = (float) (ox + 16.0 + pad - camera.x);
final float maxY = (float) (oy + 16.0 + pad - camera.y);
final float maxZ = (float) (oz + 16.0 + pad - camera.z);
return reference.testAab(minX, minY, minZ, maxX, maxY, maxZ);
}
private static boolean shaderInsideFrustum(ByteBuffer out, int ox, int oy, int oz) {
final float x = (float) (ox - out.getInt(112));
final float y = (float) (oy - out.getInt(116));
final float z = (float) (oz - out.getInt(120));
for (int i = 0; i < 6; i++) {
final float a = out.getFloat(i * 16 + 0);
final float b = out.getFloat(i * 16 + 4);
final float c = out.getFloat(i * 16 + 8);
final float d = out.getFloat(i * 16 + 12);
if (a * p.x + b * p.y + c * p.z + d < 0.0f) return false;
final float w = out.getFloat(i * 16 + 12);
if (a * x + b * y + c * z + w < 0.0f) return false;
}
return true;
}
@@ -26,6 +26,7 @@ import org.embeddedt.embeddium.impl.gl.device.IndirectMultiDrawBatch;
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatch;
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatchFactory;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.Matrix4f;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@@ -433,11 +434,10 @@ class IndirectCullDrawParityTest {
}
}
private static ByteBuffer frustumUbo(int indexPointerMask) {
private static ByteBuffer frustumUbo() {
final ByteBuffer ubo = FrustumExtractor.allocateUboByteBuffer();
final Matrix4f identity = new Matrix4f();
FrustumExtractor.writeStd140(identity, 0, indexPointerMask, ubo);
FrustumExtractor.patchCameraWorld(0f, 0f, 0f, ubo);
FrustumExtractor.writeFrustum(identity, identity, new CameraTransform(0.0, 0.0, 0.0), new Matrix4f(), ubo);
FrustumExtractor.patchBypassFrustum(true, ubo);
FrustumExtractor.patchPrimitiveRatio(QuadPrimitiveType.TRIANGULATED.getVerticesPerPrimitive(), QuadPrimitiveType.TRIANGULATED.getIndexBufferElementsPerPrimitive(), ubo);
return ubo;
@@ -528,7 +528,7 @@ class IndirectCullDrawParityTest {
gpu.beginCombinedPasses(0, 0);
assertTrue(gpu.isComputeActiveThisPass(), "compute path unexpectedly inactive");
setFrustumUbo(gpu, frustumUbo(0));
setFrustumUbo(gpu, frustumUbo());
gpu.syncSectionMetaIfDirty();
int outputBase = 0;
@@ -549,7 +549,7 @@ class IndirectCullDrawParityTest {
assertIndirectBufferMatches(result.combinedIndirectBuffer, result.expectedCombined, frameLabel + " combined solid+cutout pass");
gpu.beginCullPass(0xFFFFFFFF);
setFrustumUbo(gpu, frustumUbo(0xFFFFFFFF));
setFrustumUbo(gpu, frustumUbo());
gpu.syncSectionMetaIfDirty();
walkSections(scene.sorted, gpu, region1, 0, result.expectedSorted, 0xFFFFFFFF);
executeRegion(gpu, region1, tessSorted);
@@ -757,12 +757,12 @@ class IndirectCullDrawParityTest {
gpu.beginCullPass(0);
assertTrue(gpu.isComputeActiveThisPass(), "compute path unexpectedly inactive");
setFrustumUbo(gpu, frustumUbo(0));
setFrustumUbo(gpu, frustumUbo());
gpu.syncSectionMetaIfDirty();
final int afterSolid = walkSections(unsorted, gpu, solidRegion, 0, new ArrayList<>(), 0);
gpu.startSortedPass(0xFFFFFFFF);
setFrustumUbo(gpu, frustumUbo(0xFFFFFFFF));
setFrustumUbo(gpu, frustumUbo());
gpu.syncSectionMetaIfDirty();
walkSections(scene.sorted, gpu, sortedRegion, afterSolid, new ArrayList<>(), 0xFFFFFFFF);
gpu.finishSortedPass();