diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java index ff5af61e..ed40f53f 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java @@ -1,6 +1,7 @@ package com.craisinlord.antarchy.content.client; import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape; +import com.craisinlord.antarchy.content.portalgun.PortalGunApertureCuller; import java.util.ArrayDeque; import java.util.Deque; import java.util.UUID; @@ -12,6 +13,7 @@ public final class PortalGunPortalRenderState { private static final ThreadLocal> ACTIVE_DESTINATION_SHAPES = ThreadLocal.withInitial(ArrayDeque::new); private static final ThreadLocal> ACTIVE_SOURCE_SHAPES = ThreadLocal.withInitial(ArrayDeque::new); private static final ThreadLocal> ACTIVE_CONTEXTS = ThreadLocal.withInitial(ArrayDeque::new); + private static final ThreadLocal> ACTIVE_CULLERS = ThreadLocal.withInitial(ArrayDeque::new); private static final ThreadLocal RENDER_ALL_DEPTH = ThreadLocal.withInitial(() -> 0); private PortalGunPortalRenderState() { @@ -27,6 +29,7 @@ public final class PortalGunPortalRenderState { public static void pushPortalView(PortalRenderContext context) { ACTIVE_CONTEXTS.get().push(context); + ACTIVE_CULLERS.get().push(PortalGunApertureCuller.create(context.destinationShape(), context.cameraPos())); pushPortalView(context.sourceShape(), context.destinationShape(), context.renderAll()); } @@ -60,6 +63,13 @@ public final class PortalGunPortalRenderState { if (contexts.isEmpty()) { ACTIVE_CONTEXTS.remove(); } + Deque cullers = ACTIVE_CULLERS.get(); + if (!cullers.isEmpty()) { + cullers.pop(); + } + if (cullers.isEmpty()) { + ACTIVE_CULLERS.remove(); + } } public static void pushDestinationShape(PortalGunWorldPortalShape shape) { @@ -109,7 +119,11 @@ public final class PortalGunPortalRenderState { PortalGunWorldPortalShape sourceShape = getSourceShape(); return destinationShape == null || shouldRenderBounds(bounds, sourceShape, destinationShape, false); } - return shouldRenderBounds(bounds, context.sourceShape(), context.destinationShape(), context.renderAll()); + if (!context.destinationShape().intersectsFront(bounds, 0.125D)) { + return false; + } + Deque cullers = ACTIVE_CULLERS.get(); + return cullers.isEmpty() || cullers.peek().intersects(bounds); } private static boolean shouldRenderBounds(AABB bounds, PortalGunWorldPortalShape sourceShape, PortalGunWorldPortalShape destinationShape, boolean renderAll) { @@ -119,10 +133,7 @@ public final class PortalGunPortalRenderState { if (!destinationShape.intersectsFront(bounds, 0.125D)) { return false; } - if (!destinationShape.intersectsPortalColumn(bounds, 1.5D, 1.5D, destinationShape.halfDepth() + 96.0D)) { - return false; - } - return sourceShape == null || !sourceShape.intersectsFront(bounds, -0.02D); + return true; } public record PortalRenderContext( diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRendererPool.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRendererPool.java index 27d8e801..ece84b9f 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRendererPool.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRendererPool.java @@ -1,5 +1,6 @@ package com.craisinlord.antarchy.content.client.renderer; +import com.craisinlord.antarchy.mixins.client.SectionRenderDispatcherPortalUploadsAccessor; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Iterator; @@ -17,12 +18,15 @@ import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; public final class PortalGunPortalRendererPool { private static final int MAX_RENDERERS = 8; private static final long PORTAL_SECTION_BUDGET_NANOS = 4_166_666L; + private static final long PORTAL_UPLOAD_BUDGET_NANOS = 1_000_000L; + private static final int MAX_PORTAL_UPLOADS_PER_FRAME = 16; private static final RenderBuffers PORTAL_RENDER_BUFFERS = new RenderBuffers(2); private static final LinkedHashMap RENDERERS = new LinkedHashMap<>(8, 0.75F, true); private static final Set PROXY_RENDERERS = java.util.Collections.newSetFromMap(new IdentityHashMap<>()); private static final ThreadLocal SECTION_BUDGET_DEADLINE = ThreadLocal.withInitial(() -> 0L); private static final ThreadLocal SCHEDULED_SECTION_REBUILDS = ThreadLocal.withInitial(() -> 0); private static final ThreadLocal SKIP_SET_NOT_DIRTY = ThreadLocal.withInitial(() -> false); + private static final ThreadLocal UPLOADED_THIS_FRAME = ThreadLocal.withInitial(() -> 0); private static ClientLevel ownerLevel; private PortalGunPortalRendererPool() { @@ -66,12 +70,39 @@ public final class PortalGunPortalRendererPool { SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS); SCHEDULED_SECTION_REBUILDS.set(0); SKIP_SET_NOT_DIRTY.set(false); + UPLOADED_THIS_FRAME.set(0); + } + + public static void uploadPending(LevelRenderer renderer, SectionRenderDispatcher dispatcher) { + if (!isProxy(renderer)) { + dispatcher.uploadAllPendingUploads(); + return; + } + java.util.Queue uploads = ((SectionRenderDispatcherPortalUploadsAccessor) dispatcher).antarchy$getToUpload(); + long deadline = System.nanoTime() + PORTAL_UPLOAD_BUDGET_NANOS; + int uploaded = UPLOADED_THIS_FRAME.get(); + while (uploaded < MAX_PORTAL_UPLOADS_PER_FRAME) { + if (uploaded > 0 && System.nanoTime() >= deadline) { + break; + } + Runnable upload = uploads.poll(); + if (upload == null) { + break; + } + upload.run(); + uploaded++; + } + UPLOADED_THIS_FRAME.set(uploaded); } public static int scheduledSectionRebuilds() { return SCHEDULED_SECTION_REBUILDS.get(); } + public static int uploadedThisFrame() { + return UPLOADED_THIS_FRAME.get(); + } + public static long remainingSectionBudgetNanos() { return Math.max(0L, SECTION_BUDGET_DEADLINE.get() - System.nanoTime()); } @@ -126,6 +157,10 @@ public final class PortalGunPortalRendererPool { } RENDERERS.clear(); ownerLevel = null; + SECTION_BUDGET_DEADLINE.remove(); + SCHEDULED_SECTION_REBUILDS.remove(); + SKIP_SET_NOT_DIRTY.remove(); + UPLOADED_THIS_FRAME.remove(); } private static void trim() { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java index 3c5fd47b..b5c42993 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java @@ -8,6 +8,7 @@ import com.craisinlord.antarchy.mixins.client.MinecraftMainRenderTargetAccessor; import com.craisinlord.antarchy.mixins.client.LevelRendererPortalTargetsAccessor; import com.craisinlord.antarchy.mixins.client.PostChainTargetAccessor; import com.craisinlord.antarchy.content.portalgun.PortalGunTransformUtil; +import com.craisinlord.antarchy.content.portalgun.PortalGunProjectionUtil; import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape; import com.craisinlord.antarchy.mixins.client.CameraBasisAccessor; @@ -129,13 +130,6 @@ public final class PortalGunPortalViewRenderer { if (portals.isEmpty()) { return; } - if (portalViewShader == null) { - if (!loggedMissingPortalViewShader) { - loggedMissingPortalViewShader = true; - Antarchy.LOGGER.error("Portal gun destination view shader is unavailable; skipping destination render"); - } - return; - } Vec3 effectiveCameraPos = cameraPos; Matrix4f rootProjectionMatrix = new Matrix4f(projectionMatrix); float partialTick = tickCounter.getGameTimeDeltaPartialTick(true); @@ -201,6 +195,13 @@ public final class PortalGunPortalViewRenderer { } RenderContext rootContext = new RenderContext(effectiveCameraPos, rootLook, rootUp, rootViewMatrix); boolean directStencil = PortalStencilTarget.supportsDirect(minecraft.getMainRenderTarget()); + if (!directStencil && portalViewShader == null) { + if (!loggedMissingPortalViewShader) { + loggedMissingPortalViewShader = true; + Antarchy.LOGGER.error("Portal gun destination view shader is unavailable; skipping offscreen portal rendering"); + } + return; + } PortalViewBudget fallbackBudget = directStencil ? null : new PortalViewBudget(MAX_PORTAL_VIEWS_PER_FRAME); renderingPortalView = true; boolean projectionBackedUp = false; @@ -212,18 +213,23 @@ public final class PortalGunPortalViewRenderer { if (linkedPortal == null || !linkedPortal.isAlive()) { continue; } + String portalPairKey = portal.getUUID() + "->" + linkedPortal.getUUID(); boolean rendered; try { - if (directStencil) { - rendered = renderPortalSurfaceDirect(minecraft, cameraEntity, tickCounter, rootContext, rootProjectionMatrix, portal, linkedPortal, rootScreenClips.get(portal)); - } else { - int textureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, fallbackBudget, rootContext, - rootProjectionMatrix, portal, linkedPortal, 0, rootScreenClips.get(portal)); - minecraft.gameRenderer.resetProjectionMatrix(rootProjectionMatrix); - minecraft.getMainRenderTarget().bindWrite(true); - rendered = textureId >= 0 && drawPortalQuad(cameraPos, rootViewMatrix, rootProjectionMatrix, - minecraft.getMainRenderTarget().width, minecraft.getMainRenderTarget().height, - portal, partialTick, textureId, rootScreenClips.get(portal)); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, + "root-view-dispatch", "backend=" + (directStencil ? "direct-stencil" : "offscreen") + + " aperture=" + rootScreenClips.get(portal).width() + "x" + rootScreenClips.get(portal).height())) { + if (directStencil) { + rendered = renderPortalSurfaceDirect(minecraft, cameraEntity, tickCounter, rootContext, rootProjectionMatrix, portal, linkedPortal, rootScreenClips.get(portal)); + } else { + int textureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, fallbackBudget, rootContext, + rootProjectionMatrix, portal, linkedPortal, 0, rootScreenClips.get(portal)); + minecraft.gameRenderer.resetProjectionMatrix(rootProjectionMatrix); + minecraft.getMainRenderTarget().bindWrite(true); + rendered = textureId >= 0 && drawPortalQuad(cameraPos, rootViewMatrix, rootProjectionMatrix, + minecraft.getMainRenderTarget().width, minecraft.getMainRenderTarget().height, + portal, partialTick, textureId, rootScreenClips.get(portal)); + } } } catch (RuntimeException exception) { logRenderFailure(minecraft, exception); @@ -241,7 +247,6 @@ public final class PortalGunPortalViewRenderer { RenderSystem.backupProjectionMatrix(); projectionBackedUp = true; minecraft.getMainRenderTarget().bindWrite(true); - String portalPairKey = portal.getUUID() + "->" + linkedPortal.getUUID(); if (loggedPortalViewComposites.add(portalPairKey)) { Antarchy.LOGGER.info("Portal gun destination view rendered directly source={} destination={} target={}x{}", portal.getId(), linkedPortal.getId(), targetWidth, targetHeight); } @@ -358,7 +363,7 @@ public final class PortalGunPortalViewRenderer { ScreenClip clip ) { RenderTarget target = minecraft.getMainRenderTarget(); - if (clip == null || portalViewShader == null) { + if (clip == null) { return false; } int width = target.width; @@ -370,17 +375,18 @@ public final class PortalGunPortalViewRenderer { PortalCamera portalCamera = new PortalCamera(); portalCamera.setup(minecraft.level, cameraEntity, partialTick, transformedContext.cameraPos(), transformedContext.look(), transformedContext.up()); String portalPairKey = sourcePortal.getUUID() + "->" + destinationPortal.getUUID(); - LevelRenderer proxyRenderer = PortalGunPortalRendererPool.acquire(minecraft, destinationPortal.getUUID(), width, height); - LevelRendererPortalTargetsAccessor levelRendererTargets = (LevelRendererPortalTargetsAccessor) proxyRenderer; - PostChain entityEffect = levelRendererTargets.antarchy$getEntityEffect(); - PostChain transparencyChain = levelRendererTargets.antarchy$getTransparencyChain(); + boolean renderDestination = PortalGunPortalVisibilityQueries.shouldRender(sourcePortal.getUUID(), destinationPortal.getUUID()); + LevelRenderer proxyRenderer = renderDestination + ? PortalGunPortalRendererPool.acquire(minecraft, destinationPortal.getUUID(), width, height) : null; + LevelRendererPortalTargetsAccessor levelRendererTargets = proxyRenderer == null ? null : (LevelRendererPortalTargetsAccessor) proxyRenderer; + PostChain entityEffect = levelRendererTargets == null ? null : levelRendererTargets.antarchy$getEntityEffect(); + PostChain transparencyChain = levelRendererTargets == null ? null : levelRendererTargets.antarchy$getTransparencyChain(); PostChainTargetAccessor entityEffectTarget = entityEffect == null ? null : (PostChainTargetAccessor) entityEffect; PostChainTargetAccessor transparencyTarget = transparencyChain == null ? null : (PostChainTargetAccessor) transparencyChain; RenderTarget previousEntityEffectTarget = entityEffectTarget == null ? null : entityEffectTarget.antarchy$getScreenTarget(); RenderTarget previousTransparencyTarget = transparencyTarget == null ? null : transparencyTarget.antarchy$getScreenTarget(); PortalStencilTarget.Scope stencilScope = null; boolean contextPushed = false; - boolean renderDestination = PortalGunPortalVisibilityQueries.shouldRender(sourcePortal.getUUID(), destinationPortal.getUUID()); PortalSceneRenderTrace.event(portalPairKey, "destination-render-decision", "render=" + renderDestination + " clip=" + clip.width() + "x" + clip.height()); try { if (entityEffectTarget != null) { @@ -390,12 +396,12 @@ public final class PortalGunPortalViewRenderer { transparencyTarget.antarchy$setScreenTarget(target); } minecraft.gameRenderer.resetProjectionMatrix(clippedProjection); - int maskTexture = minecraft.getTextureManager().getTexture(BLUE_OVERLAY).getId(); try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-stencil-mask", "clip=" + clip.width() + "x" + clip.height())) { stencilScope = PortalStencilTarget.beginDirect(target, () -> { boolean queryStarted = PortalGunPortalVisibilityQueries.begin(sourcePortal.getUUID(), destinationPortal.getUUID()); try { - drawPortalQuad(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, width, height, sourcePortal, partialTick, maskTexture, clip); + drawPortalMask(minecraft, currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, + sourcePortal, partialTick); } finally { PortalGunPortalVisibilityQueries.end(sourcePortal.getUUID(), destinationPortal.getUUID(), queryStarted); } @@ -430,22 +436,37 @@ public final class PortalGunPortalViewRenderer { return true; } finally { try { - if (stencilScope != null) { - stencilScope.close(); - } - if (contextPushed) { - PortalGunPortalRenderState.popPortalView(false); + try { + if (stencilScope != null) { + stencilScope.close(); + } + } finally { + if (contextPushed) { + PortalGunPortalRenderState.popPortalView(false); + } } } finally { - if (transparencyTarget != null) { - transparencyTarget.antarchy$setScreenTarget(previousTransparencyTarget); + try { + if (transparencyTarget != null) { + transparencyTarget.antarchy$setScreenTarget(previousTransparencyTarget); + } + } finally { + try { + if (entityEffectTarget != null) { + entityEffectTarget.antarchy$setScreenTarget(previousEntityEffectTarget); + } + } finally { + try { + restorePortalDrawState(); + } finally { + try { + target.bindWrite(true); + } finally { + minecraft.gameRenderer.resetProjectionMatrix(baseProjectionMatrix); + } + } + } } - if (entityEffectTarget != null) { - entityEffectTarget.antarchy$setScreenTarget(previousEntityEffectTarget); - } - restorePortalDrawState(); - target.bindWrite(true); - minecraft.gameRenderer.resetProjectionMatrix(baseProjectionMatrix); } } } @@ -482,7 +503,9 @@ public final class PortalGunPortalViewRenderer { } Matrix4f clippedProjectionMatrix = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal); Matrix4f apertureCullProjection = cropProjectionToScreenClip(clippedProjectionMatrix, clip, target.width, target.height); - boolean renderAll = true; + // The offscreen target is still composited only through this portal's aperture, + // so use the same conservative destination bounds filter as the direct path. + boolean renderAll = false; PortalCamera portalCamera = new PortalCamera(); portalCamera.setup(minecraft.level, cameraEntity, tickCounter.getGameTimeDeltaPartialTick(true), transformedContext.cameraPos(), transformedContext.look(), transformedContext.up()); RenderTarget previousMainTarget = minecraft.getMainRenderTarget(); @@ -603,11 +626,14 @@ public final class PortalGunPortalViewRenderer { } finally { try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "restore-render-state", "depth=" + depth)) { try { - if (stencilScope != null) { - stencilScope.close(); - } - if (portalRenderStatePushed) { - PortalGunPortalRenderState.popPortalView(renderAll); + try { + if (stencilScope != null) { + stencilScope.close(); + } + } finally { + if (portalRenderStatePushed) { + PortalGunPortalRenderState.popPortalView(renderAll); + } } } finally { try { @@ -635,11 +661,13 @@ public final class PortalGunPortalViewRenderer { } private static RenderContext transformContext(RenderContext currentContext, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) { - Vec3 relativeEye = currentContext.cameraPos().subtract(sourcePortal.position()); - Vec3 transformedEyeOffset = PortalGunTransformUtil.transformRelativePosition(sourcePortal, destinationPortal, relativeEye); - Vec3 destinationEye = destinationPortal.position().add(transformedEyeOffset); - Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, currentContext.look()).normalize(); - Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, currentContext.up()).normalize(); + var sourceShape = sourcePortal.getWorldPortalShape(); + var destinationShape = destinationPortal.getWorldPortalShape(); + Vec3 relativeEye = currentContext.cameraPos().subtract(sourceShape.center()); + Vec3 transformedEyeOffset = PortalGunTransformUtil.transformPosition(sourceShape, destinationShape, relativeEye); + Vec3 destinationEye = destinationShape.center().add(transformedEyeOffset); + Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourceShape, destinationShape, currentContext.look()).normalize(); + Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourceShape, destinationShape, currentContext.up()).normalize(); Matrix4f viewMatrix = new Matrix4f().rotation(PortalGunTransformUtil.orientationQuaternion(transformedLook, transformedUp).conjugate(new Quaternionf())); return new RenderContext(destinationEye, transformedLook, transformedUp, viewMatrix); } @@ -647,51 +675,8 @@ public final class PortalGunPortalViewRenderer { private static Matrix4f applyPortalClipPlane(Matrix4f projectionMatrix, RenderContext renderContext, PortalGunPortalEntity destinationPortal) { Vec3 planePointWorld = destinationPortal.position().add(destinationPortal.getNormalVec().normalize().scale(0.01D)); Vec3 planeNormalWorld = destinationPortal.getNormalVec().normalize(); - Vector4f clipPlane = portalPlaneToCameraSpace(renderContext, planePointWorld, planeNormalWorld); - if (clipPlane.lengthSquared() <= 1.0E-6F) { - return projectionMatrix; - } - Matrix4f clippedProjection = new Matrix4f(projectionMatrix); - Vector4f q = new Vector4f(signNonZero(clipPlane.x()), signNonZero(clipPlane.y()), 1.0F, 1.0F) - .mul(new Matrix4f(projectionMatrix).invert()); - float planeCornerDot = clipPlane.dot(q); - if (!Float.isFinite(planeCornerDot) || Math.abs(planeCornerDot) <= 1.0E-6F) { - return projectionMatrix; - } - float scale = 2.0F / planeCornerDot; - Vector4f c = clipPlane.mul(scale, new Vector4f()); - clippedProjection.m02(c.x()); - clippedProjection.m12(c.y()); - clippedProjection.m22(c.z() + 1.0F); - clippedProjection.m32(c.w()); - return clippedProjection; - } - - private static Vector4f portalPlaneToCameraSpace(RenderContext renderContext, Vec3 planePointWorld, Vec3 planeNormalWorld) { - Vec3 look = renderContext.look().normalize(); - Vec3 up = renderContext.up().normalize(); - Vec3 right = look.cross(up).normalize(); - Vec3 relativePoint = planePointWorld.subtract(renderContext.cameraPos()); - Vector4f point = new Vector4f( - (float) relativePoint.dot(right), - (float) relativePoint.dot(up), - (float) -relativePoint.dot(look), - 1.0F - ); - Vector4f normal = new Vector4f( - (float) planeNormalWorld.dot(right), - (float) planeNormalWorld.dot(up), - (float) -planeNormalWorld.dot(look), - 0.0F - ); - if (normal.z() > 0.0F) { - normal.mul(-1.0F); - } - return new Vector4f(normal.x(), normal.y(), normal.z(), -(normal.x() * point.x() + normal.y() * point.y() + normal.z() * point.z())); - } - - private static float signNonZero(float value) { - return value >= 0.0F ? 1.0F : -1.0F; + return PortalGunProjectionUtil.clipAtExit(projectionMatrix, renderContext.cameraPos(), renderContext.look(), + renderContext.up(), planePointWorld, planeNormalWorld); } private static ScreenClip computeScreenClip(Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix, PortalGunPortalEntity portal, float partialTick, int width, int height) { @@ -836,6 +821,24 @@ public final class PortalGunPortalViewRenderer { return true; } + private static void drawPortalMask(Minecraft minecraft, Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix, + PortalGunPortalEntity portal, float partialTick) { + PortalGunWorldPortalShape shape = portal.getWorldPortalShape() + .scaleAperture(portal.getPortalVisualScale(partialTick) * PORTAL_APERTURE_SCALE); + Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix); + List polygon = projectPortalPolygon(combined, cameraPos, shape); + if (polygon.size() < 3) { + return; + } + BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION); + for (int index = 1; index + 1 < polygon.size(); index++) { + addDepthVertex(buffer, polygon.get(0)); + addDepthVertex(buffer, polygon.get(index)); + addDepthVertex(buffer, polygon.get(index + 1)); + } + drawClipSpaceMesh(minecraft, buffer.build()); + } + private static void clearDepthInPortalStencil(Minecraft minecraft) { int previousDepthFunction = GL11.glGetInteger(GL11.GL_DEPTH_FUNC); RenderSystem.enableDepthTest(); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java index eaf1795b..75e2b9b1 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java @@ -1,5 +1,6 @@ package com.craisinlord.antarchy.content.client.renderer; +import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState; import com.craisinlord.antarchy.mixins.client.LevelRendererPortalSceneInvoker; import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor; import com.mojang.blaze3d.systems.RenderSystem; @@ -39,6 +40,7 @@ public final class PortalGunProxySceneRenderer { outerModelViewStack.pushMatrix(); outerModelViewStack.identity(); RenderSystem.applyModelViewMatrix(); + ObjectArrayList originalVisibleSections = null; try { Vec3 cameraPosition = camera.getPosition(); double cameraX = cameraPosition.x; @@ -71,8 +73,20 @@ public final class PortalGunProxySceneRenderer { } LevelRendererPortalViewAreaAccessor viewAreaAccessor = sodium ? null : (LevelRendererPortalViewAreaAccessor) renderer; int visibleSectionCount = 0; + int candidateSectionCount = 0; if (viewAreaAccessor != null) { - ObjectArrayList fullVisibleSections = viewAreaAccessor.antarchy$getVisibleSections(); + originalVisibleSections = viewAreaAccessor.antarchy$getVisibleSections(); + candidateSectionCount = originalVisibleSections.size(); + ObjectArrayList apertureSections = new ObjectArrayList<>(); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "aperture-section-cull", "candidateSections=" + candidateSectionCount)) { + for (SectionRenderDispatcher.RenderSection section : originalVisibleSections) { + if (PortalGunPortalRenderState.shouldRenderBounds(section.getBoundingBox())) { + apertureSections.add(section); + } + } + } + viewAreaAccessor.antarchy$setVisibleSections(apertureSections); + ObjectArrayList fullVisibleSections = apertureSections; ObjectArrayList budgetedVisibleSections = new ObjectArrayList<>(); int scannedVisibleSections = 0; while (scannedVisibleSections < fullVisibleSections.size() && PortalGunPortalRendererPool.remainingSectionBudgetNanos() > 0L) { @@ -88,7 +102,11 @@ public final class PortalGunProxySceneRenderer { } visibleSectionCount = fullVisibleSections.size(); } - PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "renderer=" + (sodium ? "sodium" : "vanilla") + " count=" + visibleSectionCount + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds()); + PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "renderer=" + (sodium ? "sodium" : "vanilla") + + " count=" + visibleSectionCount + " candidates=" + candidateSectionCount + + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds() + + " uploaded=" + PortalGunPortalRendererPool.uploadedThisFrame() + + " pendingUploads=" + (sodium ? "sodium-managed" : viewAreaAccessor.antarchy$getSectionRenderDispatcher().getToUpload())); try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "opaque-terrain-layers", "sections=" + visibleSectionCount)) { RenderSystem.enableDepthTest(); @@ -195,6 +213,9 @@ public final class PortalGunProxySceneRenderer { } } } finally { + if (originalVisibleSections != null) { + ((LevelRendererPortalViewAreaAccessor) renderer).antarchy$setVisibleSections(originalVisibleSections); + } outerModelViewStack.popMatrix(); RenderSystem.applyModelViewMatrix(); RenderSystem.setShaderFogStart(previousFogStart); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java index c5fa632c..fff15039 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java @@ -6,8 +6,8 @@ import java.util.Set; public final class PortalSceneRenderTrace { private static final String DEBUG_PROPERTY = "antarchy.portalGun.debugRender"; + private static final long SLOW_PHASE_NANOS = 100_000_000L; private static final Set LOGGED_PHASES = new HashSet<>(); - private static final Phase NOOP_PHASE = new Phase(0L, "", "", false, 0L); private static long generation; private static String armedKey = ""; @@ -29,19 +29,17 @@ public final class PortalSceneRenderTrace { } public static Phase begin(String pair, String phase, String detail) { - if (!Boolean.getBoolean(DEBUG_PROPERTY)) { - return NOOP_PHASE; - } + boolean verbose = Boolean.getBoolean(DEBUG_PROPERTY); long currentGeneration; - boolean shouldLog; + boolean logStart; synchronized (PortalSceneRenderTrace.class) { currentGeneration = generation; - shouldLog = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":" + phase); + logStart = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":" + phase); } - if (shouldLog) { + if (logStart) { Antarchy.LOGGER.info("Portal scene trace begin generation={} pair={} phase={} detail={}", currentGeneration, pair, phase, detail); } - return new Phase(currentGeneration, pair, phase, shouldLog, System.nanoTime()); + return new Phase(currentGeneration, pair, phase, logStart, verbose, System.nanoTime()); } public static void event(String pair, String event, String detail) { @@ -63,15 +61,17 @@ public final class PortalSceneRenderTrace { private final long generation; private final String pair; private final String phase; - private final boolean logCompletion; + private final boolean logStart; + private final boolean verbose; private final long startNanos; private boolean closed; - private Phase(long generation, String pair, String phase, boolean logCompletion, long startNanos) { + private Phase(long generation, String pair, String phase, boolean logStart, boolean verbose, long startNanos) { this.generation = generation; this.pair = pair; this.phase = phase; - this.logCompletion = logCompletion; + this.logStart = logStart; + this.verbose = verbose; this.startNanos = startNanos; } @@ -81,9 +81,10 @@ public final class PortalSceneRenderTrace { return; } this.closed = true; - if (this.logCompletion) { + long durationNanos = System.nanoTime() - this.startNanos; + if (this.logStart && (this.verbose || durationNanos >= SLOW_PHASE_NANOS)) { Antarchy.LOGGER.info("Portal scene trace complete generation={} pair={} phase={} durationMs={}", - this.generation, this.pair, this.phase, (System.nanoTime() - this.startNanos) / 1_000_000.0D); + this.generation, this.pair, this.phase, durationNanos / 1_000_000.0D); } } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunApertureCuller.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunApertureCuller.java new file mode 100644 index 00000000..8143b7ce --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunApertureCuller.java @@ -0,0 +1,63 @@ +package com.craisinlord.antarchy.content.portalgun; + +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +/** + * Conservative side planes through the destination opening and virtual eye. + * The exit plane itself is handled separately so bounds crossing it remain visible. + */ +public final class PortalGunApertureCuller { + private static final double MIN_EYE_DISTANCE = 0.05D; + private static final double PLANE_PADDING = 0.5D; + + private final Vec3 eye; + private final Vec3[] inwardNormals; + + private PortalGunApertureCuller(Vec3 eye, Vec3[] inwardNormals) { + this.eye = eye; + this.inwardNormals = inwardNormals; + } + + public static PortalGunApertureCuller create(PortalGunWorldPortalShape aperture, Vec3 eye) { + double eyeDepth = aperture.localCoords(eye).depth(); + if (eyeDepth >= -MIN_EYE_DISTANCE) { + return new PortalGunApertureCuller(eye, null); + } + Vec3[] corners = aperture.getCorners(0.0D); + Vec3 centerRay = aperture.center().subtract(eye); + Vec3[] normals = new Vec3[corners.length]; + for (int index = 0; index < corners.length; index++) { + Vec3 first = corners[index].subtract(eye); + Vec3 second = corners[(index + 1) % corners.length].subtract(eye); + Vec3 normal = first.cross(second); + if (normal.lengthSqr() < 1.0E-8D) { + return new PortalGunApertureCuller(eye, null); + } + normal = normal.normalize(); + if (normal.dot(centerRay) < 0.0D) { + normal = normal.scale(-1.0D); + } + normals[index] = normal; + } + return new PortalGunApertureCuller(eye, normals); + } + + public boolean intersects(AABB bounds) { + if (inwardNormals == null) { + return true; + } + for (Vec3 normal : inwardNormals) { + double supportX = normal.x >= 0.0D ? bounds.maxX : bounds.minX; + double supportY = normal.y >= 0.0D ? bounds.maxY : bounds.minY; + double supportZ = normal.z >= 0.0D ? bounds.maxZ : bounds.minZ; + double maxDistance = (supportX - eye.x) * normal.x + + (supportY - eye.y) * normal.y + + (supportZ - eye.z) * normal.z; + if (maxDistance < -PLANE_PADDING) { + return false; + } + } + return true; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectionUtil.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectionUtil.java new file mode 100644 index 00000000..c02d4a21 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectionUtil.java @@ -0,0 +1,60 @@ +package com.craisinlord.antarchy.content.portalgun; + +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Vector4f; + +/** Applies the destination portal plane as the near plane of a camera projection. */ +public final class PortalGunProjectionUtil { + private PortalGunProjectionUtil() { + } + + public static Matrix4f clipAtExit(Matrix4f projection, Vec3 eye, Vec3 look, Vec3 up, + Vec3 planePoint, Vec3 planeNormal) { + Vector4f clipPlane = planeInCameraSpace(eye, look, up, planePoint, planeNormal); + if (clipPlane.lengthSquared() <= 1.0E-6F) { + return projection; + } + Vector4f q = new Vector4f(signNonZero(clipPlane.x()), signNonZero(clipPlane.y()), 1.0F, 1.0F) + .mul(new Matrix4f(projection).invert()); + float planeCornerDot = clipPlane.dot(q); + if (!Float.isFinite(planeCornerDot) || Math.abs(planeCornerDot) <= 1.0E-6F) { + return projection; + } + float scale = 2.0F / planeCornerDot; + Vector4f c = clipPlane.mul(scale, new Vector4f()); + Matrix4f clipped = new Matrix4f(projection); + clipped.m02(c.x()); + clipped.m12(c.y()); + clipped.m22(c.z() + 1.0F); + clipped.m32(c.w()); + return clipped; + } + + private static Vector4f planeInCameraSpace(Vec3 eye, Vec3 look, Vec3 up, Vec3 planePoint, Vec3 planeNormal) { + Vec3 forward = look.normalize(); + Vec3 correctedUp = up.subtract(forward.scale(up.dot(forward))).normalize(); + Vec3 right = forward.cross(correctedUp).normalize(); + Vec3 relativePoint = planePoint.subtract(eye); + Vector4f point = new Vector4f( + (float) relativePoint.dot(right), + (float) relativePoint.dot(correctedUp), + (float) -relativePoint.dot(forward), + 1.0F + ); + Vector4f normal = new Vector4f( + (float) planeNormal.dot(right), + (float) planeNormal.dot(correctedUp), + (float) -planeNormal.dot(forward), + 0.0F + ); + if (normal.z() > 0.0F) { + normal.mul(-1.0F); + } + return new Vector4f(normal.x(), normal.y(), normal.z(), -normal.dot(point)); + } + + private static float signNonZero(float value) { + return value >= 0.0F ? 1.0F : -1.0F; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java index f6f69546..67cfe2d7 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java @@ -65,6 +65,16 @@ public final class PortalGunTransformUtil { .add(destinationOut.scale(vector.dot(sourceInto))); } + public static Vec3 transformPosition(PortalGunWorldPortalShape source, PortalGunWorldPortalShape destination, Vec3 relativePosition) { + return transformVector(source, destination, relativePosition); + } + + public static Vec3 transformVector(PortalGunWorldPortalShape source, PortalGunWorldPortalShape destination, Vec3 vector) { + return destination.right().scale(-vector.dot(source.right())) + .add(destination.up().scale(vector.dot(source.up()))) + .add(destination.normal().scale(-vector.dot(source.normal()))); + } + public static float yawFromLook(Vec3 look) { return (float) Math.toDegrees(Math.atan2(-look.x, look.z)); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java index 34ae51ba..0b376fff 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java @@ -33,6 +33,7 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin { && (mixinClassName.endsWith(".LevelRendererPortalViewAreaAccessor") || mixinClassName.endsWith(".LevelRendererPortalViewAreaMixin") || mixinClassName.endsWith(".LevelRendererPortalChunkBudgetMixin") + || mixinClassName.endsWith(".SectionRenderDispatcherPortalUploadsAccessor") || mixinClassName.endsWith(".PortalViewChunkCacheMixin") || mixinClassName.endsWith(".PortalViewSectionSortingMixin"))) { return false; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java index 55218df1..6edb0d19 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java @@ -12,6 +12,14 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(LevelRenderer.class) public abstract class LevelRendererPortalChunkBudgetMixin { + @Redirect( + method = "compileSections", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;uploadAllPendingUploads()V") + ) + private void antarchy$budgetPortalUploads(SectionRenderDispatcher dispatcher) { + PortalGunPortalRendererPool.uploadPending((LevelRenderer) (Object) this, dispatcher); + } + @Redirect( method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;pollLightUpdates()V") diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java index 9edbabed..1337ede0 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java @@ -16,6 +16,9 @@ import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(LevelRenderer.class) public interface LevelRendererPortalViewAreaAccessor { + @Accessor("sectionRenderDispatcher") + SectionRenderDispatcher antarchy$getSectionRenderDispatcher(); + @Accessor("viewArea") ViewArea antarchy$getViewArea(); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SectionRenderDispatcherPortalUploadsAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SectionRenderDispatcherPortalUploadsAccessor.java new file mode 100644 index 00000000..3b919847 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SectionRenderDispatcherPortalUploadsAccessor.java @@ -0,0 +1,12 @@ +package com.craisinlord.antarchy.mixins.client; + +import java.util.Queue; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SectionRenderDispatcher.class) +public interface SectionRenderDispatcherPortalUploadsAccessor { + @Accessor("toUpload") + Queue antarchy$getToUpload(); +} diff --git a/common-1.21.1/src/main/resources/antarchy-common.mixins.json b/common-1.21.1/src/main/resources/antarchy-common.mixins.json index fe53214d..34db77ee 100644 --- a/common-1.21.1/src/main/resources/antarchy-common.mixins.json +++ b/common-1.21.1/src/main/resources/antarchy-common.mixins.json @@ -143,6 +143,7 @@ "client.LevelRendererPortalViewAreaAccessor", "client.LevelRendererPortalViewAreaMixin", "client.LevelRendererPortalChunkBudgetMixin", + "client.SectionRenderDispatcherPortalUploadsAccessor", "client.LevelRendererPortalLifecycleMixin", "client.PortalViewChunkCacheMixin", "client.PortalViewSectionSortingMixin",