fix #451: stabilize portal seam rendering
This commit is contained in:
parent
f6fe0f2a4f
commit
1d2f2f738f
29 changed files with 1650 additions and 239 deletions
|
|
@ -21,6 +21,10 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
RenderProjectionRecord outdoor = Record(
|
||||
0x0100_0000_0000_0001,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord buildingShell = Record(
|
||||
0x0100_0000_0000_0008,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true);
|
||||
RenderProjectionRecord hidden = Record(
|
||||
0x0100_0000_0000_0002,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
|
|
@ -48,6 +52,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
RenderProjectionRecord[] records =
|
||||
[
|
||||
outdoor,
|
||||
buildingShell,
|
||||
hidden,
|
||||
withdrawn,
|
||||
shell,
|
||||
|
|
@ -90,7 +95,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[outdoor.Id, hidden.Id],
|
||||
[outdoor.Id, hidden.Id, buildingShell.Id],
|
||||
view.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static item => item.Id));
|
||||
Assert.Equal(
|
||||
|
|
@ -101,28 +106,42 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[outdoorDynamic.Id, cellDynamic.Id],
|
||||
view.DynamicCandidates.ToArray()
|
||||
.Select(static item => item.Id));
|
||||
Assert.Equal(5, view.Transforms.Length);
|
||||
Assert.Equal(5, view.RouteCandidates.Length);
|
||||
Assert.Equal(6, view.Transforms.Length);
|
||||
Assert.Equal(6, view.RouteCandidates.Length);
|
||||
Assert.Equal(3, view.RouteRanges.Length);
|
||||
RenderFrameCandidateRange outdoorRange = Assert.Single(
|
||||
view.RouteRanges.ToArray(),
|
||||
range => range.Route
|
||||
== RenderFrameCandidateRoute.LandscapeOutdoorStatic);
|
||||
Assert.Equal(3, outdoorRange.Count);
|
||||
Assert.Contains(
|
||||
view.RouteCandidates.Slice(
|
||||
outdoorRange.Offset,
|
||||
outdoorRange.Count).ToArray(),
|
||||
item => item.Id == buildingShell.Id);
|
||||
Assert.DoesNotContain(
|
||||
view.RouteRanges.ToArray(),
|
||||
range => range.Route
|
||||
== RenderFrameCandidateRoute.LandscapeBuildingShell);
|
||||
Assert.DoesNotContain(
|
||||
view.RouteCandidates.ToArray(),
|
||||
item => item.Id == withdrawn.Id || item.Id == shell.Id);
|
||||
Assert.Equal(
|
||||
new RenderFrameDiagnosticCounts(
|
||||
OutdoorStaticCandidates: 2,
|
||||
OutdoorStaticCandidates: 3,
|
||||
CellStaticCandidates: 1,
|
||||
DynamicCandidates: 2,
|
||||
TransformCount: 5,
|
||||
TransformCount: 6,
|
||||
OpaqueClassificationCount: 0,
|
||||
AlphaClassificationCount: 0,
|
||||
LightSetCount: 0,
|
||||
SelectionPartCount: 0,
|
||||
RouteCandidateCount: 5,
|
||||
EntityCandidateCount: 5,
|
||||
MeshPartCount: 5),
|
||||
RouteCandidateCount: 6,
|
||||
EntityCandidateCount: 6,
|
||||
MeshPartCount: 6),
|
||||
view.DiagnosticCounts);
|
||||
Assert.Equal(5, view.EntityCandidates.Length);
|
||||
Assert.Equal(5, view.MeshParts.Length);
|
||||
Assert.Equal(6, view.EntityCandidates.Length);
|
||||
Assert.Equal(6, view.MeshParts.Length);
|
||||
Assert.Same(portal, view.PortalFrame);
|
||||
Assert.Same(clip, view.ClipAssembly);
|
||||
Assert.Equal(digest, view.SourceDigest);
|
||||
|
|
@ -133,6 +152,178 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_OrdersMultiSliceBuildingShellsAfterLookIns()
|
||||
{
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord outdoor = Record(
|
||||
0x0100_0000_0000_0101,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord buildingShell = Record(
|
||||
0x0100_0000_0000_0102,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: Cell);
|
||||
RenderProjectionRecord lookInObject = Record(
|
||||
0x0100_0000_0000_0103,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, outdoor),
|
||||
RenderProjectionDelta.Register(Generation, 2, buildingShell),
|
||||
RenderProjectionDelta.Register(Generation, 3, lookInObject),
|
||||
]);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
PortalVisibilityFrame lookIn = Portal(Cell);
|
||||
lookIn.SourceBuildingKey = 2u;
|
||||
lookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
|
||||
var anchorCell = new LoadedCell
|
||||
{
|
||||
CellId = Cell,
|
||||
BuildingId = 2u,
|
||||
};
|
||||
ClipFrameAssembly clip = TwoSliceClip(Cell);
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
RenderSceneDigest digest =
|
||||
scene.BuildDigest(new RenderSceneDigestBuffer());
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
scene.OpenQuery(),
|
||||
digest,
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[lookIn],
|
||||
[Cell],
|
||||
new DictionaryCellSource(anchorCell),
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
|
||||
builder.Build(exchange, frameSequence: 1, in input);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[
|
||||
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 1, 0u),
|
||||
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
|
||||
(RenderFrameCandidateRoute.CellStatic, 0, 0u),
|
||||
],
|
||||
view.RouteRanges.ToArray().Select(static range =>
|
||||
(range.Route, range.RouteIndex, range.CellId)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in view);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_InterleavesEachLookInWithItsOwnPackedBuildingShellRoutes()
|
||||
{
|
||||
const uint secondCell = 0xA9B40180u;
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord firstShell = Record(
|
||||
0x0100_0000_0000_0201,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: Cell);
|
||||
RenderProjectionRecord secondShell = Record(
|
||||
0x0100_0000_0000_0202,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: secondCell);
|
||||
RenderProjectionRecord firstObject = Record(
|
||||
0x0100_0000_0000_0203,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
RenderProjectionRecord secondObject = Record(
|
||||
0x0100_0000_0000_0204,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: secondCell);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, firstShell),
|
||||
RenderProjectionDelta.Register(Generation, 2, secondShell),
|
||||
RenderProjectionDelta.Register(Generation, 3, firstObject),
|
||||
RenderProjectionDelta.Register(Generation, 4, secondObject),
|
||||
]);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
PortalVisibilityFrame firstLookIn = Portal(Cell);
|
||||
firstLookIn.SourceBuildingKey = 2u;
|
||||
firstLookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
|
||||
PortalVisibilityFrame secondLookIn = Portal(secondCell);
|
||||
secondLookIn.SourceBuildingKey = 3u;
|
||||
secondLookIn.SourceBuildingLandblockId = secondCell & 0xFFFF0000u;
|
||||
var firstAnchor = new LoadedCell
|
||||
{
|
||||
CellId = Cell,
|
||||
BuildingId = 2u,
|
||||
};
|
||||
var secondAnchor = new LoadedCell
|
||||
{
|
||||
CellId = secondCell,
|
||||
BuildingId = 3u,
|
||||
};
|
||||
ClipFrameAssembly clip = TwoSliceClip(Cell);
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(1, secondCell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
RenderSceneDigest digest =
|
||||
scene.BuildDigest(new RenderSceneDigestBuffer());
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
scene.OpenQuery(),
|
||||
digest,
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[firstLookIn, secondLookIn],
|
||||
[Cell],
|
||||
new DictionaryCellSource(firstAnchor, secondAnchor),
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
|
||||
builder.Build(exchange, frameSequence: 1, in input);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[
|
||||
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
|
||||
(RenderFrameCandidateRoute.LookInObject, 1, secondCell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 2, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 3, 0u),
|
||||
],
|
||||
view.RouteRanges.ToArray()
|
||||
.Where(static range =>
|
||||
range.Route is RenderFrameCandidateRoute.LookInObject
|
||||
or RenderFrameCandidateRoute.LandscapeBuildingShell)
|
||||
.Select(static range =>
|
||||
(range.Route, range.RouteIndex, range.CellId)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in view);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords()
|
||||
{
|
||||
|
|
@ -501,7 +692,9 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
uint? parentCell = null,
|
||||
RenderProjectionFlags flags =
|
||||
RenderProjectionFlags.Draw
|
||||
| RenderProjectionFlags.SpatiallyResident)
|
||||
| RenderProjectionFlags.SpatiallyResident,
|
||||
bool isBuildingShell = false,
|
||||
uint buildingShellAnchorCellId = 0)
|
||||
{
|
||||
uint fullCellId = parentCell ?? Landblock;
|
||||
Matrix4x4 transform = Matrix4x4.Identity;
|
||||
|
|
@ -531,14 +724,14 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
SourceId: 1,
|
||||
ParentCellId: parentCell ?? 0,
|
||||
EffectCellId: 0,
|
||||
BuildingShellAnchorCellId: 0,
|
||||
BuildingShellAnchorCellId: buildingShellAnchorCellId,
|
||||
TransformFingerprint: default,
|
||||
GeometryFingerprint: default,
|
||||
AppearanceFingerprint: default),
|
||||
new RenderEntityPayload(
|
||||
[new MeshRef((uint)id, Matrix4x4.Identity)],
|
||||
PaletteOverride: null,
|
||||
IsBuildingShell: false));
|
||||
IsBuildingShell: isBuildingShell));
|
||||
}
|
||||
|
||||
private static WorldEntity Entity(
|
||||
|
|
@ -578,6 +771,22 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
return assembly;
|
||||
}
|
||||
|
||||
private static ClipFrameAssembly TwoSliceClip(uint cellId)
|
||||
{
|
||||
var first = new ClipViewSlice(
|
||||
Slot: 0,
|
||||
NdcAabb: new Vector4(-1, -1, 0, 1),
|
||||
Planes: []);
|
||||
var second = new ClipViewSlice(
|
||||
Slot: 1,
|
||||
NdcAabb: new Vector4(0, -1, 1, 1),
|
||||
Planes: []);
|
||||
var assembly = new ClipFrameAssembly();
|
||||
assembly.SetOutsideViewSlices([first, second]);
|
||||
assembly.CellIdToViewSlices[cellId] = [first];
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private static (
|
||||
uint LandblockId,
|
||||
Vector3 AabbMin,
|
||||
|
|
@ -602,4 +811,15 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
|
||||
public LoadedCell? Find(uint cellId) => null;
|
||||
}
|
||||
|
||||
private sealed class DictionaryCellSource : IRetailPViewCellSource
|
||||
{
|
||||
private readonly Dictionary<uint, LoadedCell> _cells;
|
||||
|
||||
public DictionaryCellSource(params LoadedCell[] cells) =>
|
||||
_cells = cells.ToDictionary(static cell => cell.CellId);
|
||||
|
||||
public LoadedCell? Find(uint cellId) =>
|
||||
_cells.TryGetValue(cellId, out LoadedCell? cell) ? cell : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue