Repository navigation
Conversation
`CAP_GPUI_AUTO_TIMELINE_BENCH=1` times a synchronous `Window::draw` of the editor the way `camera_bench` does for the camera bubble, after the `CAP_GPUI_AUTO_EDITOR` project is open and its waveforms have landed. It reports mean / p50 / p95 / max draw time for five scenarios: playback at the default zoom, playback zoomed out to the whole clip, a paused hover sweep, a zoomed-in following playback, and a clean draw with nothing notified. The playing scenarios dirty the timeline through the production `invalidate_playback_chrome`. The draw-and-report helpers move out of `camera_bench` into `draw_bench` so both benches share them.
…ints The timeline repaints on every playhead tick during playback, and each repaint rebuilt the mic and system waveform fills with lyon even when their inputs had not changed. Zoomed out to a whole 180 s clip the two fills tessellate to about 34,000 vertices, which cost the editor around 16 ms per draw on `main`, a full 60 Hz frame budget. `waveform_path` now takes one `WaveformGeometry` value, which doubles as the key of a `WaveformPaths` cache on `TimelineModel`: one slot per clip segment and track, compared on every builder input with the peak table matched by `Arc` identity, swept each render so only last frame's visible paths are kept, and carried across `rebuild_timeline` like the waveforms. The paint closure clones the stored path on a hit; colour, opacity, mask and DPI scale are still applied by `paint_path` per paint. Once the view follow-scrolls, the range changes every tick and the cache misses, so scrolling playback costs what it did before. Timeline draw benchmark, 180 s clip, 5 interleaved runs, median p50: playing at the default zoom before the view follows 1.12 ms -> 0.70 ms, playing zoomed out 15.7 ms -> 0.80 ms, paused hover zoomed out 16.1 ms -> 0.96 ms, scrolling playback (every draw misses) 1.39 ms -> 1.36 ms.
… in place The Linux Instant camera compositor converted every screen frame to RGBA and back, and rebuilt the camera overlay and its mask, just to blend a small camera rectangle. Blend directly into packed RGB screen frames, rebuild the overlay only when a new camera frame arrives, and compute the mask once. The old conversion path remains the fallback for other formats.
…rame ReadSample hands back a null sample for stream ticks (gaps in the stream), and read_sample reported those as Ok(None), the same as the end of the stream. The decoder then stopped a batch early and answered with an older frame. Read again until a sample, the end-of-stream flag or an error.
…est state machine The decoder thread mixed batching, a mid-batch readahead slide, deferred requests and four fallback chains in one loop. The policy now lives in FrameRequests, which never touches Media Foundation: the thread takes in requests, asks it for the next step (seek, decode one sample, or wait) and does it. Tests drive it with a fake sample stream. Once the decoder moves past a requested frame, the request gets the newest sample at or below that frame. Requests are served lowest first, each with its own seek when decoding forward cannot reach it, so the "first sample after" fallback only applies to a frame before the stream's first sample. The cache keeps every sample since the last seek and evicts the oldest, so the newest sample at or below a frame is always cached. Behaviour changes: - Several requests in flight (export transitions between two segments of one recording decode both through the same decoder) each get their own frame. The highest used to be the target, and a lower one far from it got the target's frame. - A request behind the decoder with nothing cached at or below it seeks back. Within half the cache size it used to get the next decoded, later frame. - Every request slides the readahead, cache hits included, so a sequential reader no longer drains the cache while the decoder idles. - Readahead stops at its frame count, or before the next sample would evict the one answering the latest request (frames too big for the cache budget used to get a later frame); the per-batch time budget is gone. - Samples outside the old cache window are cached too, so a frame after a VFR gap past the window gets its own sample, not the one before the gap. - A failed seek answers what waits from the cache instead of reading on from the old position as if it had landed. Export output is byte-identical to the previous decoder on the reference recording.
The decoder thread copied each sample to a staging texture and mapped it straight away, so it sat blocked in Map while the GPU decoded and copied, and the GPU sat idle while the CPU copied the frame out. read_nv12 now queues the copy of the next sample before mapping the current one, through a ring of two staging textures created with the decoder. Samples are copied from Media Foundation's output surface directly into staging, skipping the pool texture that only the disabled zero-copy path needs. The rendering decoder caches the CPU NV12 data itself and drops the zero-copy fields its frames could never fill, along with the two zero-copy constructors that were their only producers.
… job An export that ffprobe cannot read, that has the wrong frame count, or that falls outside the requested size is now an invalid run instead of a timing sample. Benchmark processes start suspended and resume only once they are in the job object, so nothing they spawn escapes its CPU accounting, and a failed assignment kills the process. A note says why playback forces FFmpeg.
…up-on-events perf(recording): read the cursor image only on ticks that record an event
…ccluded-wake fix(gpui): don't draw a hidden window when it wakes from idle
…hile the pointer moves
… and gate the backup check to unix
…y registered images
…t-waits-for-work fix(desktop-gpui): hold the automatic update prompt until recording or export finishes
…window-frame fix(gpui): end a frame quietly when its window has already closed
…ized-studio-backup fix(recording): drop the original media backup once a plain Studio stop is published
…-install-shared-crate fix(desktop-gpui): install the CLI from Settings with the shared cli-install crate
… restarting the display link
…erprint perf(recording): compare the cursor bitmap before encoding its TIFF while the pointer moves
…single-frames perf(gpui): draw one frame per wake while a window is idle instead of restarting the display link
… message on status change
…, and surface unmapped rows
…cierge-migration-20260921 feat: add Cap Pro Loom concierge migration
…rs instead of React CapSoftware#441
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Streaming MP4 export mixes through
ExportAudioView. That type only implementedsample(), socap_audio::render_audioalways took the per-sample fallback forExportAudioSource::Streaming.VoiceSource::Originalalready forwardssample_slice, andVoiceAudio/AudioDataalready provide it.ExportAudioView::sample_slicereturns the buffered window shifted bysource_start * channels. A range that is not fully inside the window returnsNone, and mixing falls back tosample(). Same samples, same multiply-add, so the output is bit-identical.The first cut of the stereo slice path used
chunks_exact().zip()intomix_stereo. Under the shipping profile (opt-level = s, thin LTO,codegen-units = 1) that iterator did not inline, so each stereo frame paid a call and the mix was slower than indexing (~0.93×).mix_stereo_sliceis#[inline(always)]and walks the returned slice with a plain indexed loop.StereoModeis chosen once per block. Mono still uses the existing slice loop. Indexed fallback is unchanged.This pull request is against
kyzer1023/Cap:main. The available PR tool opens on this fork and could not targetCapSoftware/Cap:main. The branch is two commits on top of this fork'smain(fe43d283c):d6b1a8e66addssample_slice, and7beebecabinlines the stereo slice mix. It does not touch files in CapSoftware#2521 or CapSoftware#1786.Test
offset_view_slice_and_indexed_mixing_are_bit_identicalbuilds a view withsource_start = 1537and compares the slice path with an indexed source that leavessample_sliceasNone. Mono and stereo (includingMonoR), in-window blocks, ranges that start before the window, and a window shorter than the requested range all match onf32::to_bits. The in-window case asserts the slice path is actually taken.contiguous_and_indexed_mixing_preserve_sample_bits_and_output_boundsstill matches the previous expected bit pattern, including a stereoMonoRslice mix.cargo fmt --allandcargo clippy -p cap-audio --tests --no-deps -- -D warningsare clean.cargo test -p cap-audio --lib contiguous_and_indexed_mixing_preserve_sample_bits_and_output_boundsandcargo test -p cap-editor --lib offset_view_slice_and_indexed_mixing_are_bit_identicalpassed.Benchmark
rustc 1.88.0. One hour of audio at 48 kHz (172,800,000 frames) mixed in 4096-frame blocks,
source_start = 12_345, unity gain,StereoMode::Stereo. The indexed path is today's behavior (sample_slicereturnsNone). Checksums are of every output sample'sto_bits, computed before the timed passes. Both passes and both optimization levels produced the same checksums as each other and as the previous slice implementation, so the paths are bit-identical. The second pass is the warm headline.Workspace release profile (
opt-level = s, thin LTO,codegen-units = 1):0x97051bbf7d6bd2180x97051bbf7d6bd2180x9554694b9c8eec000x9554694b9c8eec00opt-level = 3(LTO off,codegen-units = 16), same machine and toolchain:0x97051bbf7d6bd2180x97051bbf7d6bd2180x9554694b9c8eec000x9554694b9c8eec00Under the shipping profile both layouts are faster than indexing (mono 1.26×, stereo 1.20× on the warm pass). At
opt-level = 3the same paths are 1.45× and 2.80×.