@@ -3105,9 +3105,9 @@ impl Compositor {
31053105 }
31063106 // Blur du fond (avant l'ecran), si active par la scene/l'inspector.
31073107 self . blur_bg ( & mut encoder, cfg. bg_blur ) ;
3108- // Passe 2 : avant-plan ( ecran + webcam) , compose par-dessus le fond
3109- // (eventuellement floute) avec `LoadOp::Load`. Les annotations sont dans
3110- // une passe a part, cf. plus bas .
3108+ // Passe 2 : l' ecran, compose par-dessus le fond (eventuellement floute) avec
3109+ // `LoadOp::Load`. Puis le curseur, la camera et les annotations, dans cet ordre :
3110+ // celui de Windows et macOS .
31113111 {
31123112 let mut rpass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
31133113 label : Some ( "fg-pass" ) ,
@@ -3140,40 +3140,12 @@ impl Compositor {
31403140 & device_frame,
31413141 ) ,
31423142 }
3143- if let Some ( ( _buf, bind) ) = & webcam_shadow {
3144- rpass. set_bind_group ( 0 , bind, & [ ] ) ;
3145- rpass. draw ( 0 ..4 , 0 ..1 ) ;
3146- }
3147- // Fond personnalise : ENTRE l'ombre et la camera. C'est ce sandwich qui
3148- // remplace la branche « mode 3 » du shader — la camera, decoupee, se
3149- // fond dessus par alpha ; l'ombre reste dessous, elle appartient a la
3150- // bulle et non a son contenu.
3151- if let Some ( bg) = & webcam_bg {
3152- rpass. set_bind_group ( 0 , & bg. bind , & [ ] ) ;
3153- rpass. draw ( 0 ..4 , 0 ..1 ) ;
3154- }
3155- if let Some ( ( _buf, bind) ) = & webcam_draw {
3156- rpass. set_bind_group ( 0 , bind, & [ ] ) ;
3157- rpass. draw ( 0 ..4 , 0 ..1 ) ;
3158- }
3159- }
3160- // Fige la frame composee pour les annotations « flou ». ICI et nulle part
3161- // ailleurs : apres l'ecran et la camera (sinon un flou masquerait du vide)
3162- // et avant la premiere annotation (sinon deux flous qui se recouvrent
3163- // s'echantillonnent l'un l'autre). Une passe de rendu ne peut pas lire sa
3164- // propre cible, d'ou la copie -- et d'ou le fait que les annotations
3165- // doivent avoir leur propre passe.
3166- if needs_ann_copy {
3167- self . generate_ann_mips ( & mut encoder) ;
31683143 }
3169- // Passe 3 : annotations puis curseur net, par-dessus tout le reste. Elle
3170- // existe meme sans flou : deux passes consecutives sur la MEME cible avec
3171- // `LoadOp::Load` ne coutent rien de plus qu'une seule sur un GPU
3172- // desktop, et un seul chemin de code vaut mieux qu'un branchement qui ne
3173- // serait exerce que dans un projet sur dix.
3144+ // Le curseur net, et l'impact des clics sous lui. La trainee, elle, a besoin de sa
3145+ // propre cible : elle suit.
31743146 {
31753147 let mut rpass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
3176- label : Some ( "ann -pass" ) ,
3148+ label : Some ( "cursor -pass" ) ,
31773149 color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
31783150 view : & self . rt_view ,
31793151 resolve_target : None ,
@@ -3187,18 +3159,11 @@ impl Compositor {
31873159 occlusion_query_set : None ,
31883160 } ) ;
31893161 rpass. set_pipeline ( & self . pipeline ) ;
3190- for a in & ann_draws {
3191- rpass. set_bind_group ( 0 , & a. bind , & [ ] ) ;
3192- rpass. draw ( 0 ..4 , 0 ..1 ) ;
3193- }
31943162 // L'impact des clics, sous le curseur et sa trainee (dessinee plus bas).
31953163 for bind in cursor_draw. iter ( ) . flat_map ( |c| & c. impacts ) {
31963164 rpass. set_bind_group ( 0 , bind, & [ ] ) ;
31973165 rpass. draw ( 0 ..4 , 0 ..1 ) ;
31983166 }
3199- // Curseur en dernier : au-dessus de l'ecran et des annotations.
3200- // Une seule copie = curseur net, il tient dans cette pass. La
3201- // trainee, elle, a besoin de sa propre cible (voir plus bas).
32023167 if let Some ( c) = cursor_draw. as_ref ( ) . filter ( |c| c. binds . len ( ) == 1 ) {
32033168 rpass. set_bind_group ( 0 , & c. binds [ 0 ] , & [ ] ) ;
32043169 rpass. draw ( 0 ..4 , 0 ..1 ) ;
@@ -3262,6 +3227,76 @@ impl Compositor {
32623227 rpass. set_bind_group ( 0 , abind, & [ ] ) ;
32633228 rpass. draw ( 0 ..3 , 0 ..1 ) ;
32643229 }
3230+ // La camera, au-dessus de l'ecran et du curseur.
3231+ {
3232+ let mut rpass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
3233+ label : Some ( "webcam-pass" ) ,
3234+ color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
3235+ view : & self . rt_view ,
3236+ resolve_target : None ,
3237+ ops : wgpu:: Operations {
3238+ load : wgpu:: LoadOp :: Load ,
3239+ store : wgpu:: StoreOp :: Store ,
3240+ } ,
3241+ } ) ] ,
3242+ depth_stencil_attachment : None ,
3243+ timestamp_writes : None ,
3244+ occlusion_query_set : None ,
3245+ } ) ;
3246+ rpass. set_pipeline ( & self . pipeline ) ;
3247+ if let Some ( ( _buf, bind) ) = & webcam_shadow {
3248+ rpass. set_bind_group ( 0 , bind, & [ ] ) ;
3249+ rpass. draw ( 0 ..4 , 0 ..1 ) ;
3250+ }
3251+ // Fond personnalise : ENTRE l'ombre et la camera. C'est ce sandwich qui
3252+ // remplace la branche « mode 3 » du shader — la camera, decoupee, se
3253+ // fond dessus par alpha ; l'ombre reste dessous, elle appartient a la
3254+ // bulle et non a son contenu.
3255+ if let Some ( bg) = & webcam_bg {
3256+ rpass. set_bind_group ( 0 , & bg. bind , & [ ] ) ;
3257+ rpass. draw ( 0 ..4 , 0 ..1 ) ;
3258+ }
3259+ if let Some ( ( _buf, bind) ) = & webcam_draw {
3260+ rpass. set_bind_group ( 0 , bind, & [ ] ) ;
3261+ rpass. draw ( 0 ..4 , 0 ..1 ) ;
3262+ }
3263+ }
3264+ // Fige la frame composee pour les annotations « flou ». ICI et nulle part
3265+ // ailleurs : apres l'ecran, le curseur et la camera (sinon un flou masquerait du vide)
3266+ // et avant la premiere annotation (sinon deux flous qui se recouvrent
3267+ // s'echantillonnent l'un l'autre). Une passe de rendu ne peut pas lire sa
3268+ // propre cible, d'ou la copie -- et d'ou le fait que les annotations
3269+ // doivent avoir leur propre passe.
3270+ if needs_ann_copy {
3271+ self . generate_ann_mips ( & mut encoder) ;
3272+ }
3273+ // Passe 3 : les annotations, par-dessus tout le reste, curseur compris : un flou de
3274+ // confidentialite doit le couvrir, sans quoi le cristal de Prism Glow, qui refracte
3275+ // l'enregistrement brut, montrerait ce que le flou cache. Elle existe meme sans flou :
3276+ // deux passes consecutives sur la MEME cible avec `LoadOp::Load` ne coutent rien de plus
3277+ // qu'une seule sur un GPU desktop, et un seul chemin de code vaut mieux qu'un
3278+ // branchement qui ne serait exerce que dans un projet sur dix.
3279+ {
3280+ let mut rpass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
3281+ label : Some ( "ann-pass" ) ,
3282+ color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
3283+ view : & self . rt_view ,
3284+ resolve_target : None ,
3285+ ops : wgpu:: Operations {
3286+ load : wgpu:: LoadOp :: Load ,
3287+ store : wgpu:: StoreOp :: Store ,
3288+ } ,
3289+ } ) ] ,
3290+ depth_stencil_attachment : None ,
3291+ timestamp_writes : None ,
3292+ occlusion_query_set : None ,
3293+ } ) ;
3294+ rpass. set_pipeline ( & self . pipeline ) ;
3295+ for a in & ann_draws {
3296+ rpass. set_bind_group ( 0 , & a. bind , & [ ] ) ;
3297+ rpass. draw ( 0 ..4 , 0 ..1 ) ;
3298+ }
3299+ }
32653300 self . gpu . context . submit ( std:: iter:: once ( encoder. finish ( ) ) ) ;
32663301 Ok ( ( ) )
32673302 }
@@ -5807,6 +5842,39 @@ mod tests {
58075842 assert ! ( failures. is_empty( ) , "{failures:#?}" ) ;
58085843 }
58095844
5845+ /// Un flou de confidentialité couvre le curseur, comme sous Windows et macOS. Le cristal de
5846+ /// Prism Glow réfracte l'enregistrement brut : dessiné par-dessus le flou, il montrerait ce
5847+ /// que le flou cache. Sous le flou, ce que change le curseur reste flou : aucun bord franc, là
5848+ /// où le serti du cristal, dessiné par-dessus, en ferait des centaines.
5849+ #[ test]
5850+ fn a_privacy_blur_covers_the_crystal_cursor ( ) {
5851+ let Some ( gpu) = gpu ( ) else { return } ;
5852+ let comp = Compositor :: new_sized ( & gpu, 1280 , 720 ) . expect ( "Compositor::new_sized" ) ;
5853+ let ( y, uv) = model_screen_planes ( false ) ;
5854+ let blue = FakeFrame :: from_planes ( & gpu, 640 , 360 , & y, & uv) ;
5855+ let blur = r#""annotations":[{"id":"b","startSec":0,"endSec":10,"kind":"blur","x":0.3,"y":0.3,"w":0.6,"h":0.7,"blur":{"style":"blur","shape":"rectangle","color":"white","intensity":24,"blockSize":16}}]"# ;
5856+ let scene = |show : bool | {
5857+ model_scene_json ( "null" , Some ( true ) , "default" , show, 5.0 )
5858+ . replace ( r#"/arrow.png","# , r#"/arrow.png","sculpt":"prism-glow/arrow","# )
5859+ . replace ( r#""annotations":[]"# , blur)
5860+ } ;
5861+ let still = model_track ( "arrow" , false , 0.5 ) ;
5862+ let with = compose_model ( & comp, & blue, & scene ( true ) , & still) ;
5863+ let without = compose_model ( & comp, & blue, & scene ( false ) , & still) ;
5864+ model_save ( "privacy-blur-over-crystal" , & with) ;
5865+ // Ce que change le curseur, pixel par pixel ; un bord franc y est un saut de plus de 40
5866+ // entre deux voisins.
5867+ let diff = |i : usize , c : usize | with[ i * 4 + c] as i32 - without[ i * 4 + c] as i32 ;
5868+ let changed = ( 0 ..1280 * 720 ) . filter ( |& i| ( 0 ..3 ) . any ( |c| diff ( i, c) != 0 ) ) . count ( ) ;
5869+ let sharp = ( 0 ..1280 * 719 )
5870+ . filter ( |& i| i % 1280 < 1279 )
5871+ . filter ( |& i| ( 0 ..3 ) . any ( |c| ( diff ( i, c) - diff ( i + 1 , c) ) . abs ( ) > 40 || ( diff ( i, c) - diff ( i + 1280 , c) ) . abs ( ) > 40 ) )
5872+ . count ( ) ;
5873+ println ! ( "sous le flou : {changed} px changés par le curseur, {sharp} bords francs" ) ;
5874+ assert ! ( changed > 0 , "le curseur ne change rien : il manque, ou le flou l'a effacé" ) ;
5875+ assert_eq ! ( sharp, 0 , "{sharp} bords francs sous le flou : le curseur est dessiné par-dessus" ) ;
5876+ }
5877+
58105878 /// Les thèmes cerclés gardent le trait de leur dessin (`design/cursors/<thème>`) : sur la
58115879 /// fleche comme sur la main, le modele est pour une bonne part de la couleur du trait (le
58125880 /// plateau, le jonc, les rainures entre les doigts), autour de la couleur du corps, et montre
0 commit comments