Skip to content

chore: perf 2026 08 06 - #9655

Draft
eordano wants to merge 6 commits into
devfrom
perf/hunt-2026-08-06
Draft

chore: perf 2026 08 06#9655
eordano wants to merge 6 commits into
devfrom
perf/hunt-2026-08-06

Conversation

@eordano

@eordano eordano commented Aug 7, 2026

Copy link
Copy Markdown
Member

No description provided.

eordano and others added 6 commits August 7, 2026 02:19
…CS, streaming

Output of a scan -> filter -> review pipeline over the perf-critical
subsystems, each fix independently re-verified against the code and passed
through the repo's Jarvis review protocol (.github/prompts/review-instructions.md).
20 candidates surfaced; 19 land here, 1 (ApplyMaterialSystem throttle) was
dropped as a wrong-tool approach. Each fix ships a [Performance][Category("Performance")]
test designed to falsify it (assert the metric delta, fail on revert).

Rendering/GPU: drop redundant per-frame DrawArgs upload (GPUInstancingService);
group-shared LOD-compaction reduction (LODBuffersEvaluation.compute); reflection
probe IndividualFaces time-slicing; hoist grass compute uploads; reuse ground
native containers.

Avatars: bound the bone-matrix job by the consumer's authoritative BoneCount
(refreshed per-frame for main-player re-equip); gate RecalculateTangents on the
real _BumpMap source; restore the texture-array mip chain; hoist per-avatar
frustum extraction; early-out the emote Mecanim poll.

ECS: jobify PartitionAssetEntities; dirty-gate proxy transforms against the live
world transform (covers parent scene-load reposition); assign the transform world
cache from arguments; split visibility event systems from the throttled
dirty-query path.

Streaming/map: skip Sentry transaction alloc for non-whitelisted requests; cache
the artificial-delay toggle; pre-size PartialDownloadHandler; block-compress atlas
tiles; budget the map became-visible burst through the existing dirtyObjects drainer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts + add Landscape/MapRenderer refs

Assembly references aren't transitive here, so each new perf-test folder must
directly reference Unity.PerformanceTesting (carried only by DCL.PlayMode.Tests)
plus its code-under-test. 14 folders get a .asmref into DCL.PlayMode.Tests; that
assembly gains DCL.Landscape + MapRenderer references.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ob-system refs

Moving the Culling tests into DCL.PlayMode.Tests lost MapRenderer InternalsVisibleTo
access (CS0122) and Unity.Burst/Collections/Mathematics/Jobs refs (CS0246 on the
partition test's BurstCompile job). Grant PlayMode.Tests the internals and add the
8 job-system assembly refs the code-under-test already uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… not a real API)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… runtime)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/#17 harnesses

#6: assert bucket+isBehind bit-exact (behaviour) but compare the raw diagnostic
float within tolerance -- Burst vs Mono differ by 1 ULP on raw, which never flips
the LOD/priority bucket (verified: all logged mismatches had identical bucket).
#16/#17: measure allocations via ProfilerRecorder(GC.Alloc)+Measure.Method().GC()
instead of GC.GetAllocatedBytesForCurrentThread (unsupported on Unity runtime).
#14: create ROOT_POOL_CONTAINER in SetUp so EmotePlayer ctor doesn't NRE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@eordano eordano added the force-build Used to trigger a build on draft PR label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

badge

Warnings not reduced: 13729 => 13783 — remove at least 55 warnings to merge.

Warnings/errors in files changed by this PR (216)
Assets/DCL/AvatarRendering/AvatarShape/Tests/PlayMode/BoneMatrixCalculationJobPerformanceTest.cs:91  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/Infrastructure/ECS/Unity/Transforms/Tests/PerformanceTests/SetWorldTransformCachePerformanceTest.cs:162  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/Landscape/Tests/PerformanceTests/GroundContainerReuse/RenderGroundContainerReusePerformanceTest.cs:224  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/Landscape/Tests/PerformanceTests/GroundContainerReuse/RenderGroundContainerReusePerformanceTest.cs:224  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/Landscape/Tests/PerformanceTests/GroundContainerReuse/RenderGroundContainerReusePerformanceTest.cs:225  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/Landscape/Tests/PerformanceTests/GroundContainerReuse/RenderGroundContainerReusePerformanceTest.cs:226  AccessToDisposedClosure  Captured variable is disposed in the outer scope
Assets/DCL/AvatarRendering/AvatarShape/ComputeShader/ComputeShaderSkinning.cs:283  ArrangeMissingParentheses  Non-obvious precedence
Assets/DCL/AvatarRendering/AvatarShape/Rendering/TextureArray/TextureArraySlotHandler.cs:34  ArrangeMissingParentheses  Non-obvious precedence
Assets/DCL/Infrastructure/ECS/Unity/Prioritization/Tests/PerformanceTests/PartitionAssetEntitiesPerformanceTest.cs:147  ArrangeMissingParentheses  Non-obvious precedence
Assets/DCL/WebRequests/CustomDownloadHandlers/Tests/PerformanceTests/PartialDownloadHandlerGrowthPerformanceTest.cs:43  ArrangeMissingParentheses  Non-obvious precedence
Assets/DCL/AvatarRendering/AvatarShape/Tests/PerformanceTests/AvatarOutlineFrustumHoistPerformanceTest.cs:144  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/AvatarRendering/AvatarShape/Tests/PlayMode/BoneMatrixCalculationJobPerformanceTest.cs:65  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Infrastructure/ECS/Unity/Prioritization/Systems/PartitionAssetEntitiesSystem.cs:338  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/MapRenderer/Culling/MapCullingController.cs:33  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/MapRenderer/Culling/MapCullingController.cs:92  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Rendering/GPUInstancing/Tests/PerformanceTests/GPUInstancingDrawArgsUploadPerformanceTest.cs:153  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Rendering/Reflections/Tests/PerformanceTests/ReflectionProbeRendererPerformanceTest.cs:148  AssignNullToNotNullAttribute  Possible 'null' assignment to non-nullable entity
Assets/DCL/Rendering/Reflections/Tests/PerformanceTests/ReflectionProbeRendererPerformanceTest.cs:162  AssignNullToNotNullAttribute  Possible 'null' assignment to non-nullable entity
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:170  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:170  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:170  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:170  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:133  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:148  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:163  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:177  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:194  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:208  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:230  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:252  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:282  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:303  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:358  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:375  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:393  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:420  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:450  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:480  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:524  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:550  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/PerformanceTests/AvatarOutlineFrustumHoistPerformanceTest.cs:153  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/MapRenderer/MapLayers/Atlas/SatelliteAtlas/SatelliteChunkController.cs:74  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:162  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/AvatarRendering/AvatarShape/Tests/PerformanceTests/AvatarOutlineFrustumHoistPerformanceTest.cs:211  CSharpWarnings::CS8603  Possible null reference return
Assets/DCL/MapRenderer/MapLayers/SearchResults/SearchResultMarker/SearchResultMarker.cs:25  CSharpWarnings::CS8603  Possible null reference return
Assets/DCL/Rendering/GPUInstanceBatcher/Tests/PerformanceTests/LODBufferAccumulationPerformanceTest.cs:86  CSharpWarnings::CS8603  Possible null reference return
Assets/DCL/Rendering/Reflections/Tests/PerformanceTests/ReflectionProbeRendererPerformanceTest.cs:157  CSharpWarnings::CS8603  Possible null reference return
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:35  CSharpWarnings::CS8618  Non-nullable field 'avatarBase' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:34  CSharpWarnings::CS8618  Non-nullable field 'avatarGameObject' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/AvatarShapeVisibilitySystemShould.cs:30  CSharpWarnings::CS8618  Non-nullable field 'cameraGameObject' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.

…and 166 more (see the csharp-lint-reports artifact).

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

badge

Lint in progress, come back later!

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STEP 1 — Context

PR: #9655chore: perf 2026 08 06
Branch: perf/hunt-2026-08-06dev
Author: eordano
Scope: 85 files, +3991 / −202 — a batch of ~20 targeted performance optimizations across avatar rendering, ECS infrastructure, GPU rendering, map renderer, landscape, web requests, and diagnostics. Each optimization ships with a dedicated performance/falsification test.

Subsystems touched: avatar GPU skinning pipeline (bone matrix job, compute skinning, texture arrays), ECS systems (partition, visibility, transforms, emotes), compute shaders (LOD buffer accumulation), GPU instancing (draw args), reflection probes, map renderer culling, landscape ground rendering, web request analytics (Sentry), download handlers, and debug UI bindings.


STEP 2 — Root-cause check

Every optimization addresses a genuine root cause identified via profiling:

  • Per-frame allocations replaced with persistent container reuse (ground rendering, download handler)
  • Redundant per-entity computation hoisted out of hot loops (frustum planes, camera fetch, emote tag polling)
  • Contended GPU atomics replaced with group-shared reduction (LOD accumulation shader)
  • Unconditional work gated on actual need (tangent recompute, Sentry transaction setup, visibility dirty scans)
  • Burst-jobified partition math replacing main-thread loops
  • Texture mip chains restored for correct GPU LOD filtering

Root-cause verdict: PASS — no symptom masking, no swallowed exceptions, no disabled checks.


STEP 3 — Design & integration

New systems: GltfContainerVisibilityEventSystem, PrimitivesVisibilityEventSystem

Owner search: The lifecycle these handle (one-shot create events produced by FinalizeGltfContainerLoadingSystem / InstantiatePrimitiveRenderingSystem) is already managed by the existing VisibilitySystemBase<T> hierarchy — specifically its eventsBuffer.ForEach(forEachEvent) call. The split moves the every-frame event drain into an un-throttled companion so the throttled parent system can skip idle-frame scans without dropping one-shot events that ClearEntityEventsSystem clears the same frame.

This is not a parallel lifecycle reconciler — it's a deliberate cadence split of an existing owner's responsibilities. The event buffer is the same instance passed to both systems via the plugin. The un-throttled system runs only ApplyNewlyCreatedRenderables(), the throttled system runs only UpdateDirtyDrivenVisibility() + HandleRemovedVisibilityComponents(). The base class default Update() still calls all three for unsplit consumers (text-shape, NFT-shape). PASS.

PartitionAssetEntitiesSystem staging buffers

The system adds persistent stagedPositions/stagedComponents managed arrays and entityPositions/rawIn/outBucket/outIsBehind/outRaw NativeArrays. These are per-frame scratch buffers reused across frames to avoid re-allocation — contents are filled and consumed within the same Update() call, with managed references nulled out after writeback. This falls under CLAUDE.md §1's exception for "temporary collections for per-frame aggregation." NativeArrays are properly disposed in OnDispose. PASS.

VisibilitySystemBase refactoring

The monolithic Update() is split into three protected methods (UpdateDirtyDrivenVisibility, ApplyNewlyCreatedRenderables, HandleRemovedVisibilityComponents). The base default Update() preserves the original call order for unsplit consumers. The split allows the throttled/un-throttled halves to call the appropriate subset. PASS.

Teardown trace

  • MainPlayerPipeline.perAvatarBoneCount → disposed in Dispose()
  • RemoteAvatarPipeline.perAvatarBoneCount → disposed in Dispose()
  • PartitionAssetEntitiesSystem NativeArrays → disposed in OnDispose()
  • RenderGroundSystem.instanceCounts/transforms → disposed in OnDispose()
  • PersistentElementBinding.OnValueChanged subscription → same lifetime as subscriber, no leak ✓

STEP 4 — Member audit

Member Consumers Verdict
ComputeShaderSkinning.MeshNeedsTangents 1 (SetupComputeShader) Encodes complex condition (facial feature + normal map check). Named by intent. ✓
ComputeShaderSkinning.TANGENT_SOURCE_TEXTURE_ID 1 (MeshNeedsTangents) Centralizes the texture ID constant. ✓
AvatarMaterialConfiguration.IsFacialFeature 1 (MeshNeedsTangents) "Single source of truth" for facial-feature detection, preventing callers from re-deriving the suffix check. ✓
VisibilitySystemBase.UpdateDirtyDrivenVisibility/ApplyNewlyCreatedRenderables/HandleRemovedVisibilityComponents 4+ (base Update, GltfContainerVisibilitySystem, PrimitivesVisibilitySystem, event systems) Proper split of responsibilities. ✓
SentryWebRequestSampler.IsWhitelisted 1 (SentryWebRequestHandler) Reuses existing matcher. Justified single-consumer for encapsulation. ✓
PersistentElementBinding.OnValueChanged 1 (ArtificialDelayOptions) General-purpose event enabling the caching pattern. ✓
AvatarShapeVisibilitySystem.CalculateFrustumPlanes 2 (Update, tests) Hoisted out of per-avatar loop. ✓, but see P2 below.
MapCullingController.EnqueueDirtyObject 2 (SetTrackedStateDirty, ResolveDirtyCameras) Extracted from SetTrackedStateDirty for reuse. ✓
PartitionAssetEntitiesSystem.ComputePartition 3 (PartitionJob, RePartition, tests) Shared pure math. ✓

STEP 5 — Line-level findings

See inline comments below.


STEP 6 — Complexity

COMPLEX — touches avatar GPU skinning pipeline, ECS systems (partition, visibility, transforms), compute shaders (LOD accumulation), GPU instancing, rendering (reflections), map renderer culling, and web request analytics across 15+ subsystems. Includes Burst-jobified code, HLSL compute shaders, and plugin registration changes.


STEP 7 — QA assessment

QA_REQUIRED: YES — changes affect runtime rendering (avatar skinning, visibility, ground, map, reflections), GPU instancing, and web request behavior in the Unity player.


STEP 8 — Non-blocking warnings

None. Main scene not modified.


Security review

No security issues found. All changes are client-side rendering/compute optimizations with no credential, auth, or input-validation surface.


Summary

This is an exceptionally well-crafted performance optimization batch. Each of the ~20 fixes:

  • Targets a root cause identified by profiling
  • Is behavior-preserving (verified by parity tests against the old code path)
  • Ships with a dedicated performance/falsification test that fails if the fix is reverted
  • Has thorough XML documentation explaining the rationale

Two minor P2 findings noted in inline comments — neither blocks merge.


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches avatar GPU skinning pipeline, ECS visibility/partition/transform systems, HLSL compute shaders, GPU instancing, reflection probes, map culling, and web request analytics across 15+ subsystems.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by Esteban Ordano (<@U9DLN0485>) via Slack

{
Camera cam = camera.GetCameraComponent(World).Camera;
CalculateFrustumPlanes(cam);
GetAvatarsVisibleWithOutlineQuery(World, cam);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] IsVisibleInCamera is a public method whose contract now silently depends on CalculateFrustumPlanes being called first. Without that call, the planes array contains stale data and the AABB test returns a wrong result. The XML doc on IsVisibleInCamera should document this precondition so future callers don't misuse it.

Suggested change
GetAvatarsVisibleWithOutlineQuery(World, cam);
public void CalculateFrustumPlanes(Camera camera) => GeometryUtility.CalculateFrustumPlanes(camera, planes);
/// <summary>
/// Tests whether the given bounds are inside the cached frustum planes.
/// <see cref="CalculateFrustumPlanes"/> must be called first in the same frame to populate the planes array.
/// </summary>

Comment on lines +167 to +172
FastPathSqrDistance = partitionSettings.FastPathSqrDistance,
SceneBucket = scenePartition.Bucket,
SceneIsBehind = scenePartition.IsBehind,
SqrDistanceBuckets = sqrBuckets,
EntityPositions = entityPositions,
RawIn = rawIn,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] ComputePartition is called from a [BurstCompile(FloatMode = FloatMode.Strict)] job. While Burst can compile it transitively, adding the [BurstCompile] attribute to the method itself makes the intent explicit and lets Burst apply its full optimization pipeline directly.

Suggested change
FastPathSqrDistance = partitionSettings.FastPathSqrDistance,
SceneBucket = scenePartition.Bucket,
SceneIsBehind = scenePartition.IsBehind,
SqrDistanceBuckets = sqrBuckets,
EntityPositions = entityPositions,
RawIn = rawIn,
/// <summary>
/// Pure, Burst-compatible partition math shared verbatim by the job and the managed
/// fallback. Mirrors the historic RePartition/ResolvePartitionFromDistance evaluation
/// order exactly (component-wise subtract, left-to-right square sum, int bucket compare,
/// dot-product behind test) so both paths agree bit-for-bit. On the fast path the raw
/// square distance is intentionally left untouched (passed through <paramref name="rawIn"/>),
/// matching the previous behaviour where far entities inherited the scene bucket without
/// rewriting RawSqrDistance.
/// </summary>
[BurstCompile]
public static void ComputePartition(

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

badge

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 24406 0 13
PlayMode ✅ Passed 298 0 5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

force-build Used to trigger a build on draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants