Skip to main content

Crate appcore_render

Crate appcore_render 

Source
Expand description

§appcore-render — E3DN

Release candidate appcore-render 1.0.0-rc.1. Select this RC explicitly; it does not replace earlier stable releases or certify a deployment. Internal AppCore dependencies, where present, are pinned to each dependency’s independent RC.

1.0.0-rc.1 is the current source release candidate. E3DN is standalone, with independent SemVer and no AppCore dependencies. Default features use only the standard library. Optional metal pins wgpu 26.0.1 (MIT/Apache-2.0, dependency MSRV 1.84); package MSRV is 1.89. The initial qualified graphics target is native macOS/Metal.

RenderLimits admits dimensions and managed payload before allocation; it excludes driver/OS overhead. ResourceArena uses owner-scoped generational IDs and rejects stale/foreign handles. Resource removal does not prove GPU completion. Offscreen RGBA8/Depth32Float capture is explicit diagnostic work, with a five-second timeout and row-padding removal; interactive rendering has no synchronous readback.

NativeRenderer borrows its surface owner, stays on the main thread and admits at most two pending frames through RAII permits transferred to completion callbacks. Zero dimensions suspend rendering; resize requires drained submissions. Surface loss requires explicit reconfiguration, device loss requires a new generation owned by deployment, and shutdown is pumped under an owner deadline. Platform acquisition/configuration may stall; input latency has no universal guarantee. The optional appcore-ui gpu-macos adapter preserves default features and UI V1.

Lorenz uses six GPU RK4 trajectories, dt=0.005, at most eight steps per submission and fixed 6×4096 trail samples. Native controls expose pause/resume, step, reset, orbit/tilt, zoom and isolation. The Metal test compares 16 steps with an f64 CPU reference (absolute tolerance 0.0002); long-term chaotic equality is not required. CPU indices advance only after submission; compute and drawing share frame admission.

The CPU scene has render-only and render+velocity archetypes with aligned contiguous columns, world-scoped generational IDs, migration/compaction repair and owned immutable snapshots. Snapshot payload/count and the 1–256 structural command queue are bounded. Commands return ordered nontransactional outcomes. Velocity stepping validates all candidates before commit. AssetRegistry resolves validated mesh/material identities; prepared frames retain immutable assets and their payload budget after registry retirement. Unsupported animation/morph selections are rejected.

GpuMeshBatch supports shared opaque static geometry, poses/colors, conservative GPU frustum selection and draw_indexed_indirect. GpuMeshScene composes up to 64 fixed batches and 65536 reserved instances: frustum prepass → fresh max-depth Hi-Z → conservative queries → compacted indices → final indirect draws. Prepass/final buffers and depth targets are distinct; the pyramid is rebuilt once per frame, with far padding for odd viewports. Camera/near-plane and viewport crossings remain visible. Groups match descriptor order and mesh identity; empty groups reset counts. Managed reservation includes both depth targets, all buffers and padded mips; standalone scene excludes caller-owned color. Native presentation admits that color too. Initial implementation duplicates shared geometry between prepass/final batches, counted in the budget.

Encode each visibility instance/batch/scene once between queue.submit calls, including when one submit contains multiple encoders; queued uniforms/resets otherwise alias. Lower-level GpuOcclusion requires matching current depth/camera/device provenance. GpuMeshScene owns this ordering; it uses no temporal history or inter-stage CPU readback. Callers observe completion before resizing/replacing resources. Lorenz and mesh scenes are mutually exclusive in this initial native profile.

Real Metal pixel tests verify depth/color, masked suppression/reappearance, multi-mesh composition, invalid groups, budget rejection and empty-frame clearing. Native multi-mesh qualification displayed 256 cubes + 64 pyramids, delivered ten consecutive physical orbit clicks, and exercised pause, zoom, orbit, reset, resume and zoom/restore resizing. Shutdown: frames=3102, actions=15, retained_windows=0. Earlier surface qualification covered six minimize/restore cycles, two injected surface losses, deliberate device destruction/loss callback and explicit recreation (202/213 frames, zero pending/retained windows). This is not a physical GPU reset. Fault hooks are opt-in qualification/gpu-qualification.

Stage 4 remains incomplete: visual/input qualification of native LOD, wider ECS/scene qualification and measured GPU render performance still need work. Hybrid SVO/BVH streaming and floating origin, meshlets/virtual textures, SPH/volumes/hybrid collision/GPU animation and scale benchmarks remain future stages. No 4K60 qualification or browser/backend parity is claimed.

Resize admission also includes simultaneously retained old and new depth/Hi-Z textures. Identical dimensions reuse attachments; insufficient peak budget is rejected before allocation and keeps the scene unchanged.

cargo test -p appcore-render
cargo run -p appcore-render --example basic
cargo run -p appcore-render --example intermediate
cargo run -p appcore-render --example scene
cargo run -p appcore-render --features metal --example offscreen
cargo test -p appcore-render --features metal -- --include-ignored
cargo run -p appcore-ui --features gpu-macos --example native_gpu
cargo run -p appcore-ui --features gpu-macos --example native_lorenz
cargo run -p appcore-ui --features gpu-macos --example native_meshes
cargo run -p appcore-ui --features gpu-qualification --example native_gpu_recovery

LorenzParameters exposes bounded sigma (1–20), rho (1–50), beta (0.5–6), rejecting nonfinite values. reset_with_parameters/configure_lorenz restart trajectories and trail history after completion; reset preserves coefficients. The fixed timestep remains 0.005; bounds do not guarantee universal numerical stability. Metal tests compare custom coefficients with an f64 reference and verify invalid input leaves state untouched. The native Params button cycles three bounded presets. Real-window qualification exercised 0→1→2→0, reset history in pause (zero samples), a single GPU step (48 samples), resume and close.

The optional appcore-ui feature native-input-timing adds local event timing without changing UiNativeActivation or UI V1. next_timed_activation returns callback-to-observation timing and, only inside correlated mouse/keyboard pump dispatch, event-to-observation timing. Uncorrelated accessibility callbacks remain None. Correlation is scoped and restored across nested dispatch/unwinding; invalid clocks do not produce a fabricated zero. Mouse-down timing includes hold/tracking until release. Raw uptime is never serialized or logged.

NativeRenderer.frame_progress reports generation-local submitted/completed frame IDs through queue callbacks; counters never wrap or regress. Completion is GPU queue work observed by polling, not compositor presentation or display scanout. The bounded native_meshes probe retains 256 samples/markers, reports overflow and outstanding markers, and correlates actions with the first subsequently submitted frame; intermediate states may coalesce. Presentation-call return and observed completion are separate endpoints. The optional timing profile uses systemUptime for local elapsed time; bundle examples/native-gpu/PrivacyInfo.xcprivacy (reason 35F9.1) with the application according to Apple’s API reasons.

Qualification: Metal, Apple M1, physical 1024×768, 320 instances/2 meshes, pump wait 8 ms. Ten coordinate clicks through native events and ten accessibility activations delivered 20 actions. Native-event p50/p95/p99: action 1.549/19.964/19.964 ms; present-call return 13.326/30.874/30.874 ms; observed GPU completion 23.511/42.509/42.509 ms (n=10). Mixed callback-to-action 2.289/103.954/104.105 ms (n=20); accessibility had no invented event timestamps (uncorrelated=10). All overflow/pending counters were zero; submitted=completed=3435, retained_windows=0. Small-sample results on this profile do not qualify 4K60 or universal input latency.

cargo run -p appcore-ui --features gpu-macos,native-input-timing --example native_meshes

The source LOD profile supports 1–8 fixed packed triangle ranges and 1–65536 objects. LodView describes a centered perspective camera, physical maximum focal length, viewport half-diagonal upper bound and pixel error budget. Bounds must enclose every level, and geometric_error is a producer-owned conservative deviation from finest geometry. Errors are finite/nondecreasing, finest error is zero, index spans do not overlap, and coarsest stays resident. Orthographic/shifted projection and asset simplification/streaming are not implemented by this selector.

choose_lod is an f64 CPU reference. GpuLodVisibility runs frustum/mask rejection, conservative projected-error selection and per-level compaction in compute. It uses nearest forward depth (including sphere radius/rounding guard), maximum scale, off-axis viewport extent and error-dependent denominator; clipping or near-camera bounds request finest. Missing desired detail selects the next resident coarser level and exposes degradation (choice bit 31); MAX means culled and must be checked first. Residency flags do not retire geometry. The caller retains packed buffers through completion and accounts their payload separately. Visible partitions have 256-byte alignment and each level has a 20-byte indexed-indirect record; selected indices consume no CPU readback in the draw path.

Real Metal tests compare choices/counts/compacted membership with the f64 reference, exercise missing/restored residency, masks, clipping, nonuniform scale, invalid-input preservation, empty reset and byte admission. A separate 512×512 pixel fixture packs a tessellated square, diamond and triangle (24/6/3 indices) into shared buffers and consumes GPU-generated draws in the same encoder. Colors identify levels. Removing/restoring the middle level changes green diamond → blue coarse triangle → green diamond; captured resident/degraded images were inspected. Native visual qualification, offline meshlet/LOD compilation and streaming remain pending.

cargo run -p appcore-render --example lod
cargo test -p appcore-render --features metal --test lod -- --include-ignored
cargo test -p appcore-render --features metal --test lod_draw -- --include-ignored

GpuLodMeshBatch now consumes resolved MeshLodDescriptor assets, packs all levels into shared vertex/index buffers and uses the production pose/color/normal-lighting pipeline. Frames must bind the finest mesh identity and remain opaque/static. A conservative origin-centered model sphere encloses every level and is transformed per instance. Reservation includes packed geometry, poses, camera and LOD selection partitions before upload; byte/device limits fail before allocation. Mesh payload owners remain retained after registry retirement. set_resident changes logical availability only; it does not free packed geometry. A real Metal 512×512 color/depth test verifies poses/material colors, disappearance/reappearance of a diamond-only pixel when middle detail is missing/restored, invalid identity/parameters, budget rejection and empty-frame clearing. This is the reusable production batch; native visual/input/resize qualification remains pending.

cargo test -p appcore-render --features metal --test lod_mesh_batch -- --include-ignored

GpuMeshScene::new_lod now composes fixed LOD batches through separate prepass/final selection buffers, a shared fresh Hi-Z pyramid and conservative all-level occlusion bounds. encode_lod takes LodSceneFrame with explicit matching matrix/view/groups; using the static encode method for an LOD scene is rejected before writes. Geometry remains duplicated per pass and included in admission. set_lod_resident updates availability consistently in both passes; no packed geometry is evicted. Static scene construction/encoding stays unchanged. NativeRenderer exposes enable_mesh_lods, present_mesh_lods and drained-frame availability changes under existing surface ownership, frame permits, resize peak admission and completion tracking.

native_lod adds 256 refined spheres/octahedron proxies and 64 boxes/tetrahedron proxies, orbit/zoom/pause, pixel budgets 4/12/32/96 and logical Fine on/off. A pure reducer rejects unknown commands; camera-to-projection alignment and bounded controls are tested. Real Metal 512×512 tests with two LOD groups verify detail loss/restoration, empty reset, admission and explicit scene mode. Native process ran to its 180-second deadline: frames=3239, submitted=completed=3239, retained_windows=0. This run had actions=0. Visual/input/resize qualification of this new example remains pending: native automation returned cgWindowNotFound for this app and the previously qualified control app. This does not qualify 4K60, streaming, meshlet compilation or memory eviction.

cargo run -p appcore-ui --features gpu-macos,native-input-timing --example native_lod
cargo test -p appcore-render --features metal --test lod_scene -- --include-ignored

Native shutdown now exposes abort_close for an error or an expired drain deadline. It closes and destroys that device generation without a GPU wait or synthetic frame-completion updates; repeated calls are harmless. Drop also destroys the device before the borrowed surface owner can be released, covering early error returns. Prefer begin_close/finish_close for normal draining. Device destruction is a backend call, not a hard wall-clock guarantee; abort does not prove pending frames executed or reached display. After abort, finish_close returns RendererClosed even if begin_close is called again; aborted work cannot be reported as a normal successful drain.

Lorenz resize admission now counts the retained old Depth32Float texture together with the new color/depth targets and fixed simulation/camera buffers before surface reconfiguration. Equal dimensions reuse depth storage. A peak-budget rejection keeps the previous scene and configuration usable; zero dimensions still suspend. Pure tests cover grow/shrink, exact byte limits and unchanged dimensions. Native recovery qualification additionally rejects a grow whose final allocation fits but replacement peak does not, then presents the preserved Lorenz scene.

SceneWorld now admits a new/reused identity slot in constant time through a preallocated bounded free list instead of scanning all slots. SceneLimits includes four additional payload bytes per identity slot. Reuse order is unspecified; generation exhaustion permanently retires a slot, and invalid/stale/foreign requests cannot add duplicate vacancies or consume one on a failed spawn. The deterministic churn test keeps an owned snapshot while migrating/removing/recreating 4096 entities, checks every live row against a reference, and verifies exact-budget admission and capacity refill. cargo run -p appcore-render --release --example scene_admission reports a bounded CPU-only profile (1 warmup + 5 samples for 1024/8192/32768 entities), excluding world/asset allocation, drop and GPU work. It does not qualify GPU frame time or 4K60.

The blocking diagnostic cargo run -p appcore-render --release --features metal --example gpu_scene_profile explicitly requires Metal timestamp queries. It profiles the static frustum → Hi-Z → indirect graph with 2048 instances/3072 input triangles in two batches, using an orthographic grid and an empty control at 512×512, 960×540 and 1920×1080. Two warmups precede seven raw CSV samples per case; each includes GPU interval, CPU encoding duration, final indirect counts and admitted payload. Render-pass end markers bracket the graph; the interval includes the ending 1×1 marker. Query resolution is encoded only after host-observed sampling completion; sampling/readback waits each have a five-second deadline. Zero/reversed timestamps or invalid counts fail the run, without filtering or CPU substitution. Uploads, queue delay, readback, display and driver overhead are excluded. This is serialized static offscreen diagnostics, not native input/resize, LOD or continuous-frame/4K60 qualification. The Metal counter lifecycle investigation is recorded in upstream issue 9414. No dependency patch or version change is required by this profile.

The diagnostic profile also accepts cargo run -p appcore-render --release --features metal --example gpu_scene_profile -- --lod. This explicit mode uses a matching 45-degree centered perspective matrix/LodView, near 0.1/far 100, a 32-pixel budget, two batches of 1024 instances and two packed levels (4096 finest/2048 coarsest input triangles). The 0.066 model-space error conservatively exceeds each model’s full bounding-box diagonal. Resident → fine_missing → restored → empty phases each have two warmups and seven measured samples at all three resolutions (84 measured intervals). The readback checks every level’s indexed-indirect record, per-batch capacity, both active levels, zero draws of missing fine geometry, restored counts and empty reset. Logical fallback can exceed the pixel budget and does not evict VRAM. Timestamp/readback/marker payload is 132 bytes in LOD mode (92 for static); color and complete scene reservation are admitted too. Raw output includes per-level counts; static and LOD scenes use different cameras/geometry and are not a speedup comparison. This remains serialized offscreen diagnostics; native LOD visual/input/resize qualification is pending.

The opt-in gpu-qualification feature adds UiNativeWindow::qualification_content_size(width, height), in logical points. It changes the real AppKit content size within the page’s original minimum/maximum dimensions and 4194304-point area ceiling, rejecting invalid sizes before mutation and closed windows as unavailable. It does not resize the renderer: the owner pumps, reads graphics_size in physical pixels and explicitly reconfigures after GPU completion. native_gpu_recovery now exercises nine real Lorenz window resizes, three Lorenz minimize/restore cycles, paused-history preservation, reset and a single step, with 21 submitted/completed frames and zero retained windows. Invalid size requests preserve physical size. This automated lifecycle qualification does not prove visual quality or physical input latency.

A separate real-Metal test now checks the actual 393216-byte GPU trail storage bit for bit across depth-target growth/shrink and paused drawing, and after rejected resize. A test-only storage-to-storage u32 copy preserves bits without adding COPY_SRC to production trail buffers. After resume it checks changed data, fills/wraps the six 4096-sample rings with at most two compute submissions before each drain, verifies finite samples and saturation/cursor state, then confirms the next step changes exactly one next-ring cell per trajectory. Readback is an explicit bounded test diagnostic, never an interactive frame operation. Run cargo test -p appcore-render --features metal --lib depth_resize_and_paused -- --include-ignored.

NativeRenderer::managed_gpu_bytes reports admitted color, depth and managed scene payload bytes at current dimensions. It is budget accounting, not measured VRAM; driver/compositor/extra swapchain allocations are excluded. Native UI examples expose this value alongside process RSS and camera input; the renderer itself owns neither OS input nor process memory sampling.

Structs§

AssetLimits
Bounded static asset/CPU preparation profile, separate from GPU/driver allocations.
AssetRegistry
Static mesh/material registry. IDs are typed by separate arenas and generations. Geometry is immutable; deleting a registry entry does not invalidate already prepared frames.
BoundingSphere
Conservative local sphere for visibility and spatial indexing.
EntityId
Scene-scoped generational identity, stable when a row moves between archetypes.
Frustum
Six normalized inward-facing planes using WebGPU’s 0..1 clip-depth convention.
LodChoice
Selected geometry and explicit degradation when the desired level is not resident.
LodLevel
Packed triangle range and conservative model-space geometric error, finest first.
LodObject
Per-instance conservative bounds and maximum absolute transform scale.
LodView
Explicit centered-perspective error budget; caller supplies matching frustum and camera provenance.
LorenzParameters
Bounded Lorenz coefficients for the fixed RK4 timestep 0.005.
LorenzView
Bounded orbit camera for the native Lorenz scene; angles are in radians.
MeshAsset
Immutable static triangle mesh. Cloned Arc ownership keeps retired payloads accounted.
MeshVertex
Validated static mesh vertex, with finite bounded position and unit normal.
MovingColumns
Disjoint mutable columns borrowed from the moving archetype; structural changes cannot occur while this value is borrowed. Component types preserve numeric validity.
PreparedInstance
Instance with resolved immutable asset ownership; no lookup or fallback is deferred to shaders.
PreparedScene
Owned resolved frame, retaining snapshot admission and CPU assets through registry retirement. GPU upload/retirement remains a separate submission-completion boundary.
RenderBinding
Application-selected generic asset identities and small instance appearance data. Asset lifetime/type validation belongs to the renderer’s asset registry before upload.
RenderLimits
Limits for managed resources; excludes driver and operating-system overhead.
ResourceArena
Bounded resource ownership. GPU users must remove only after last submission completes. This arena does not infer GPU completion or provide a submission timeline.
ResourceId
Opaque arena-scoped generational identity; carries no pointer or authority.
SceneCommands
Independent bounded structural queue. Application owns ordering and frame boundaries.
SceneInstance
Immutable extracted instance; does not borrow the world or resolve its assets.
SceneLimits
CPU ownership budgets. The byte ceiling accounts columns, slots, free indices, snapshots and step scratch, excluding allocator overhead and independently owned mesh/material assets.
SceneSnapshot
SceneWorld
Native two-archetype ECS: render-only and render+velocity. No external ECS or locks. IDs index a location table; row compaction always repairs the moved entity’s location.
Transform
Validated local GPU-space transform, aligned for contiguous SIMD-friendly columns.
Velocity
Validated bounded velocity component; no fluid/rigid-body solver is implied.

Enums§

RenderError
Controlled errors at rendering/resource admission boundaries.
SceneCommand
Deferred structural operations, applied only when mutable world ownership is available.

Functions§

choose_lod
f64 visibility/LOD reference using perspective screen-error bounds. Bounds must enclose every level. Clipped spheres request finest. Missing geometry chooses the next resident coarser level and reports degradation; culled returns None.