This document lists the runtime areas most likely to matter in the upcoming performance review. It is not a finding list yet; it is a map of where to measure and what behavior must be preserved.
DynamicDrawManager.DrawDynamicThings postfix:
- Calls
DotDrawer.DrawDots(map)during dynamic drawing. - Enumerates
map.mapPawns.AllPawnsSpawned. - Performs hidden/fog filtering, rule lookup, color lookup, material lookup, edge marker math, and mesh draw calls.
- This is the first place to profile on large colonies, animal-heavy maps, raids, and vehicle-heavy maps.
Pawn and label suppression prefixes:
PawnRenderer.RenderPawnAtVehicles.VehicleRenderer:RenderPawnAtSelectionDrawer.DrawSelectionBracketForPawnUIOverlay.DrawPawnGUIOverlaySilhouetteUtility.ShouldDrawSilhouetteGenMapUI.DrawPawnLabelGenMapUI.DrawThingLabel
These run as RimWorld tries to draw or label things. Keep prefix decisions cheap and avoid allocations.
Camera patches:
CameraDriver.UpdateCameraDriver.ApplyPositionToGameObjectCameraDriver.CurrentViewRectCameraDriver.CurrentZoom
These are lower volume than per-pawn rendering but central to user feel. Regressions show up immediately as zoom drift, clipping, culling, wrong mouse anchoring, or odd movement speed.
Settings and editor UI:
Dialog_Customization.DoWindowContents()clearsCaches.dotConfigCacheevery tick while the rule editor is open.- The editor performs per-row layout and color/mode UI work.
- This is less critical during gameplay, but it can matter for users with many custom rules.
FastUI is frame-scoped and avoids repeating RimWorld UI coordinate calls in the same frame.
MarkerDecisionCache is frame-scoped and stores one marker decision per pawn thingIDNumber. It shares the expensive decision work between the dynamic draw postfix and vanilla-rendering suppression prefixes.
Caches.dotConfigCache and Caches.shouldShowLabelCache are quota-based. Each cached entry is refreshed after 60 retrievals, not by tick or frame. Entries are mutable so repeated hits do not replace dictionary values just to increment the retrieval count. This can reduce repeated rule scans but can also keep stale rule decisions briefly after state changes.
MarkerCache holds per-pawn Unity materials and refreshes entries only when the marker mode, custom marker name, or outline factor no longer matches the current rule/settings state. It destroys old materials through MaterialAllocator.Destroy(). Better-silhouette textures are copied into cutout-mask textures before they are passed into the Camera+ bordered shader, because RimWorld's silhouette path relies on alpha cutout behavior that Camera+ otherwise loses when it reuses only material.mainTexture.
cachedMainColors stores sampled texture colors by pawn runtime type and body graphic path. This avoids repeated texture readback/downsampling after the first sample for a graphic.
cachedCameraDelegates stores reflection-discovered optional integration methods by pawn runtime type.
Likely candidates to verify with profiling:
- LINQ in
DotDrawer.DrawDots()and rule matching paths. DotConfig.conditions.All(...)for every uncached rule lookup.Tools.GetMainColor(), especially texture downsampling and pixel grouping on cache misses.MarkerCache.MaterialFor(), especially material creation/destruction cadence and cache key lifetime.CameraDelegatesreflection for new pawn runtime types.- Repeated
UI.MapToUIPosition()andUI.UIToMapPosition()calls in edge-marker calculations. - File watcher reload behavior for custom marker PNGs.
Verified during the 2026-05-15 prep pass:
- Removed LINQ allocation from
DotDrawer.DrawDots()by iteratingmap.mapPawns.AllPawnsSpawneddirectly. - Reused the already-fetched
DotConfigthrough marker color/material paths. - Changed quota-cache hits to mutate cache entries instead of replacing dictionary values every request.
- Cached per-material marker colors so
_FillColorand_OutlineColorare only set when the colors change. - Replaced per-edge UI conversion scale work with one per-frame clipped-marker map scale.
- Replaced normalized edge ray intersection with direct rectangle scale math.
- Preserved render state in
Tools.DownsampleTexture()by restoring the previous activeRenderTexture. - Converted raw RimWorld silhouette textures into cached cutout-mask textures before drawing them with the Camera+ bordered shader.
The primary 962-pawn scenario improved from 3629.618 us to 2363.312 us average DotDrawer.DrawDots time at the 600-draw snapshot. Keep both the performance CSV and the visual closeup screenshots when evaluating later refactors; the earlier broad color corruption and the later silhouette texture-frame artifact were real regressions.
Verified during the follow-up performance pass:
- Added
MarkerDecisionCacheso marker draw code and vanilla-suppression patches share the same per-frame decision result. - Deferred marker color and material lookup in
DotDrawer.DrawDots()until an edge marker or in-map marker is actually needed. - Replaced retrieval-count material refreshes with state-based invalidation for marker mode, custom marker name, and outline factor.
- Cleared marker materials when custom marker PNGs are reloaded.
- Added a
CAMERAPLUS_PERF-onlyPawnRenderer.DynamicDrawPhaseAtprefix that can measure the effect of skipping vanilla renderer phases for marker-replaced pawns.
The perf-gated run on CameraPlusPerf_962Pawns_EdgeDots reached the 600-draw snapshot with 962 visible pawns, 962 marker draws, and active edge dots. DotDrawer.DrawDots averaged 2342.038 us and DynamicDrawManager.DrawDynamicThings.Postfix averaged 2343.504 us in that snapshot. The production build still does not include the renderer-phase skip.
Do not break these behaviors while optimizing:
- Zoom-to-mouse should keep the map point under the cursor stable unless Shift is held or the setting is off.
- Effective zoom limits come from
zoomedInPercent,zoomedOutPercent, andexponentiality. - Marker display is controlled by the first matching
DotConfigrule. - Rule matching is an AND across all conditions in a rule.
- Mouse proximity can reveal labels and suppress markers.
- Edge indicators are separate from in-map markers.
- Dead pawn hiding affects body rendering and corpse forbidden overlays.
- Animal display must respect
LabelStyleandincludeNotTamedAnimals. skipCustomRenderingmust allow other mods to bypass CameraPlus custom drawing.- Custom marker PNG files in the CameraPlus save-data folder must be reloadable without restarting RimWorld.
- Save-specific marker rules live in
CameraSettings; default rules for new games live inCameraPlusDefaultRules.xml.
Use at least these scenarios before and after performance changes:
- Empty colony, default settings, close/middle/far/furthest zoom.
- Large late-game colony with many colonists, animals, mechs, corpses, and items.
- Raid/combat scene with drafted colonists, downed pawns, mental states, and many labels.
- Many custom marker rules, including text predicates for hediffs, apparel, inventory, equipment, weapon, pawn name, and faction name.
- Custom PNG marker modes.
- Long session with map changes to check material/cache cleanup.
- Optional compat pass with Vehicle Framework, Save Our Ship 2, and Dubs Performance Analyzer when available.
Minimum verification after source changes:
dotnet build Source/CameraPlus.csproj -c ReleaseRuntime verification should include:
- open game to main menu and confirm asset bundle load has no errors.
- load a save and test zoom-to-mouse, camera shake setting, zoom limits, and edge scrolling.
- check map markers and edge markers at several zoom levels.
- open marker-rule editor, change a rule, and verify it takes effect immediately.
- save/load a customization preset.
- save/load camera views with the configured shortcuts.