Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
771b86b
perf(renderer): add reproducible benchmark suite
LisaScheers Aug 26, 2026
cb37608
fix(perf): isolate macOS benchmark profiles
LisaScheers Aug 26, 2026
6f726d9
fix(perf): normalize macOS benchmark display scale
LisaScheers Aug 26, 2026
fa87595
perf(renderer): enforce controlled scene validity
LisaScheers Aug 26, 2026
99037cb
perf(renderer): record blocked Apple Silicon baseline
LisaScheers Aug 26, 2026
3670a76
fix(perf): preserve isolated warm cache
LisaScheers Aug 26, 2026
01d152c
fix(perf): attribute avatar readiness failures
LisaScheers Aug 26, 2026
91ef936
feat(render): define backend-neutral frame contract
LisaScheers Aug 26, 2026
6db53d4
feat(render): replay tonemap packets through OpenGL
LisaScheers Aug 26, 2026
1d6886f
feat(render): replay tonemap packets through Vulkan
LisaScheers Aug 26, 2026
e0bc0e9
feat(render): replay material packets through OpenGL
LisaScheers Aug 27, 2026
577308a
feat(render): replay material packets through Vulkan
LisaScheers Aug 27, 2026
bd24a2a
feat(render): replay texture uploads through OpenGL
LisaScheers Aug 27, 2026
4ac936c
feat(render): replay texture uploads through Vulkan
LisaScheers Aug 27, 2026
5559a42
feat(render): snapshot legacy material draws
LisaScheers Aug 27, 2026
b44fa92
feat(render): manifest material shader profiles
LisaScheers Aug 27, 2026
fdd3860
feat(render): prove production material shader
LisaScheers Aug 27, 2026
1e17328
feat(render): materialize production parameters
LisaScheers Aug 27, 2026
ad0aff6
feat(render): load verified material artifacts
LisaScheers Aug 27, 2026
3a19f0d
feat(render): publish material shader generations
LisaScheers Aug 27, 2026
fc40661
feat(render): own Vulkan material shader modules
LisaScheers Aug 27, 2026
d2d4f0e
feat(render): own Vulkan material layouts
LisaScheers Aug 27, 2026
5058db0
feat(render): own Vulkan material descriptors
LisaScheers Aug 27, 2026
bc5af26
feat(render): resolve Vulkan material attachments
LisaScheers Aug 27, 2026
bf0382a
feat(render): own Vulkan material render pass
LisaScheers Aug 27, 2026
731d46e
fix(render): require Vulkan independent blend
LisaScheers Aug 27, 2026
8ffed43
feat(render): resolve Vulkan material capability
LisaScheers Aug 27, 2026
8dc04bb
feat(render): own cold Vulkan pipeline cache
LisaScheers Aug 27, 2026
3f9138e
feat(render): resolve Vulkan global dispatch
LisaScheers Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 296 additions & 0 deletions doc/performance/renderer-benchmark.md

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions doc/performance/renderer-stage-1-decision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Stage 1 renderer benchmark decision

## Decision

Do not begin a Vulkan implementation or select an OpenGL optimization from the measurements collected in Stage 1. The benchmark suite is ready, but the available live-world workload was not controlled well enough to diagnose the reported newer-hardware regression.

Stage 2 will establish a deterministic workload on affected newer hardware, verify the context the driver actually creates, and capture external CPU/GPU traces. It will not add Vulkan. A current-OpenGL fix or a renderer-interface spike becomes eligible only after that evidence identifies the limiting path.

## What Stage 1 delivered

- Compile-gated CPU phase timers and renderer resource counters. Ordinary builds define `LL_RENDER_BENCHMARK=0` and compile the instrumentation out.
- A benchmark-safe LLLeap event that exports selected renderer, build, settings, and hardware context without exporting the normal profile or location context.
- Versioned manifest and result schemas, six workload manifests, a synthetic fixture, an isolated-profile runner, and a guarded reporter.
- Native OpenGL and Mesa Zink backend detection, including a check against the renderer string rather than trusting the requested backend.
- A 30-second warm-up, 120-second capture, five-repeat protocol. Warm-cache sequences use an unmeasured prime and one shared isolated cache; cold-cache repeats receive separate state.
- Comparison and privacy tests that reject incomplete results, unlike configurations, too few repeats, and identifying keys.

The steady loop adds no GPU query reads. GPU pass timing remains an explicitly separate RenderDoc, Radeon GPU Profiler, Nsight Graphics, Tracy, or equivalent capture.

## Reference host and coverage

The suite was built and launched on Linux with an AMD Ryzen 7 5800X, an AMD Radeon RX 480, Mesa 26.2.1, and the RADV Vulkan driver. Native OpenGL and Zink both selected the physical RX 480. This is an older reference machine, not the reported newer-hardware class.

The following required matrix cells were not available and no conclusion is drawn for them:

- affected newer GPU and a matched older control;
- Windows on current NVIDIA, AMD, and Intel drivers;
- Linux on current Intel and NVIDIA drivers;
- macOS on Apple Silicon and a still-supported Intel baseline;
- a second machine or driver from any class showing a threshold-crossing result.

The local build disabled media plugins because their development dependencies were not present. A GCC 15 false positive in an untouched source file required a generated-build-only warning suppression. Neither deviation is part of the commit.

## Diagnostic matrix

Five native OpenGL and five Zink captures completed end to end. They are invalid as steady warm-cache performance evidence: the viewer did not honor the requested destination, first-use UI opened over the scene, assets were still arriving, and each initial repeat had a fresh profile and cache. The runner now performs an unmeasured prime and reuses warm state, but the live location and first-use UI still cannot be gated automatically. Raw captures and screenshots are intentionally not checked in because they include live-world details.

The invalid captures remain useful as a harness and noise diagnostic:

| Backend | Median run p50 | Median run p95 | Median run p99 | Run-p95 range |
| --- | ---: | ---: | ---: | ---: |
| Native OpenGL | 5.155 ms | 8.634 ms | 13.266 ms | 4.21 ms |
| Zink | 5.399 ms | 7.364 ms | 11.071 ms | 2.70 ms |

Zink's median p95 was 1.27 ms lower. The predeclared threshold for this matrix is the larger of 1 ms and three times the largest run-p95 range: 12.63 ms. The observed delta is below that threshold and cannot support a backend claim even if the scene had been valid.

The largest median-of-run p95 CPU phases were:

| Phase | Native OpenGL | Zink |
| --- | ---: | ---: |
| Unclassified | 3.180 ms | 2.738 ms |
| Swap | 0.319 ms | 1.602 ms |
| Idle | 1.355 ms | 1.256 ms |
| GL submission | 1.275 ms | 0.827 ms |
| State sort | 0.870 ms | 0.541 ms |
| Texture work | 0.553 ms | 0.445 ms |

Texture uploads varied too widely to treat the runs as the same workload. Native upload bytes ranged from roughly 1.0 MB to 521.7 MB; Zink ranged from roughly 1.0 MB to 360.7 MB. No explicit texture readbacks, synchronization events, or shader compilations occurred during the capture windows. External GPU pass timing and per-core traces were not collected because the workload had already failed its validity checks.

The initial artifacts labeled the profile from the requested core-profile setting. The advertised OpenGL version was actually a Mesa 4.6 compatibility profile. The exporter now derives `opengl_profile` from the advertised version and records the requested setting separately; a short post-fix launch verified that distinction. Results collected before that fix are not comparison inputs.

## What the evidence supports

- The build option, viewer export, isolated runner, schemas, backend verification, reporter, and native/Zink launch path work end to end on the Linux reference host.
- Live-world asset and UI variability is larger than the backend delta seen here.
- Zink is viable as a Linux diagnostic backend on this host, but this run says nothing about Windows, macOS, or affected newer hardware.
- The actual versus requested OpenGL profile must be treated as separate data. Context negotiation is now a specific investigation target.

## What the evidence does not support

- that OpenGL compatibility behavior causes the reported regression;
- that Zink or native Vulkan would improve it;
- that scene preparation, GL submission, present, or a GPU pass is the primary bottleneck;
- a visual-equivalence claim between native OpenGL and Zink;
- any cross-platform or newer-hardware performance conclusion.

No current-OpenGL fix is justified yet. Candidate fixes must come from a trace and may include eliminating an identified driver wait, reducing state or draw submission, correcting context selection, or reducing resource churn. They are hypotheses, not Stage 1 findings.

## Stage 2 selection

Stage 2 is **capture the affected-hardware regression**. Its commit will add the smallest deterministic workload and validity gates needed to obtain five comparable runs on an affected current system and an older control. It will also capture actual context negotiation, per-core CPU behavior, and one external GPU trace per important configuration.

The decision gate at the end of Stage 2 is:

- fix the current OpenGL path first when the controlled trace identifies a bounded scene-preparation, submission, synchronization, present, or GPU-pass bottleneck;
- proceed to an explicit dual-backend contract only when the measured problem is inseparable from the current API boundary or when a separately funded platform-longevity requirement justifies it;
- do not use GL-on-Vulkan as a production compatibility layer unless matched visual, feature, stability, and frame-time evidence shows that it is a supportable deployment path.

## Verification

- Benchmark-enabled Linux Release build linked and launched.
- Benchmark-disabled touched objects compiled, confirming the default-off path.
- Six manifests and the synthetic result validate against their JSON schemas.
- Eighteen runner and reporter unit tests pass.
- Native OpenGL and Zink selected the expected physical GPU and completed capture/export.
- Dry-run coverage verifies that credentials are neither opened nor printed.
- A native visual launch rendered the world without gross corruption; deterministic screenshot parity remains a Stage 2 gate.

Stage 1 changes no production service, live database, normal viewer profile, shader, renderer output, or Vulkan dependency.
146 changes: 146 additions & 0 deletions doc/performance/renderer-stage-10-decision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Stage 10 OpenGL tonemap replay decision

## Decision

Accept the tonemap packet and narrow OpenGL executor as master Stage 1B. The
same immutable packet now validates independently and drives one real viewer
pass through the existing OpenGL objects. The legacy implementation remains
the production default, and a rejected packet falls back before the destination
is bound or modified.

This does not complete master Stage 1. There is no Vulkan executor, dual-API
comparison, material replay, streaming-upload replay, or performance result.
The live-world baseline remains paused after zero valid measured repeats.

## Frozen trace

Tracing corrected and completed the earlier representative fixture:

- the four-word parameter block is `{ exposure, tonemap mix, tonemap type,
gamma }`; gamma was previously a hidden environment-fed shader input;
- all six compiled variants are represented by stable bits for `NO_POST`,
`GAMMA_CORRECT`, and `LEGACY_GAMMA`, including the currently unreachable
legacy-gamma/non-no-post combination;
- the destination may be RGBA8 or RGBA16F and its extent is dynamic;
- the scene and exposure render-target textures retain mirrored addressing,
with point and linear filtering respectively;
- disabled depth retains the legacy `LEQUAL` comparison; and
- the real screen triangle remains three `Float3` positions in a 48-byte
buffer with a 16-byte stride.

The pure builder owns a copy of the parameter bytes and rejects invalid
variants, formats, extents, handles, exposure ranges, and non-finite values.
The decoder accepts only the canonical packet shape rather than treating any
otherwise valid frame as a tonemap request.

## Boundary and fallback

`lltonemapcontract` remains part of the independently linked, GL-free contract
target. It contains no viewer settings, sky state, render-target pointers,
shader objects, GL types, or API calls.

The OpenGL registry borrows existing `LLRenderTarget`, `LLVertexBuffer`, and
`LLGLSLShader` objects for one synchronous replay. Resolution requires the
exact typed handle index and generation, and pipeline resolution also requires
the exact program name and variant. These handles are lexical replay handles;
they are not yet persistent ownership records across reallocations or shader
reloads.

The executor decodes the complete packet and resolves every object, format,
extent, sampler, shader feature, permutation, texture channel, and required
uniform before binding the destination. It then applies the packet's color,
blend, cull, depth, viewport, scissor, sampler, parameter, geometry, and draw
state through the existing wrappers. The developer setting
`RenderUseTonemapContract` is non-persistent and defaults to false. Failure in
the builder, registry, or executor takes the independent legacy path once.

## Fixed-input parity

The account-free `--tonemapparity` startup mode runs after GL and shader
initialization and exits before login. It creates an 8 by 8 HDR scene, a 1 by 1
exposure image, and separate legacy and contract destinations. Before each
path, it poisons shader uniforms, texture bindings, vertex-buffer binding, and
relevant GL state so equality cannot come from cached state inherited from the
other path. Before contract execution it also poisons the actual sampler
objects and blend, cull, scissor, and color-mask state.

The reference side calls the same direct submission helper used by the
production legacy path. Before every case, the two destinations receive
different case-specific sentinels, and both draws are bracketed by explicit GL
error checks. A missing draw therefore cannot pass by reusing an old output or
by making both paths fail in the same way.

The matrix covers both output formats, all six compiled variants, and both
supported tonemap types: 24 cases. Every RGBA component is compared after
readback with a declared tolerance of zero. A separate stale-generation case
requires preflight rejection and proves that an RGBA8 destination sentinel is
unchanged.

On the Apple OpenGL 4.1 context, all 24 cases matched exactly:

```text
TONEMAP_CONTRACT_PARITY result=pass cases=24 tolerance=0 max_abs_error=0 mismatches=0 execution_failures=0 rejection_failures=0
```

This is correctness evidence for one OpenGL pass. It is not a performance
measurement and says nothing about Vulkan speed.

## Measured surface

| Surface | Size |
| --- | ---: |
| Pure tonemap contract header and implementation | 386 lines |
| OpenGL registry and executor | 395 lines |
| Focused registry tests | 80 lines |
| Existing contract fixture changes | 87 additions, 41 removals |
| Render-target and build wiring | 16 additions, 1 removal |
| Viewer integration, parity harness, and Mac isolation | 536 additions, 45 removals |

The 395-line API adapter is deliberately pass-specific. Material, upload,
presentation, window, and general-purpose command-encoder behavior did not
enter it.

## Verification

- Linux passed 18 contract cases and 2 registry cases.
- macOS passed the same 18 and 2 focused cases on the host architecture.
- The account-free native OpenGL parity mode passed all 24 exact comparisons
and the stale-generation non-mutation check.
- macOS rejected parity startup without `SECONDLIFE_USER_DIR` before Cocoa or
viewer construction. The XIB now starts the main window hidden; ordinary
launches order it forward after context creation, while parity keeps it
hidden and still creates the required GL context.
- The benchmark-enabled universal Release viewer built after the final changes
and contains both `x86_64` and `arm64` executable slices.
- All 57 Python benchmark tests passed. Draft 2020-12 validation accepted both
schemas, all six manifests, and the schema-3 fixture.
- GCC 15 accepted the pure builder and decoder as C++20 with `-Wall -Wextra
-Wpedantic -Werror`.
- Whitespace validation passed, and adversarial review found no remaining
correctness blocker in the executor or parity boundary.

The parity launch used a disposable profile and no credentials. Its temporary
profile, cache, logs, build environment, and incidental startup output were
removed. No benchmark result or timing was retained.

## Reanalysis

The next dependency is no longer another OpenGL abstraction. The tonemap
packet has a reference output and a proven executor boundary, so master Stage
1 now needs the same packet consumed by an isolated Vulkan process.

Repository inspection found no Vulkan headers, loader target, MoltenVK package,
SPIR-V compiler, shader manifest, or Vulkan implementation. Existing GLSL is
compiled and reflected at runtime through OpenGL-specific shader management.
The next commit therefore has to establish a portable Vulkan toolchain and an
offscreen executor together, while keeping platform surfaces and GL/Vulkan
interop out of scope. A dependency-only commit would not test the contract; a
windowed or whole-frame backend would jump ahead of the master plan.

Stage 11 should end with the canonical tonemap packet replayed into offscreen
Vulkan images from the same fixed CPU fixture, validation enabled, deterministic
readback compared with the Stage 10 reference, and an explicit portability
result on Linux and MoltenVK-capable macOS. If shader translation or platform
availability prevents that bounded replay, the stage must commit only an
evidence-backed stop decision and remove unused scaffolding rather than widen
into presentation or production renderer work.
Loading
Loading