fix #451: stabilize portal seam rendering
All checks were successful
CI / linux-portable (push) Successful in 3m32s
CI / windows-gate (push) Successful in 6m55s
CI / release (push) Successful in 2m12s

This commit is contained in:
Erik 2026-08-27 14:30:21 +02:00
parent f6fe0f2a4f
commit 1d2f2f738f
29 changed files with 1650 additions and 239 deletions

View file

@ -23,6 +23,7 @@ public sealed class RetailPViewPassExecutorTests
[
"begin",
"assemble",
"append-look-in-clips",
"prepare-clip:3",
"indoor-routing",
"prepare-cells",
@ -33,6 +34,7 @@ public sealed class RetailPViewPassExecutorTests
"terrain-clip",
"clear-routing",
"landscape-late",
"unattached-particles",
"landscape-alpha",
"indoor-routing",
"indoor-routing",
@ -272,6 +274,88 @@ public sealed class RetailPViewPassExecutorTests
executor);
Assert.Contains("look-in-punch", executor.Operations);
AssertAppearsInOrder(
string.Join('|', executor.Operations),
"landscape-early",
"unattached-particles",
"landscape-static-particles",
"landscape-alpha",
"look-in-punch",
"landscape-late",
"landscape-alpha",
"interior-depth-clear");
}
[Fact]
public void DrawInside_repaints_the_exterior_building_shell_after_its_look_in()
{
var renderer = new RetailPViewRenderer();
using var executor = new RecordingExecutor();
LoadedCell root = InteriorWithExit(0xA9B40100u);
root.BuildingId = 1u;
LoadedCell[] building = NearbyTwoCellBuilding();
WorldEntity exteriorShell = Entity(
0x700u,
isBuildingShell: true,
buildingShellAnchorCellId: building[0].CellId);
renderer.DrawInside(
Frame(
root,
[exteriorShell],
nearbyBuildingCells: building,
additionalCells: building),
executor);
AssertAppearsInOrder(
string.Join('|', executor.Operations),
"landscape-early",
"look-in-punch",
"landscape-building-shell",
"landscape-late");
}
[Fact]
public void DrawInside_pairs_each_look_in_with_only_its_own_shell_and_alpha_barrier()
{
var renderer = new RetailPViewRenderer();
using var executor = new RecordingExecutor();
LoadedCell root = InteriorWithExit(0xA9B40100u);
root.BuildingId = 1u;
LoadedCell[] first = NearbyTwoCellBuilding();
LoadedCell[] second = NearbyTwoCellBuilding(
0xA9B40172u,
0xA9B40173u,
buildingId: 3u);
LoadedCell[] buildings = [.. first, .. second];
WorldEntity[] shells =
[
Entity(
0x700u,
isBuildingShell: true,
buildingShellAnchorCellId: first[0].CellId),
Entity(
0x701u,
isBuildingShell: true,
buildingShellAnchorCellId: second[0].CellId),
];
renderer.DrawInside(
Frame(
root,
shells,
nearbyBuildingCells: buildings,
additionalCells: buildings),
executor);
AssertAppearsInOrder(
string.Join('|', executor.Operations),
"look-in-punch",
"landscape-building-shell",
"landscape-static-particles",
"landscape-alpha",
"look-in-punch",
"landscape-building-shell");
}
[Fact]
@ -429,20 +513,21 @@ public sealed class RetailPViewPassExecutorTests
return cell;
}
private static LoadedCell[] NearbyTwoCellBuilding()
private static LoadedCell[] NearbyTwoCellBuilding(
uint vestibuleId = 0xA9B40170u,
uint roomId = 0xA9B40171u,
uint buildingId = 2u)
{
const uint vestibuleId = 0xA9B40170u;
const uint roomId = 0xA9B40171u;
var vestibule = new LoadedCell
{
CellId = vestibuleId,
BuildingId = 2u,
BuildingId = buildingId,
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Portals =
[
new CellPortalInfo(0xFFFF, 0, 0, 0),
new CellPortalInfo(0x0171, 1, 0, 0),
new CellPortalInfo((ushort)(roomId & 0xFFFFu), 1, 0, 0),
],
ClipPlanes =
[
@ -472,10 +557,17 @@ public sealed class RetailPViewPassExecutorTests
var room = new LoadedCell
{
CellId = roomId,
BuildingId = 2u,
BuildingId = buildingId,
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Portals = [new CellPortalInfo(0x0170, 0, 0, 1)],
Portals =
[
new CellPortalInfo(
(ushort)(vestibuleId & 0xFFFFu),
0,
0,
1),
],
};
room.PortalPolygons.Add(
[
@ -490,7 +582,9 @@ public sealed class RetailPViewPassExecutorTests
private static WorldEntity Entity(
uint id,
uint serverGuid = 0,
uint? parentCellId = null) => new()
uint? parentCellId = null,
bool isBuildingShell = false,
uint? buildingShellAnchorCellId = null) => new()
{
Id = id,
ServerGuid = serverGuid,
@ -499,6 +593,8 @@ public sealed class RetailPViewPassExecutorTests
Rotation = Quaternion.Identity,
MeshRefs = [new MeshRef(1u, Matrix4x4.Identity)],
ParentCellId = parentCellId,
IsBuildingShell = isBuildingShell,
BuildingShellAnchorCellId = buildingShellAnchorCellId,
};
private static void AssertAppearsInOrder(string source, params string[] needles)
@ -566,6 +662,17 @@ public sealed class RetailPViewPassExecutorTests
return ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
}
public void AppendLookInClipFrames(
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
ClipFrameAssembly assembly)
{
Operations.Add("append-look-in-clips");
ClipFrameAssembler.AppendLookInFrames(
_clipFrame,
lookInFrames,
assembly);
}
public void PrepareClipFrame(int terrainUploadCount) =>
Operations.Add($"prepare-clip:{terrainUploadCount}");
@ -574,6 +681,8 @@ public sealed class RetailPViewPassExecutorTests
public void ClearClipRouting() => Operations.Add("clear-routing");
public void UseIndoorMembershipOnlyRouting() => Operations.Add("indoor-routing");
public void UseCellPortalViewRouting(uint cellId, ClipViewSlice slice) =>
Operations.Add($"cell-portal-routing:{cellId:X8}:{slice.Slot}");
public void PrepareCellBatches(RetailPViewFrameInput frame, HashSet<uint> visibleCellIds) =>
Operations.Add("prepare-cells");
public void DrawOpaqueCellShells(HashSet<uint> cellIds) => Operations.Add("opaque-shells");
@ -624,10 +733,37 @@ public sealed class RetailPViewPassExecutorTests
request.TupleLandblockId);
}
}
public void DrawLandscapeStaticParticles(
RetailPViewFrameInput frame,
RetailPViewLandscapeStaticParticleContext context) =>
Operations.Add("landscape-static-particles");
public void DrawLandscapeBuildingShellSlice(
RetailPViewFrameInput frame,
RetailPViewLandscapeBuildingShellSliceContext context)
{
Operations.Add("landscape-building-shell");
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
{
RenderFrameView view = request.View;
DrawEntityRoute(
frame.Camera,
in view,
request.Route,
request.RouteIndex,
request.CellId,
request.TupleLandblockId);
}
}
public void ClearInteriorDepth() => Operations.Add("interior-depth-clear");
public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask");
public void DrawLookInPortalPunch(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("look-in-punch");
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame) => Operations.Add("unattached-particles");
public void DrawLookInPortalPunch(
RetailPViewFrameInput frame,
RetailPViewCellSliceContext context,
int portalIndex) => Operations.Add("look-in-punch");
public void DrawUnattachedSceneParticles(
RetailPViewFrameInput frame,
ClipViewSlice slice) => Operations.Add("unattached-particles");
public void FlushLandscapeAlpha() => Operations.Add("landscape-alpha");
public void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("cell-particles");
public void DrawDynamicsParticles(