1111//! Metal-compatible IOSurface-backed BGRA buffer from a fixed ring, ready for
1212//! `paint_surface` with zero further CPU touches.
1313//!
14- //! Run: cargo run -p cap-desktop-gpui --example camera-preview-convert-benchmark --release
14+ //! `--bubble` instead measures the preview's conversion at full camera size
15+ //! (what `main` converts every frame to) against the bubble-sized cover
16+ //! target, for small/medium/large bubbles on a 2x display, mirrored and not:
17+ //! per-frame time, bytes written per frame, the ring's resident memory, and
18+ //! how close each looks once gpui's linear sampler draws it at bubble size.
19+ //!
20+ //! Run: cargo run -p cap-desktop-gpui --example camera-preview-convert-benchmark --release [-- --bubble]
1521
1622#[ cfg( not( target_os = "macos" ) ) ]
1723fn main ( ) {
@@ -20,7 +26,11 @@ fn main() {
2026
2127#[ cfg( target_os = "macos" ) ]
2228fn main ( ) {
23- macos:: run ( ) ;
29+ if std:: env:: args ( ) . any ( |arg| arg == "--bubble" ) {
30+ macos:: bubble ( ) ;
31+ } else {
32+ macos:: run ( ) ;
33+ }
2434}
2535
2636#[ cfg( target_os = "macos" ) ]
@@ -249,4 +259,284 @@ mod macos {
249259 ) ;
250260 }
251261 }
262+
263+ type CreateRotationSession = unsafe extern "C-unwind" fn (
264+ Option < & cf:: Allocator > ,
265+ * mut Option < arc:: R < vt:: PixelRotationSession > > ,
266+ ) -> cidre:: os:: Status ;
267+ type RotateImage = unsafe extern "C-unwind" fn (
268+ & vt:: PixelRotationSession ,
269+ & cv:: PixelBuf ,
270+ & mut cv:: PixelBuf ,
271+ ) -> cidre:: os:: Status ;
272+
273+ struct FlipSession {
274+ session : arc:: R < vt:: PixelRotationSession > ,
275+ rotate_image : RotateImage ,
276+ }
277+
278+ impl FlipSession {
279+ fn new ( ) -> Self {
280+ let create = unsafe {
281+ libc:: dlsym ( libc:: RTLD_DEFAULT , c"VTPixelRotationSessionCreate" . as_ptr ( ) )
282+ } ;
283+ let rotate = unsafe {
284+ libc:: dlsym (
285+ libc:: RTLD_DEFAULT ,
286+ c"VTPixelRotationSessionRotateImage" . as_ptr ( ) ,
287+ )
288+ } ;
289+ let key = unsafe {
290+ libc:: dlsym (
291+ libc:: RTLD_DEFAULT ,
292+ c"kVTPixelRotationPropertyKey_FlipHorizontalOrientation" . as_ptr ( ) ,
293+ )
294+ } ;
295+ assert ! ( !create. is_null( ) && !rotate. is_null( ) && !key. is_null( ) ) ;
296+ let create =
297+ unsafe { std:: mem:: transmute :: < * mut libc:: c_void , CreateRotationSession > ( create) } ;
298+ let rotate_image =
299+ unsafe { std:: mem:: transmute :: < * mut libc:: c_void , RotateImage > ( rotate) } ;
300+ let key = unsafe { key. cast :: < * const cf:: String > ( ) . read ( ) . as_ref ( ) }
301+ . expect ( "flip property key" ) ;
302+ let mut session = None ;
303+ unsafe { create ( None , & mut session) . result ( ) } . expect ( "rotation session" ) ;
304+ let mut session = session. expect ( "rotation session" ) ;
305+ session
306+ . set_prop ( key, Some ( cf:: Boolean :: value_true ( ) . as_ref ( ) ) )
307+ . expect ( "flip property" ) ;
308+ Self {
309+ session,
310+ rotate_image,
311+ }
312+ }
313+
314+ fn rotate ( & self , source : & cv:: PixelBuf , destination : & mut cv:: PixelBuf ) {
315+ unsafe { ( self . rotate_image ) ( & self . session , source, destination) . result ( ) }
316+ . expect ( "flip" ) ;
317+ }
318+ }
319+
320+ const MAIN_RING : usize = 4 ;
321+ const PREVIEW_RING : usize = 6 ;
322+ const QUALITY_SEED : u8 = 200 ;
323+
324+ struct Case {
325+ dims : ( usize , usize ) ,
326+ stats : ( f64 , f64 , f64 ) ,
327+ buffer_bytes : usize ,
328+ output : Vec < u8 > ,
329+ }
330+
331+ fn bgra_ring ( count : usize , ( width, height) : ( usize , usize ) ) -> Vec < arc:: R < cv:: PixelBuf > > {
332+ let attrs = iosurface_bgra_attrs ( ) ;
333+ ( 0 ..count)
334+ . map ( |_| {
335+ let buf = cv:: PixelBuf :: new ( width, height, cv:: PixelFormat :: _32_BGRA, Some ( & attrs) )
336+ . expect ( "IOSurface BGRA destination" ) ;
337+ assert ! ( buf. io_surf( ) . is_some( ) ) ;
338+ buf
339+ } )
340+ . collect ( )
341+ }
342+
343+ fn run_case (
344+ sources : & [ arc:: R < cv:: PixelBuf > ] ,
345+ quality_source : & cv:: PixelBuf ,
346+ dims : ( usize , usize ) ,
347+ mirrored : bool ,
348+ ) -> Case {
349+ let mut session = vt:: PixelTransferSession :: new ( ) . expect ( "VT session" ) ;
350+ session. set_realtime ( true ) . expect ( "VT realtime" ) ;
351+ let flip = mirrored. then ( FlipSession :: new) ;
352+ let mut ring = bgra_ring ( PREVIEW_RING , dims) ;
353+ let scratch = flip. as_ref ( ) . map ( |_| bgra_ring ( 1 , dims) . remove ( 0 ) ) ;
354+ let mut stats = Stats :: new ( ) ;
355+ let mut next = 0 ;
356+ for iteration in 0 ..( WARMUP + ITERATIONS ) {
357+ let source = & sources[ iteration % sources. len ( ) ] ;
358+ let out = & mut ring[ next] ;
359+ let started = Instant :: now ( ) ;
360+ match ( & flip, & scratch) {
361+ ( Some ( flip) , Some ( scratch) ) => {
362+ session. transfer ( source, scratch) . expect ( "transfer" ) ;
363+ flip. rotate ( scratch, out) ;
364+ }
365+ _ => session. transfer ( source, out) . expect ( "transfer" ) ,
366+ }
367+ let elapsed = started. elapsed ( ) . as_secs_f64 ( ) * 1e6 ;
368+ next = ( next + 1 ) % PREVIEW_RING ;
369+ if iteration >= WARMUP {
370+ stats. push ( elapsed) ;
371+ }
372+ }
373+ session
374+ . transfer ( quality_source, & ring[ 0 ] )
375+ . expect ( "quality transfer" ) ;
376+ let buffer_bytes = {
377+ let ptr = ring[ 0 ] . as_ref ( ) as * const cv:: PixelBuf ;
378+ unsafe { CVPixelBufferGetBytesPerRow ( ptr) * dims. 1 }
379+ } ;
380+ let output = read_bgra ( & mut ring[ 0 ] , dims. 0 , dims. 1 ) ;
381+ Case {
382+ dims,
383+ stats : stats. report ( ) ,
384+ buffer_bytes,
385+ output,
386+ }
387+ }
388+
389+ fn make_quality_source ( width : usize , height : usize ) -> arc:: R < cv:: PixelBuf > {
390+ let mut buf = make_420v_source ( width, height, QUALITY_SEED ) ;
391+ unsafe {
392+ buf. lock_base_addr ( cv:: pixel_buffer:: LockFlags :: DEFAULT )
393+ . result ( )
394+ . expect ( "lock 420v source" ) ;
395+ let y_stride = buf. plane_bytes_per_row ( 0 ) ;
396+ let y_base = buf. plane_base_address ( 0 ) . cast_mut ( ) ;
397+ for row in 0 ..height {
398+ for col in 0 ..width {
399+ let x = col as f64 / width as f64 ;
400+ let y = row as f64 / height as f64 ;
401+ let smooth = 0.5 + 0.25 * ( x * 23.0 ) . sin ( ) * ( y * 17.0 ) . cos ( ) ;
402+ let detail = 0.15 * ( ( x * 640.0 ) . sin ( ) * ( y * 360.0 ) . sin ( ) ) ;
403+ let edge = if ( ( col * 8 / width) + ( row * 6 / height) ) % 2 == 0 {
404+ 0.1
405+ } else {
406+ -0.1
407+ } ;
408+ let value = ( smooth + detail + edge) . clamp ( 0.0 , 1.0 ) ;
409+ * y_base. add ( row * y_stride + col) = ( 16.0 + value * 219.0 ) as u8 ;
410+ }
411+ }
412+ buf. unlock_lock_base_addr ( cv:: pixel_buffer:: LockFlags :: DEFAULT ) ;
413+ }
414+ buf
415+ }
416+
417+ fn area_average ( pixels : & [ u8 ] , src : ( usize , usize ) , dst : ( usize , usize ) ) -> Vec < f64 > {
418+ let mut out = vec ! [ 0.0 ; dst. 0 * dst. 1 * 4 ] ;
419+ for y in 0 ..dst. 1 {
420+ let y0 = y * src. 1 / dst. 1 ;
421+ let y1 = ( ( y + 1 ) * src. 1 / dst. 1 ) . max ( y0 + 1 ) ;
422+ for x in 0 ..dst. 0 {
423+ let x0 = x * src. 0 / dst. 0 ;
424+ let x1 = ( ( x + 1 ) * src. 0 / dst. 0 ) . max ( x0 + 1 ) ;
425+ let mut sum = [ 0.0 ; 4 ] ;
426+ for sy in y0..y1 {
427+ for sx in x0..x1 {
428+ let offset = ( sy * src. 0 + sx) * 4 ;
429+ for ( channel, total) in sum. iter_mut ( ) . enumerate ( ) {
430+ * total += pixels[ offset + channel] as f64 ;
431+ }
432+ }
433+ }
434+ let count = ( ( y1 - y0) * ( x1 - x0) ) as f64 ;
435+ for ( channel, total) in sum. iter ( ) . enumerate ( ) {
436+ out[ ( y * dst. 0 + x) * 4 + channel] = total / count;
437+ }
438+ }
439+ }
440+ out
441+ }
442+
443+ fn linear_sample ( pixels : & [ u8 ] , src : ( usize , usize ) , dst : ( usize , usize ) ) -> Vec < f64 > {
444+ let mut out = vec ! [ 0.0 ; dst. 0 * dst. 1 * 4 ] ;
445+ let texel =
446+ |x : usize , y : usize , channel : usize | pixels[ ( y * src. 0 + x) * 4 + channel] as f64 ;
447+ for y in 0 ..dst. 1 {
448+ let v = ( ( y as f64 + 0.5 ) * src. 1 as f64 / dst. 1 as f64 - 0.5 )
449+ . clamp ( 0.0 , ( src. 1 - 1 ) as f64 ) ;
450+ let ( y0, fy) = ( v. floor ( ) as usize , v. fract ( ) ) ;
451+ let y1 = ( y0 + 1 ) . min ( src. 1 - 1 ) ;
452+ for x in 0 ..dst. 0 {
453+ let u = ( ( x as f64 + 0.5 ) * src. 0 as f64 / dst. 0 as f64 - 0.5 )
454+ . clamp ( 0.0 , ( src. 0 - 1 ) as f64 ) ;
455+ let ( x0, fx) = ( u. floor ( ) as usize , u. fract ( ) ) ;
456+ let x1 = ( x0 + 1 ) . min ( src. 0 - 1 ) ;
457+ for channel in 0 ..4 {
458+ let top = texel ( x0, y0, channel) * ( 1.0 - fx) + texel ( x1, y0, channel) * fx;
459+ let bottom = texel ( x0, y1, channel) * ( 1.0 - fx) + texel ( x1, y1, channel) * fx;
460+ out[ ( y * dst. 0 + x) * 4 + channel] = top * ( 1.0 - fy) + bottom * fy;
461+ }
462+ }
463+ }
464+ out
465+ }
466+
467+ fn psnr ( reference : & [ f64 ] , candidate : & [ f64 ] ) -> f64 {
468+ let mut squared = 0.0 ;
469+ let mut count = 0usize ;
470+ for ( pixel, other) in reference. chunks_exact ( 4 ) . zip ( candidate. chunks_exact ( 4 ) ) {
471+ for ( a, b) in pixel[ ..3 ] . iter ( ) . zip ( & other[ ..3 ] ) {
472+ squared += ( a - b) . powi ( 2 ) ;
473+ count += 1 ;
474+ }
475+ }
476+ let mse = squared / count as f64 ;
477+ if mse == 0.0 {
478+ return f64:: INFINITY ;
479+ }
480+ 10.0 * ( 255.0f64 . powi ( 2 ) / mse) . log10 ( )
481+ }
482+
483+ fn mb ( bytes : usize ) -> f64 {
484+ bytes as f64 / 1_048_576.0
485+ }
486+
487+ pub fn bubble ( ) {
488+ let bubbles = [
489+ ( "small 230pt round" , ( 460.0f32 , 460.0f32 ) ) ,
490+ ( "medium 400pt round" , ( 800.0 , 800.0 ) ) ,
491+ ( "large 600pt full" , ( 600.0 * 16.0 / 9.0 * 2.0 , 1200.0 ) ) ,
492+ ] ;
493+ println ! (
494+ "| source | bubble | mirrored | path | output | mean us | p50 us | p95 us | MB written/frame | ring MB | PSNR at bubble size dB |"
495+ ) ;
496+ println ! ( "|---|---|---|---|---|---|---|---|---|---|---|" ) ;
497+ for ( width, height) in [ ( 1280usize , 720usize ) , ( 1920 , 1080 ) , ( 3840 , 2160 ) ] {
498+ let sources: Vec < _ > = ( 0 ..SOURCE_RING )
499+ . map ( |seed| make_420v_source ( width, height, seed as u8 ) )
500+ . collect ( ) ;
501+ let quality_source = make_quality_source ( width, height) ;
502+ for mirrored in [ false , true ] {
503+ let passes = if mirrored { 2 } else { 1 } ;
504+ let scratch = usize:: from ( mirrored) ;
505+ let full = run_case ( & sources, & quality_source, ( width, height) , mirrored) ;
506+ for ( label, physical) in bubbles {
507+ let displayed = cap_gpui_kernels:: camera_preview:: cover_dimensions (
508+ ( width, height) ,
509+ ( physical. 0 . ceil ( ) as usize , physical. 1 . ceil ( ) as usize ) ,
510+ ) ;
511+ let target = cap_gpui_kernels:: camera_preview:: quantized_target ( physical)
512+ . map_or ( ( width, height) , |target| {
513+ cap_gpui_kernels:: camera_preview:: cover_dimensions (
514+ ( width, height) ,
515+ target,
516+ )
517+ } ) ;
518+ let bubble = run_case ( & sources, & quality_source, target, mirrored) ;
519+ let reference = area_average ( & full. output , full. dims , displayed) ;
520+ for ( path, case, slots) in [
521+ ( "main (full size)" , & full, MAIN_RING ) ,
522+ ( "branch (bubble)" , & bubble, PREVIEW_RING ) ,
523+ ] {
524+ let ( mean, p50, p95) = case. stats ;
525+ let shown = linear_sample ( & case. output , case. dims , displayed) ;
526+ println ! (
527+ "| {width}x{height} | {label} | {mirrored} | {path} | {}x{} | {mean:.1} | {p50:.1} | {p95:.1} | {:.2} | {:.1} | {:.2} |" ,
528+ case. dims. 0 ,
529+ case. dims. 1 ,
530+ mb( case. buffer_bytes * passes) ,
531+ mb( case. buffer_bytes * ( slots + scratch) ) ,
532+ psnr( & reference, & shown) ,
533+ ) ;
534+ }
535+ }
536+ }
537+ }
538+ println ! (
539+ "ring MB: main keeps {MAIN_RING} slots (#2761 and this branch {PREVIEW_RING}), plus one scratch buffer when mirrored; PSNR compares gpui's linear sampling of each output at the displayed cover size against an area average of the full-size conversion."
540+ ) ;
541+ }
252542}
0 commit comments