streaming: passage-heavy tiles first, far parents after LOD0-1 (L84, headless only)
Predicted passage-overlap tiles are scheduled earlier (FI worst tile rank 116 -> 69, dispatch 0.95 -> 0.82 s); LOD0-1 publish from the base shape before far parents, which are built before LOD2 is queued. Every tile hash identical to L83 (all four whole views). Fast tier PASS. Cold LOD0-2 W8 / W12: SW 1.02 / 0.99, FI 2.26 / 2.18, TN 2.27 / 2.15, VS 1.13 / 1.09 s. Heaviest tiles (FI/TN junction band, z=-93) still take 0.5-0.7 s each, mostly full-grid fill; idealized floor ~0.92 s. Unreal scheduler not yet updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
a81b5b7d06
commit
0a98da6fd0
6 files changed
+590
-9
No files matched your search
@@ -0,0 +1,69 @@
|
||||
# Linux 84 — lower the serial floor
|
||||
|
||||
## Verdict
|
||||
|
||||
Passage-overlap scheduling moves predicted heavy tiles earlier without changing density or mesh output. In the W16 profiles, the repeated FI tile `(0,1,-93,lod0)` moved from priority rank 116 to 69 and its median dispatch time fell from 0.945 s to 0.822 s. The same TN tile moved from rank 366 to 91 and dispatch fell from 1.294 s to 0.959 s. Its intrinsic service time did not fall: the heaviest full-grid tiles still take 0.5–0.7 s in the noisy W16 profiles.
|
||||
|
||||
LOD0–1 now publish from the base shape before far parents are built. The first attempt also released LOD2 early, but changed its pinned hash. The final schedule builds far parents before queuing LOD2, which preserves the exact owner hashes. This trims total shape work by about 11 ms for FI and 20 ms for TN; it does not remove the far-parent work.
|
||||
|
||||
## Slow LOD0 tiles
|
||||
|
||||
The repeat outliers sit in the FI/TN junction band: LOD0 tile `z=-93` spans `-744..-736 m`. The L83 FI maximum `(2,-2,-93)` is 4.1 m from the `passage/1-2/0` route, inside its 4.5 m radius; the L83 TN maximum `(1,-2,-93)` is 3.7 m away. The new maximum `(0,1,-93)` intersects that route. Each listed tile materialized all 42,875 density samples (a 35³ grid, 32,768 candidate cells); their MC grid-fill stage took 341–510 ms. By comparison, FI LOD0 p50 service was 0.8–2.8 ms across the three profiles, and TN p50 was 0.02–0.024 ms. Uniform sign proofs let most tiles skip the full grid; passage and mouth tiles remain unknown or mixed and cannot use that shortcut.
|
||||
|
||||
Slowest observed tile per view across three interleaved W16 profiles:
|
||||
|
||||
| View | L83 before | L84 after |
|
||||
|---|---|---|
|
||||
| FI | `(2,-2,-93)` 561 ms / 399 ms grid; `(1,-2,-93)` 497 / 419; `(1,1,-93)` 475 / 341 | `(0,1,-93)` 709 / 475; `(2,0,-92)` 587 / 499; `(2,-1,-92)` 585 / 502 |
|
||||
| TN | `(1,-2,-93)` 556 ms / 443 ms grid; `(2,0,-93)` 463 / 348; `(3,-2,-93)` 448 / 407 | `(0,1,-93)` 605 / 510; `(1,1,-93)` 600 / 444; `(2,-1,-92)` 527 / 436 |
|
||||
|
||||
Entries show `service ms / MC grid-fill ms`; all are LOD0 with 42,875 samples. The observed maximum rose in this small W16 sample, so the scheduler is a queue-position fix, not a demonstrated reduction in per-tile compute. W8/W12 readiness improved, and whole-view hashes remained exact.
|
||||
|
||||
## Shape compile
|
||||
|
||||
Interleaved W16 medians (milliseconds):
|
||||
|
||||
| View/version | Local shapes | Intervals/signs | Surface band | Far parents | Total shape work |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| FI L83 | 52 | 65 | 65 | 112 | 344 |
|
||||
| FI L84 | 48 | 64 | 69 | 114 | 333 |
|
||||
| TN L83 | 49 | 70 | 87 | 114 | 372 |
|
||||
| TN L84 | 51 | 69 | 88 | 111 | 352 |
|
||||
|
||||
For L84, far parents are reported separately from the base compile. Tiles LOD0–1 run after the base compile; far parents are built after those tiles finish and before LOD2 is queued. The shape object adds far primitives and rebuilds spatial bins in place, so building it concurrently with density reads is unsafe. The controlled L83 shape totals were stable at 338–347 ms FI and 365–378 ms TN; the earlier ~1 s noisy outlier was not reproduced, and these runs do not identify its cause.
|
||||
|
||||
## Cold readiness
|
||||
|
||||
Each W8/W12 cell is the median of three measured interleaved pairs per stratum after one warmup per worker count. Values are seconds, `LOD0–2 / whole view`.
|
||||
|
||||
| Stratum | L83 W8 | L83 W12 | L84 W8 | L84 W12 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| SW | 1.455 / 3.023 | 1.210 / 2.427 | 1.021 / 2.209 | 0.990 / 2.081 |
|
||||
| FI | 2.478 / 3.791 | 2.328 / 3.566 | 2.264 / 3.429 | 2.180 / 3.341 |
|
||||
| TN | 2.475 / 3.631 | 2.327 / 3.384 | 2.269 / 3.377 | 2.146 / 3.153 |
|
||||
| VS | 1.205 / 2.242 | 1.274 / 2.254 | 1.127 / 2.080 | 1.086 / 2.023 |
|
||||
|
||||
FI/TN improve by 0.15–0.21 s at W8 and 0.15–0.18 s at W12, but still miss the approximately 1 s target. W12 helps over W8. L83 W12-to-W16 medians were flat: FI near/whole changed 2.328/3.566 to 2.342/3.608 s; TN changed 2.327/3.384 to 2.295/3.368 s.
|
||||
|
||||
CPUs 0–11 map to six physical cores with two logical threads each. The W16 traces reached 15 active workers while the queue remained over 1,500 tiles deep, so queue starvation is not the limit and W16 oversubscribes the pinned logical CPUs. No bandwidth counters were collected; these results cannot separate SMT contention from memory bandwidth.
|
||||
|
||||
## Updated floor and hashes
|
||||
|
||||
Adding total shape work to the median per-run slowest-tile service gives an idealized floor of about **0.92 s FI / 0.91 s TN**, with W16 noisy-run maxima near 1.04 s / 0.96 s. This is essentially the L83 ~1 s floor: far-parent deferral trims a few milliseconds of total work, while full-grid tile service remains the larger serial term.
|
||||
|
||||
Every L84 tile hash and whole-view aggregate matched L83 (zero mismatches):
|
||||
|
||||
| Stratum | Tiles | Density | Mesh | Payload | Topology |
|
||||
|---|---:|---|---|---|---|
|
||||
| SW | 2,850 | `40b76d0072a2adcd` | `8559df0850d4d7f1` | `7700965ff31ac702` | `0e7f2a16b4cc71a9` |
|
||||
| FI | 2,249 | `99ca213b21ce86e4` | `2977525405b79fff` | `3d7d2efb581710bd` | `af1916c949224871` |
|
||||
| TN | 2,233 | `8d23c26f5c092be1` | `15c3aad2e29d9ea0` | `3d48f68d511c21df` | `5b1584676884edd8` |
|
||||
| VS | 2,329 | `876b44981d106be3` | `aaef57cb08bd6845` | `b19617df9f4acbb1` | `e8eee991260adff4` |
|
||||
|
||||
Fast tier passed all 274 checks. No Unreal `Source/` or Unreal-compiled Core files changed, so no Linux UE build was required.
|
||||
|
||||
## Failed runs
|
||||
|
||||
- Initial fast-tier launch at `Saved/Linux/l84/verify/runner.log` stopped before running gates because its round-specific binary was missing. The final fast run passed.
|
||||
- The first W16 profile runner rejected a valid L83 FI run because its parser expected the new `far_shape` summary field. The parser was fixed and all 12 final interleaved profiles completed.
|
||||
- Broad far-parent deferral failed the pinned owner LOD2 and whole-view hash gate. Deferring only until LOD0–1 finish, then publishing far parents before LOD2, passed the final fast and full-view hash checks.
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare L83 and L84 per-tile and whole-view hashes for all four full views."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l84/hash-check-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L83": ROOT / "Saved/Linux/l83/bin/VoxelForgeCoreHeadless-l83-clang19",
|
||||
"L84": ROOT / "Saved/Linux/l84/bin/VoxelForgeCoreHeadless-l84-clang19",
|
||||
}
|
||||
STAMPS = {
|
||||
"L83": "08af4e98c86a69477cc79e21463d8c93bcd11ebe0f077d7c11519031d9141fa9",
|
||||
"L84": "c433a039b0255ce3c4794bf20313b8a5a11442feeb07b6555f585688949741a5",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
EXPECTED_WHOLE = {0: 2850, 1: 2249, 2: 2233, 3: 2329}
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
TILE_HASH = re.compile(
|
||||
r"^\[PipelineTileHash\] lod=(-?\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||
r"density=(0x[0-9a-f]+) mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) "
|
||||
r"topology=(0x[0-9a-f]+)$", re.M)
|
||||
WHOLE_HASH = re.compile(
|
||||
r"^\[PipelineWholeViewHash\] tiles=(\d+) density=(0x[0-9a-f]+) "
|
||||
r"mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) topology=(0x[0-9a-f]+)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, version: str) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / f"{version.lower()}-s{strate}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--stream-scheduler-policy=3", "--stream-hash-tiles",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
source, whole = STAMP.search(output), WHOLE_HASH.search(output)
|
||||
hashes = {
|
||||
(int(match.group(1)), int(match.group(2)), int(match.group(3)), int(match.group(4))):
|
||||
match.group(5, 6, 7, 8)
|
||||
for match in TILE_HASH.finditer(output)
|
||||
}
|
||||
if result.returncode or source is None or whole is None:
|
||||
raise RuntimeError(f"hash run failed: exit={result.returncode} {log}")
|
||||
if source.group(1) != STAMPS[version]:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}: {source.group(1)}")
|
||||
if int(whole.group(1)) != EXPECTED_WHOLE[strate] or len(hashes) != EXPECTED_WHOLE[strate]:
|
||||
raise RuntimeError(f"incomplete whole-view hashes in {log}: tiles={len(hashes)} whole={whole.group(1)}")
|
||||
row = {
|
||||
"version": version, "strate": strate, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3), "tile_count": len(hashes),
|
||||
"whole_hash": whole.group(1, 2, 3, 4, 5), "hashes": hashes,
|
||||
"source_stamp": source.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L84HashRun] " + json.dumps(
|
||||
{key: value for key, value in row.items() if key != "hashes"}, sort_keys=True),
|
||||
flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for binary in (*BINARIES.values(), DATA):
|
||||
if not binary.is_file():
|
||||
raise SystemExit(f"missing input: {binary}")
|
||||
rows, comparisons = [], []
|
||||
for strate in range(4):
|
||||
pair = {version: run(strate, version) for version in ("L83", "L84")}
|
||||
before, after = pair["L83"], pair["L84"]
|
||||
mismatches = sorted(key for key in set(before["hashes"]) | set(after["hashes"])
|
||||
if before["hashes"].get(key) != after["hashes"].get(key))
|
||||
comparison = {
|
||||
"strate": strate, "tile_count": len(before["hashes"]),
|
||||
"tile_hashes_identical": not mismatches,
|
||||
"whole_hash_identical": before["whole_hash"] == after["whole_hash"],
|
||||
"mismatch_count": len(mismatches), "first_mismatches": mismatches[:12],
|
||||
"l83_whole_hash": before["whole_hash"], "l84_whole_hash": after["whole_hash"],
|
||||
"logs": {version: pair[version]["log"] for version in pair},
|
||||
}
|
||||
comparisons.append(comparison)
|
||||
rows.extend({key: value for key, value in row.items() if key != "hashes"}
|
||||
for row in pair.values())
|
||||
(OUT / "runs-partial.json").write_text(json.dumps(
|
||||
{"comparisons": comparisons, "runs": rows}, indent=2) + "\n", encoding="utf-8")
|
||||
print("[L84HashComparison] " + json.dumps(comparison, sort_keys=True), flush=True)
|
||||
return 0 if all(row["tile_hashes_identical"] and row["whole_hash_identical"]
|
||||
for row in comparisons) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""L80-protocol W8/W12 readiness pairs for the L84 far-parent deferral candidate."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l84/scaling-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARY = ROOT / "Saved/Linux/l84/bin/VoxelForgeCoreHeadless-l84-clang19"
|
||||
WORKERS = (8, 12)
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
EXPECTED_NEAR = {0: 1408, 1: 1216, 2: 1344, 3: 1344}
|
||||
EXPECTED_WHOLE = {0: 2850, 1: 2249, 2: 2233, 3: 2329}
|
||||
SOURCE_STAMP = "c433a039b0255ce3c4794bf20313b8a5a11442feeb07b6555f585688949741a5"
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, workers: int, phase: str, rep: int) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / f"s{strate}-w{workers}-{phase}{rep}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARY), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", f"--workers={workers}", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--profile-lod-work", "--stream-scheduler-policy=3",
|
||||
]
|
||||
start = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
pipeline_match, near_match, stamp_match = (
|
||||
PIPELINE.search(output), NEAR.search(output), STAMP.search(output))
|
||||
if result.returncode or pipeline_match is None or near_match is None:
|
||||
raise RuntimeError(f"readiness run failed: exit={result.returncode} {log}")
|
||||
if stamp_match is None or stamp_match.group(1) != SOURCE_STAMP:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}")
|
||||
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
|
||||
near_done, near_total = int(near_match.group(1)), int(near_match.group(2))
|
||||
whole_done, whole_total = (int(v) for v in pipeline["whole_tiles"].split("/"))
|
||||
if (near_done, near_total) != (EXPECTED_NEAR[strate], EXPECTED_NEAR[strate]):
|
||||
raise RuntimeError(f"near readiness incomplete in {log}: {near_done}/{near_total}")
|
||||
if (whole_done, whole_total) != (EXPECTED_WHOLE[strate], EXPECTED_WHOLE[strate]):
|
||||
raise RuntimeError(f"whole readiness incomplete in {log}: {whole_done}/{whole_total}")
|
||||
row = {
|
||||
"strate": strate, "workers": workers, "phase": phase, "replicate": rep,
|
||||
"exit_code": result.returncode, "wall_s": round(time.monotonic() - start, 3),
|
||||
"lod02_ready_s": float(near_match.group(3)),
|
||||
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||
"shape_s": float(pipeline["shape"].removesuffix("s")),
|
||||
"far_shape_s": float(pipeline["far_shape"].removesuffix("s")),
|
||||
"first_tile_ready_s": float(pipeline["first_tile_ready"].removesuffix("s")),
|
||||
"neighborhood_ready_s": float(pipeline["neighborhood_ready"].removesuffix("s")),
|
||||
"full_detail_ready_s": float(pipeline["full_detail_ready"].removesuffix("s")),
|
||||
"near_tiles": near_total, "whole_tiles": whole_total,
|
||||
"source_stamp": stamp_match.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L84ScalingRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not BINARY.is_file() or not DATA.is_file():
|
||||
raise SystemExit(f"missing input: {BINARY} or {DATA}")
|
||||
rows = []
|
||||
for strate in range(4):
|
||||
for workers in WORKERS:
|
||||
run(strate, workers, "warmup", 0)
|
||||
for rep, order in enumerate(((8, 12), (12, 8), (8, 12)), 1):
|
||||
for workers in order:
|
||||
rows.append(run(strate, workers, "measured", rep))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in range(4):
|
||||
for workers in WORKERS:
|
||||
subset = [row for row in rows
|
||||
if row["strate"] == strate and row["workers"] == workers]
|
||||
item = {"strate": strate, "workers": workers, "measured_runs": len(subset)}
|
||||
for metric in ("lod02_ready_s", "whole_view_ready_s", "shape_s", "far_shape_s",
|
||||
"first_tile_ready_s", "neighborhood_ready_s", "full_detail_ready_s"):
|
||||
values = [row[metric] for row in subset]
|
||||
item[metric] = values
|
||||
item[f"{metric}_median"] = statistics.median(values)
|
||||
item[f"{metric}_min"] = min(values)
|
||||
item[f"{metric}_max"] = max(values)
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Interleaved W16 tile and shape-stage profiles before and after L84."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l84/serial-floor-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L83": ROOT / "Saved/Linux/l83/profile-bin/VoxelForgeCoreHeadless-l83-profile-clang19",
|
||||
"L84": ROOT / "Saved/Linux/l84/profile-bin/VoxelForgeCoreHeadless-l84-profile-clang19",
|
||||
}
|
||||
STAMPS = {
|
||||
"L83": "08af4e98c86a69477cc79e21463d8c93bcd11ebe0f077d7c11519031d9141fa9",
|
||||
"L84": "c433a039b0255ce3c4794bf20313b8a5a11442feeb07b6555f585688949741a5",
|
||||
}
|
||||
CAMERAS = {
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
}
|
||||
NAMES = {1: "FI", 2: "TN"}
|
||||
EXPECTED_NEAR = {1: 1216, 2: 1344}
|
||||
EXPECTED_WHOLE = {1: 2249, 2: 2233}
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
SHAPE_FIELDS = re.compile(r"([a-zA-Z0-9_+\-]+)=([0-9.]+)")
|
||||
TIMELINE = re.compile(
|
||||
r"^\[CpuTileTimeline\] event=finish key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
STAGES = re.compile(
|
||||
r"^\[CpuTileStages\] key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
SHAPE_STAGE = re.compile(r"^\[ShapeStage\] (.*)$", re.M)
|
||||
FAR_STAGE = re.compile(r"^\[FarShapeStage\] (.*)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, version: str, rep: int) -> dict:
|
||||
out_dir = OUT / version.lower()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
log = out_dir / f"{NAMES[strate].lower()}-w16-r{rep}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=16", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--profile-lod-work", "--instrument-tile-drain", "--stream-scheduler-policy=3",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
source = STAMP.search(output)
|
||||
near = NEAR.search(output)
|
||||
pipeline = PIPELINE.search(output)
|
||||
if result.returncode or source is None or near is None or pipeline is None:
|
||||
raise RuntimeError(f"profile run failed: exit={result.returncode} {log}")
|
||||
if source.group(1) != STAMPS[version]:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}: {source.group(1)}")
|
||||
near_done, near_total = int(near.group(1)), int(near.group(2))
|
||||
p = dict(FIELDS.findall(pipeline.group(1)))
|
||||
whole_done, whole_total = (int(value) for value in p["whole_tiles"].split("/"))
|
||||
if near_total != EXPECTED_NEAR[strate] or near_done != near_total:
|
||||
raise RuntimeError(f"incomplete near tiles in {log}: {near_done}/{near_total}")
|
||||
if whole_total != EXPECTED_WHOLE[strate] or whole_done != whole_total:
|
||||
raise RuntimeError(f"incomplete whole view in {log}: {whole_done}/{whole_total}")
|
||||
|
||||
stage_rows = {}
|
||||
for match in STAGES.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
stage_rows[key] = dict(FIELDS.findall(match.group(5)))
|
||||
tiles = []
|
||||
for match in TIMELINE.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
fields = dict(FIELDS.findall(match.group(5)))
|
||||
if key[3] > 2:
|
||||
continue
|
||||
stage = stage_rows.get(key, {})
|
||||
tiles.append({
|
||||
"key": key, "lod": key[3], "service_ms": float(fields["service_ms"]),
|
||||
"queued_ms": float(fields["queued_ms"]), "dispatch_ms": float(fields["dispatch_ms"]),
|
||||
"finish_ms": float(fields["finish_ms"]), "priority_rank": int(fields["priority_rank"]),
|
||||
"queue_depth": int(fields["queue_depth"]), "active_workers": int(fields["active_workers"]),
|
||||
"samples": int(stage.get("samples", "0")),
|
||||
"candidate_cells": int(stage.get("candidate_cells", "0")),
|
||||
"surface_cells": int(stage.get("surface_cells", "0")),
|
||||
"tile_class": stage.get("tile_class", "unknown"),
|
||||
"mc_grid_ms": float(stage.get("mc_grid_ms", "0")),
|
||||
"mc_cell_ms": float(stage.get("mc_cell_ms", "0")),
|
||||
"mc_gradient_ms": float(stage.get("mc_gradient_ms", "0")),
|
||||
"mc_skirt_ms": float(stage.get("mc_skirt_ms", "0")),
|
||||
})
|
||||
if len(tiles) != near_total:
|
||||
raise RuntimeError(f"incomplete near tile timeline in {log}: {len(tiles)}/{near_total}")
|
||||
by_lod = {}
|
||||
for lod in range(3):
|
||||
subset = [tile for tile in tiles if tile["lod"] == lod]
|
||||
services = [tile["service_ms"] for tile in subset]
|
||||
by_lod[lod] = {
|
||||
"tiles": len(subset), "p50_service_ms": statistics.median(services),
|
||||
"p90_service_ms": sorted(services)[int((len(services) - 1) * 0.90)],
|
||||
"p95_service_ms": sorted(services)[int((len(services) - 1) * 0.95)],
|
||||
"max_service_ms": max(services),
|
||||
}
|
||||
slow = sorted(tiles, key=lambda tile: tile["service_ms"], reverse=True)[:12]
|
||||
stages = [dict(SHAPE_FIELDS.findall(match.group(1))) for match in SHAPE_STAGE.finditer(output)]
|
||||
far_stages = [dict(FIELDS.findall(match.group(1))) for match in FAR_STAGE.finditer(output)]
|
||||
far_shape_seconds = (float(p["far_shape"].removesuffix("s"))
|
||||
if "far_shape" in p else
|
||||
(float(far_stages[-1]["total"]) / 1000.0 if far_stages else 0.0))
|
||||
row = {
|
||||
"version": version, "strate": strate, "name": NAMES[strate], "replicate": rep,
|
||||
"workers": 16, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
"near_ready_s": float(near.group(3)), "pipeline_shape_s": float(p["shape"].removesuffix("s")),
|
||||
"pipeline_far_shape_s": far_shape_seconds,
|
||||
"shape_stages": stages, "far_shape_stages": far_stages,
|
||||
"tile_count": len(tiles), "by_lod": by_lod, "slow_tiles": slow,
|
||||
"source_stamp": source.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L84SerialFloorRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for binary in BINARIES.values():
|
||||
if not binary.is_file():
|
||||
raise SystemExit(f"missing binary: {binary}")
|
||||
rows = []
|
||||
orders = ((1, "L83"), (1, "L84"), (2, "L84"), (2, "L83"))
|
||||
for rep in range(1, 4):
|
||||
order = orders if rep % 2 else tuple(reversed(orders))
|
||||
for strate, version in order:
|
||||
rows.append(run(strate, version, rep))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in (1, 2):
|
||||
for version in ("L83", "L84"):
|
||||
subset = [row for row in rows
|
||||
if row["strate"] == strate and row["version"] == version]
|
||||
item = {"strate": strate, "name": NAMES[strate], "version": version,
|
||||
"runs": [row["log"] for row in subset]}
|
||||
for metric in ("near_ready_s", "pipeline_shape_s", "pipeline_far_shape_s"):
|
||||
values = [row[metric] for row in subset]
|
||||
item[f"{metric}_runs"] = values
|
||||
item[f"{metric}_median"] = statistics.median(values)
|
||||
item[f"{metric}_min"] = min(values)
|
||||
item[f"{metric}_max"] = max(values)
|
||||
item["shape_stage_medians_ms"] = {}
|
||||
for stage, key in (("local_shapes", "local-shapes"),
|
||||
("intervals_signs", "intervals+signs"),
|
||||
("surface_band", "surface-band"),
|
||||
("far_parents", "far-parents")):
|
||||
values = [float(shape_row[key]) for row in subset
|
||||
for shape_row in row["shape_stages"] if key in shape_row]
|
||||
item["shape_stage_medians_ms"][stage] = (
|
||||
statistics.median(values) if values else None)
|
||||
slow_per_run = [tile for row in subset for tile in row["slow_tiles"]]
|
||||
item["slowest_tiles_all_runs"] = slow_per_run
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -2030,6 +2030,15 @@ namespace StandaloneStreamBenchmark
|
||||
World.OperationStorage.clear();
|
||||
World.DensitySamples.clear();
|
||||
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
|
||||
// LOD0-1 can start from the base field while far parents compile. LOD2 can sample
|
||||
// parent transitions in owner-world views, so publish far parents before queuing LOD2.
|
||||
// Special audits retain the historical eager build because they may query the compiled
|
||||
// shape directly.
|
||||
const bool bFarWorldAudit=bAuditNativeCaves||bAuditNativeLattice||bAuditMotifIntervals
|
||||
||bAuditOreVeinYield||bAuditLandmarkReachability||bAuditStrateTransitions
|
||||
||bAuditResolvedLayoutPassages||bAuditResolvedLayoutBoundaries
|
||||
||bAuditTileShapeReadiness||bAuditBasePageEquivalence||bAuditRouteParity;
|
||||
const bool bDeferFarWorld=bIncludeFarWorld&&!bMovingCameraTrace&&!bFarWorldAudit;
|
||||
VoxelForgeCore::ShapeBuildRequest ShapeRequest;
|
||||
ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z};
|
||||
ShapeRequest.maxVoxel={FineMax.X,FineMax.Y,FineMax.Z};
|
||||
@@ -2037,7 +2046,7 @@ namespace StandaloneStreamBenchmark
|
||||
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
|
||||
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,16));
|
||||
ShapeRequest.parallelSignEdgeRepair=bParallelSignEdgeRepair;
|
||||
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
|
||||
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld&&!bDeferFarWorld;
|
||||
ShapeRequest.roundTunnels=bRoundTunnels;
|
||||
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
|
||||
ShapeRequest.validatePassageRoutes=!bCaptureSkipWalkability;
|
||||
@@ -2047,6 +2056,7 @@ namespace StandaloneStreamBenchmark
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
|
||||
bAllowUniformSkipsWhenHashing);
|
||||
Streamer.SetPassageHeavyPriority(ResolvedLayout);
|
||||
Streamer.SetSchedulerPolicy(SchedulerPolicy);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
|
||||
@@ -4978,13 +4988,25 @@ namespace StandaloneStreamBenchmark
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
FStandaloneDesiredTileSet EarlyDesired=Desired;
|
||||
if(bDeferFarWorld)
|
||||
std::erase_if(EarlyDesired.Tiles,[](const FVoxelTileKey& Key){return Key.Level>1;});
|
||||
const int32 EarlyDesiredCount=static_cast<int32>(EarlyDesired.Tiles.size());
|
||||
const int32 InitialDesiredCount=bDeferFarWorld?EarlyDesiredCount:
|
||||
static_cast<int32>(Desired.Tiles.size());
|
||||
FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(
|
||||
bDeferFarWorld?EarlyDesired:Desired,{},0);
|
||||
if(Update.DesiredTiles!=InitialDesiredCount)
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after shape publication but queued %d.\n",
|
||||
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %d tiles after shape publication but queued %d.\n",
|
||||
InitialDesiredCount,Update.DesiredTiles);return 7;
|
||||
}
|
||||
double DeferredFarShapeSeconds=0.0;
|
||||
bool bDeferredFarTilesQueued=false;
|
||||
if(bDeferFarWorld)
|
||||
std::printf("[ShapeSchedule] defer_far_parents=1 early_tiles=%d late_tiles=%zu start=after_base_shape_compile\n",
|
||||
EarlyDesiredCount,Desired.Tiles.size()-EarlyDesired.Tiles.size());
|
||||
if(bMovingCameraTrace)
|
||||
{
|
||||
constexpr double SamplePeriodSeconds=0.25;
|
||||
@@ -5141,7 +5163,7 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
|
||||
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
|
||||
static_cast<std::uint32_t>(Update.DesiredTiles),
|
||||
static_cast<std::uint32_t>(Desired.Tiles.size()),
|
||||
ResolvedLayout&&!ResolvedLayout->oreVeins.empty());
|
||||
if(!bSceneExportPassing)
|
||||
{
|
||||
@@ -5496,6 +5518,25 @@ namespace StandaloneStreamBenchmark
|
||||
FullDetailFinished,FullDetailExpected);
|
||||
bFullDetailReported=true;
|
||||
}
|
||||
if(bDeferFarWorld&&!bDeferredFarTilesQueued&&Finished==EarlyDesiredCount
|
||||
&&Desired.Tiles.size()>EarlyDesired.Tiles.size())
|
||||
{
|
||||
const auto FarBuildStart=Clock::now();
|
||||
Shape.buildFarWorld();
|
||||
DeferredFarShapeSeconds=std::chrono::duration<double>(
|
||||
Clock::now()-FarBuildStart).count();
|
||||
std::printf("[ShapeSchedule] defer_far_parents=1 early_ready_s=%.6f far_shape_s=%.6f action=publish_before_lod2_tiles\n",
|
||||
std::chrono::duration<double>(Clock::now()-ColdStart).count(),
|
||||
DeferredFarShapeSeconds);
|
||||
Update=Streamer.UpdateDesiredWithSet(Desired,EarlyDesired.Tiles,0);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after far-parent publication but queued %d.\n",
|
||||
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
||||
}
|
||||
bDeferredFarTilesQueued=true;
|
||||
}
|
||||
}
|
||||
if(bHasCameraStep)
|
||||
{
|
||||
@@ -5746,7 +5787,7 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
if(bExportRequested)
|
||||
{
|
||||
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Update.DesiredTiles));
|
||||
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Desired.Tiles.size()));
|
||||
std::error_code FileSizeError;
|
||||
const std::uintmax_t ExportBytes=std::filesystem::file_size(MeshExportPath,FileSizeError);
|
||||
std::printf("[MeshSceneExport] tiles=%u expected=%d bytes=%llu status=%s path=%s\n",
|
||||
@@ -5962,8 +6003,9 @@ namespace StandaloneStreamBenchmark
|
||||
const double WholeViewSeconds=std::chrono::duration<double>(WholeViewReady-ColdStart).count();
|
||||
const double StreamSeconds=std::chrono::duration<double>(WholeViewReady-StreamStart).count();
|
||||
const double FirstTileSeconds=std::chrono::duration<double>(FirstTileReady-ColdStart).count();
|
||||
std::printf("[StreamPipeline] workers=%d shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n",
|
||||
WorkerCount,ShapeSeconds,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
|
||||
const double FarShapeSeconds=Shape.buildStats().farShapeSeconds;
|
||||
std::printf("[StreamPipeline] workers=%d shape=%.3fs far_shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n",
|
||||
WorkerCount,ShapeSeconds,FarShapeSeconds,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
|
||||
FullDetailSeconds,WholeViewSeconds,NeighborhoodFinished,
|
||||
FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds);
|
||||
if(bReportMeshCoverage)
|
||||
|
||||
@@ -437,6 +437,13 @@ namespace StandaloneWorldMesher
|
||||
|
||||
class FStandaloneStreamingGenerator
|
||||
{
|
||||
private:
|
||||
struct FPassagePrioritySegment
|
||||
{
|
||||
std::array<std::int64_t,3> Minimum{};
|
||||
std::array<std::int64_t,3> Maximum{};
|
||||
};
|
||||
|
||||
public:
|
||||
static void SetCoarseFirstSchedulingEnabled(bool bEnabled)
|
||||
{bCoarseFirstSchedulingEnabled.store(bEnabled,std::memory_order_relaxed);}
|
||||
@@ -553,6 +560,35 @@ public:
|
||||
|
||||
void SetSchedulerPolicy(int32 Policy)
|
||||
{SchedulerPolicy=std::clamp(Policy,-1,3);}
|
||||
void SetPassageHeavyPriority(const VoxelForgeCore::ResolvedLayoutSnapshotV3* Layout)
|
||||
{
|
||||
PassagePrioritySegments.clear();
|
||||
if(Layout)
|
||||
{
|
||||
constexpr std::int64_t SubvoxelsPerVoxel=
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr std::int64_t ExtraInfluenceVoxels=32;
|
||||
for(const auto& Passage:Layout->passages)
|
||||
for(std::size_t Index=1;Index<Passage.controlPoints.size();++Index)
|
||||
{
|
||||
const auto& A=Passage.controlPoints[Index-1];
|
||||
const auto& B=Passage.controlPoints[Index];
|
||||
const std::int64_t Radius=std::max(A.radiusSubvoxels,B.radiusSubvoxels)
|
||||
/SubvoxelsPerVoxel+ExtraInfluenceVoxels;
|
||||
FPassagePrioritySegment Segment;
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
const std::int64_t AVoxel=A.positionSubvoxels[Axis]/SubvoxelsPerVoxel;
|
||||
const std::int64_t BVoxel=B.positionSubvoxels[Axis]/SubvoxelsPerVoxel;
|
||||
Segment.Minimum[Axis]=std::min(AVoxel,BVoxel)-Radius;
|
||||
Segment.Maximum[Axis]=std::max(AVoxel,BVoxel)+Radius;
|
||||
}
|
||||
PassagePrioritySegments.push_back(Segment);
|
||||
}
|
||||
}
|
||||
std::printf("[PassageHeavyPriority] route_segments=%zu enabled=%d\n",
|
||||
PassagePrioritySegments.size(),PassagePrioritySegments.empty()?0:1);
|
||||
}
|
||||
void SetSchedulerMotionSpeedMetersPerSecond(double Speed)
|
||||
{
|
||||
const double ClampedSpeed=std::max(0.0,Speed);
|
||||
@@ -911,6 +947,16 @@ private:
|
||||
else if(Key.Level<=2)NearTiles.push_back(Key);
|
||||
else FarTiles.push_back(Key);
|
||||
}
|
||||
std::unordered_map<FVoxelTileKey,std::size_t,FStandaloneTileKeyHash>
|
||||
PassageScores;
|
||||
if(!PassagePrioritySegments.empty())
|
||||
{
|
||||
PassageScores.reserve((WalkableRing.size()+NearTiles.size())*2+1);
|
||||
for(const FVoxelTileKey& Key:WalkableRing)
|
||||
PassageScores.emplace(Key,PassageOverlapScore(Key));
|
||||
for(const FVoxelTileKey& Key:NearTiles)
|
||||
PassageScores.emplace(Key,PassageOverlapScore(Key));
|
||||
}
|
||||
const auto IsAhead=[&](const FVoxelTileKey& Key)
|
||||
{
|
||||
if(TravelDirection.X*TravelDirection.X+TravelDirection.Y*TravelDirection.Y<=1.0e-12f)
|
||||
@@ -925,6 +971,12 @@ private:
|
||||
std::sort(Group.begin(),Group.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
if(A.Level!=B.Level)return bNearFirst?A.Level<B.Level:A.Level>B.Level;
|
||||
if(A.Level<=2&&!PassageScores.empty())
|
||||
{
|
||||
const std::size_t ScoreA=PassageScores.at(A);
|
||||
const std::size_t ScoreB=PassageScores.at(B);
|
||||
if(ScoreA!=ScoreB)return ScoreA>ScoreB;
|
||||
}
|
||||
const bool bAheadA=IsAhead(A),bAheadB=IsAhead(B);
|
||||
if(bAheadA!=bAheadB)return bAheadA;
|
||||
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A,NewDesired.CenterChunk);
|
||||
@@ -1253,6 +1305,25 @@ private:
|
||||
{
|
||||
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
|
||||
}
|
||||
std::size_t PassageOverlapScore(const FVoxelTileKey& Key) const
|
||||
{
|
||||
const FIntVector Origin=Key.OriginVoxels();
|
||||
const std::int64_t TileMinimum[3]={Origin.X,Origin.Y,Origin.Z};
|
||||
const std::int64_t TileMaximum[3]={
|
||||
static_cast<std::int64_t>(Origin.X)+Key.ExtentVoxels(),
|
||||
static_cast<std::int64_t>(Origin.Y)+Key.ExtentVoxels(),
|
||||
static_cast<std::int64_t>(Origin.Z)+Key.ExtentVoxels()};
|
||||
std::size_t Score=0;
|
||||
for(const FPassagePrioritySegment& Segment:PassagePrioritySegments)
|
||||
{
|
||||
bool bOverlap=true;
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
bOverlap=bOverlap&&Segment.Maximum[Axis]>=TileMinimum[Axis]
|
||||
&&Segment.Minimum[Axis]<=TileMaximum[Axis];
|
||||
Score+=bOverlap?1u:0u;
|
||||
}
|
||||
return Score;
|
||||
}
|
||||
static void PrintTileStageEvent(const FStandaloneCpuTileTimelineEvent& Event)
|
||||
{
|
||||
const auto& D=Event.Diagnostics;
|
||||
@@ -1386,6 +1457,7 @@ private:
|
||||
bool bActiveLaneBatching=false;
|
||||
int32 ProofBlockCells=8;
|
||||
bool bAuditUniformBlockProofs=false;
|
||||
std::vector<FPassagePrioritySegment> PassagePrioritySegments;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
std::condition_variable Wake;
|
||||
|
||||
Reference in new issue
Block a user