@@ -341,8 +341,8 @@ pub struct Compositor {
341341 /// touche depuis appartient au jeu actif et ne peut pas etre evince -- voir
342342 /// `cached_image`.
343343 img_frame_start : std:: cell:: Cell < u64 > ,
344- /// Champs et reliefs des sprites de curseur (mode 15), RG16F , par chemin, avec leur forme.
345- /// Pas d'eviction : l'ensemble des cartes livrees reste inferieur a quelques dizaines de Mo.
344+ /// Champs de distance des sprites de curseur (mode 15), R16F , par chemin, avec leur forme.
345+ /// Pas d'eviction : seuls les sprites du theme en cours y passent (~2,6 Mo pour les seize) .
346346 sdf_cache : RefCell < std:: collections:: HashMap < String , ( wgpu:: Texture , SpriteShape ) > > ,
347347
348348 /// Copie mipmappee de la frame composee, lue par les annotations « flou »
@@ -1321,27 +1321,22 @@ impl Compositor {
13211321 Ok ( ( tex, w, h) )
13221322 }
13231323
1324- /// Champ et relief du sprite `path` (binding 2 du mode 15) et sa forme, calcules au
1324+ /// Champ de distance du sprite `path` (binding 2 du mode 15) et sa forme, calcules au
13251325 /// premier appel. Parite `compositor_windows::cursor_sdf`.
1326- fn cursor_sdf (
1327- & self ,
1328- path : & str ,
1329- depth_path : Option < & str > ,
1330- ) -> Result < ( wgpu:: Texture , SpriteShape ) > {
1331- let cache_key = format ! ( "{path}\0 {}" , depth_path. unwrap_or_default( ) ) ;
1332- if let Some ( hit) = self . sdf_cache . borrow ( ) . get ( & cache_key) {
1326+ fn cursor_sdf ( & self , path : & str ) -> Result < ( wgpu:: Texture , SpriteShape ) > {
1327+ if let Some ( hit) = self . sdf_cache . borrow ( ) . get ( path) {
13331328 return Ok ( hit. clone ( ) ) ;
13341329 }
1335- let sdf = crate :: cursor_sdf:: CursorSdf :: load_with_depth ( path, depth_path ) ?;
1336- let texels = sdf. rg16_bytes ( ) ;
1330+ let sdf = crate :: cursor_sdf:: CursorSdf :: load ( path) ?;
1331+ let texels = sdf. f16_bytes ( ) ;
13371332 let size = wgpu:: Extent3d { width : sdf. width , height : sdf. height , depth_or_array_layers : 1 } ;
13381333 let tex = self . gpu . device . create_texture ( & wgpu:: TextureDescriptor {
13391334 label : Some ( "cursor-sdf" ) ,
13401335 size,
13411336 mip_level_count : 1 ,
13421337 sample_count : 1 ,
13431338 dimension : wgpu:: TextureDimension :: D2 ,
1344- format : wgpu:: TextureFormat :: Rg16Float ,
1339+ format : wgpu:: TextureFormat :: R16Float ,
13451340 usage : wgpu:: TextureUsages :: TEXTURE_BINDING | wgpu:: TextureUsages :: COPY_DST ,
13461341 view_formats : & [ ] ,
13471342 } ) ;
@@ -1355,13 +1350,13 @@ impl Compositor {
13551350 & texels,
13561351 wgpu:: TexelCopyBufferLayout {
13571352 offset : 0 ,
1358- bytes_per_row : Some ( sdf. width * 4 ) ,
1353+ bytes_per_row : Some ( sdf. width * 2 ) ,
13591354 rows_per_image : Some ( sdf. height ) ,
13601355 } ,
13611356 size,
13621357 ) ;
13631358 let entry = ( tex, sdf. shape ) ;
1364- self . sdf_cache . borrow_mut ( ) . insert ( cache_key , entry. clone ( ) ) ;
1359+ self . sdf_cache . borrow_mut ( ) . insert ( path . to_string ( ) , entry. clone ( ) ) ;
13651360 Ok ( entry)
13661361 }
13671362
@@ -2832,9 +2827,9 @@ impl Compositor {
28322827 // Curseur modelise (mode 15) : ce meme sprite extrude, `plan_cursor` en a tire la
28332828 // pose. Sprite au binding 1 (texY), champ au binding 2 (texU). Parite Windows/macOS.
28342829 if let Some ( pose) = plan. model {
2835- match self . cursor_sdf ( & sprite. path , sprite . model_depth_path . as_deref ( ) ) {
2830+ match self . cursor_sdf ( & sprite. path ) {
28362831 Ok ( ( sdf, shape) ) => {
2837- let shape = SpriteShape { hotspot , .. shape } ;
2832+ let shape = crate :: frame_geometry :: model_shape ( sprite , shape) ;
28382833 let sdf_view = sdf. create_view ( & wgpu:: TextureViewDescriptor :: default ( ) ) ;
28392834 for placement in placements {
28402835 let Some ( cb) = cursor_model_cb (
@@ -5137,16 +5132,52 @@ mod tests {
51375132 comp. set_cursor ( track. clone ( ) ) ;
51385133 comp. set_cursor_time ( Some ( 2.0 ) ) ;
51395134 comp. set_timeline_time ( Some ( 2.0 ) ) ;
5135+ let cfg = model_cfg ( ) ;
5136+ unsafe {
5137+ comp. compose_frame ( screen. as_ptr ( ) , screen. as_ptr ( ) , 0.0 , & cfg) . expect ( "compose_frame" ) ;
5138+ comp. readback_direct ( ) . expect ( "readback_direct" ) . 2
5139+ }
5140+ }
5141+
5142+ fn model_cfg ( ) -> crate :: config:: Cfg {
51405143 let mut cfg = crate :: config:: Cfg :: c8 ( ) ;
51415144 cfg. bg_blur = false ;
51425145 cfg. zoom = false ;
51435146 cfg. layout_anim = false ;
51445147 cfg. cursor = true ;
51455148 cfg. mblur_n = 1 ;
51465149 cfg. shadow = false ;
5147- unsafe {
5148- comp. compose_frame ( screen. as_ptr ( ) , screen. as_ptr ( ) , 0.0 , & cfg) . expect ( "compose_frame" ) ;
5149- comp. readback_direct ( ) . expect ( "readback_direct" ) . 2
5150+ cfg
5151+ }
5152+
5153+ /// Le pixel du hotspot et l'unité du modèle (px) que `plan_cursor` donne à la scène que
5154+ /// `compose_model` rend.
5155+ fn model_tip ( json : & str , track : & crate :: cursor:: CursorTrack ) -> ( [ f32 ; 2 ] , f32 ) {
5156+ let scene = crate :: scene:: Scene :: from_json ( json) . expect ( "scene json" ) ;
5157+ let ( cfg, live, render_px, src) = ( model_cfg ( ) , live_params_from_scene ( & scene) , [ 1280.0 , 720.0 ] , [ 640.0 , 360.0 ] ) ;
5158+ let g = plan_frame ( & FrameGeometryInput {
5159+ render_px,
5160+ screen_tex_px : src,
5161+ screen_visible_px : src,
5162+ webcam_visible_px : src,
5163+ u_max : 1.0 ,
5164+ v_max : 1.0 ,
5165+ frame : 0.0 ,
5166+ cfg : & cfg,
5167+ live,
5168+ scene : Some ( & scene) ,
5169+ cursor : Some ( track) ,
5170+ timeline_t_override : Some ( 2.0 ) ,
5171+ programme_time : None ,
5172+ } ) ;
5173+ let input = CursorPlanInput { render_px, u_max : 1.0 , v_max : 1.0 , cfg : & cfg, live, scene : Some ( & scene) , track, t : 2.0 } ;
5174+ let plan = plan_cursor ( & g, & input) . expect ( "un curseur a dessiner" ) ;
5175+ match plan. placement {
5176+ CursorPlacement :: Tilted { plane_pt, quad, center_px, .. } => {
5177+ let ( x, y) = quad. point_px ( plane_pt[ 0 ] , plane_pt[ 1 ] ) ;
5178+ ( [ center_px[ 0 ] + x, center_px[ 1 ] + y] , plan. size_px * quad. scale )
5179+ }
5180+ CursorPlacement :: Upright { center } => ( [ center[ 0 ] * render_px[ 0 ] , center[ 1 ] * render_px[ 1 ] ] , plan. size_px ) ,
51505181 }
51515182 }
51525183
@@ -5384,6 +5415,67 @@ mod tests {
53845415 assert ! ( failures. is_empty( ) , "{failures:#?}" ) ;
53855416 }
53865417
5418+ /// Pendant de `the_sculpted_cursors_stand_at_the_hotspot` (Windows) : la flèche et la main
5419+ /// sculptées des thèmes d'origine (`sculpt.rs`) passent par le WGSL, tiennent au hotspot, en
5420+ /// bas à droite de lui, et portent leur ombre en l'air.
5421+ #[ test]
5422+ fn the_sculpted_cursors_stand_at_the_hotspot ( ) {
5423+ let Some ( gpu) = gpu ( ) else { return } ;
5424+ let comp = Compositor :: new_sized ( & gpu, 1280 , 720 ) . expect ( "Compositor::new_sized" ) ;
5425+ let ( y, uv) = model_screen_planes ( false ) ;
5426+ let blue = FakeFrame :: from_planes ( & gpu, 640 , 360 , & y, & uv) ;
5427+ let ( y, uv) = model_screen_planes ( true ) ;
5428+ let orange = FakeFrame :: from_planes ( & gpu, 640 , 360 , & y, & uv) ;
5429+ let extruded = model_scene_json ( "null" , Some ( true ) , "default" , true , 5.0 ) ;
5430+ let hidden = model_scene_json ( "null" , Some ( true ) , "default" , false , 5.0 ) ;
5431+ let bare = compose_model ( & comp, & blue, & hidden, & model_track ( "arrow" , false , 0.5 ) ) ;
5432+ let mut failures = Vec :: new ( ) ;
5433+ for state in [ "arrow" , "pointer" ] {
5434+ let still = model_track ( state, false , 0.5 ) ;
5435+ let sprite = compose_model ( & comp, & blue, & extruded, & still) ;
5436+ for theme in [ "studio-ink" , "prism-glow" , "pop-coral" , "pixel-candy" , "star-sprout" ] {
5437+ // Le sprite reste celui du theme par defaut : seul le nom pose le modele.
5438+ let json = extruded
5439+ . replace ( & format ! ( r#"/{state}.png","# ) , & format ! ( r#"/{state}.png","sculpt":"{theme}/{state}","# ) ) ;
5440+ let ( hover, hover_b) = ( compose_model ( & comp, & blue, & json, & still) , compose_model ( & comp, & orange, & json, & still) ) ;
5441+ model_save ( & format ! ( "sculpt-{theme}-{state}" ) , & hover) ;
5442+ if hover == sprite {
5443+ failures. push ( format ! ( "{theme}/{state}: le sprite extrude au lieu du modele" ) ) ;
5444+ }
5445+ let ( tip, u) = model_tip ( & json, & still) ;
5446+ let mask = model_opaque ( & hover, & hover_b, & bare) ;
5447+ let ( mut body, mut c, mut near) = ( 0usize , [ 0.0f32 ; 2 ] , f32:: MAX ) ;
5448+ for ( i, _) in mask. iter ( ) . enumerate ( ) . filter ( |( _, m) | * * m) {
5449+ let ( x, y) = ( ( i % 1280 ) as f32 , ( i / 1280 ) as f32 ) ;
5450+ body += 1 ;
5451+ c = [ c[ 0 ] + x, c[ 1 ] + y] ;
5452+ near = near. min ( ( x - tip[ 0 ] ) . hypot ( y - tip[ 1 ] ) ) ;
5453+ }
5454+ let c = [ c[ 0 ] / body. max ( 1 ) as f32 , c[ 1 ] / body. max ( 1 ) as f32 ] ;
5455+ let shadow = ( 0 ..1280 * 720 )
5456+ . filter ( |& i| {
5457+ let ( a, b) = ( & hover[ i * 4 ..i * 4 + 3 ] , & bare[ i * 4 ..i * 4 + 3 ] ) ;
5458+ !mask[ i] && a != b && model_luma ( a) < model_luma ( b) - 6.0
5459+ } )
5460+ . count ( ) ;
5461+ println ! ( "{theme}/{state} : unite {u:.1} px, corps {body} px, a {near:.1} px du hotspot {tip:?}, centroide {c:?}, ombre {shadow} px" ) ;
5462+ if ( body as f32 ) < 0.12 * u * u {
5463+ failures. push ( format ! ( "{theme}/{state}: {body} px de corps pour {u:.0} px d'unite" ) ) ;
5464+ }
5465+ if near > 0.08 * u {
5466+ failures. push ( format ! ( "{theme}/{state}: le modele est a {near:.1} px du hotspot" ) ) ;
5467+ }
5468+ if !( c[ 0 ] > tip[ 0 ] && c[ 1 ] > tip[ 1 ] + 0.2 * u) {
5469+ failures. push ( format ! ( "{theme}/{state}: corps en {c:?}, pas en bas a droite de {tip:?}" ) ) ;
5470+ }
5471+ if shadow * 10 < body * 3 {
5472+ failures. push ( format ! ( "{theme}/{state}: pas d'ombre en l'air ({shadow} px)" ) ) ;
5473+ }
5474+ }
5475+ }
5476+ assert ! ( failures. is_empty( ) , "{failures:#?}" ) ;
5477+ }
5478+
53875479 /// Pendant de `tests/cursor_tap_render.rs` (Windows) : sous un lissage qui traine loin
53885480 /// derriere la souris, la pointe du modele se pose sur la pastille rouge du clic, et l'anneau
53895481 /// de l'impact (mode 16) l'entoure, a plat et incline.
0 commit comments