@@ -1319,10 +1319,11 @@ impl Renderer {
13191319 }
13201320
13211321 log:: trace!(
1322- "Rendering post-blur layer {}: has_quads={}, has_triangles={}, has_text={}" ,
1322+ "Rendering post-blur layer {}: has_quads={}, has_triangles={}, has_primitives={}, has_text={}" ,
13231323 layer_index,
13241324 !layer. quads. is_empty( ) ,
13251325 !layer. triangles. is_empty( ) ,
1326+ !layer. primitives. is_empty( ) ,
13261327 !layer. text. is_empty( )
13271328 ) ;
13281329
@@ -1392,6 +1393,104 @@ impl Renderer {
13921393 ) ) ;
13931394 }
13941395
1396+ // Custom shader primitives are prepared with the rest of the
1397+ // layer, but the post-blur replay used to omit them entirely. A
1398+ // shader-backed child (for example the voice orb) was therefore
1399+ // skipped in the main pass as intended and never drawn again.
1400+ // Mirror the normal render path here so every primitive held back
1401+ // for backdrop blur is actually composited above it.
1402+ if !layer. primitives . is_empty ( ) {
1403+ let render_span = debug:: render ( debug:: Primitive :: Shader ) ;
1404+
1405+ let primitive_storage = self
1406+ . engine
1407+ . primitive_storage
1408+ . read ( )
1409+ . expect ( "Read primitive storage" ) ;
1410+
1411+ let mut need_render = Vec :: new ( ) ;
1412+
1413+ for instance in & layer. primitives {
1414+ let bounds = instance. bounds * scale;
1415+
1416+ if let Some ( clip_bounds) = ( instance. bounds * scale)
1417+ . intersection ( & physical_bounds)
1418+ . and_then ( Rectangle :: snap)
1419+ {
1420+ render_pass. set_viewport (
1421+ bounds. x ,
1422+ bounds. y ,
1423+ bounds. width ,
1424+ bounds. height ,
1425+ 0.0 ,
1426+ 1.0 ,
1427+ ) ;
1428+
1429+ render_pass. set_scissor_rect (
1430+ clip_bounds. x ,
1431+ clip_bounds. y ,
1432+ clip_bounds. width ,
1433+ clip_bounds. height ,
1434+ ) ;
1435+
1436+ let drawn = instance
1437+ . primitive
1438+ . draw ( & primitive_storage, & mut render_pass) ;
1439+
1440+ if !drawn {
1441+ need_render. push ( ( instance, clip_bounds) ) ;
1442+ }
1443+ }
1444+ }
1445+
1446+ render_pass. set_viewport (
1447+ 0.0 ,
1448+ 0.0 ,
1449+ viewport. physical_width ( ) as f32 ,
1450+ viewport. physical_height ( ) as f32 ,
1451+ 0.0 ,
1452+ 1.0 ,
1453+ ) ;
1454+
1455+ render_pass. set_scissor_rect (
1456+ 0 ,
1457+ 0 ,
1458+ viewport. physical_width ( ) ,
1459+ viewport. physical_height ( ) ,
1460+ ) ;
1461+
1462+ if !need_render. is_empty ( ) {
1463+ let _ = std:: mem:: ManuallyDrop :: into_inner ( render_pass) ;
1464+
1465+ for ( instance, clip_bounds) in need_render {
1466+ instance
1467+ . primitive
1468+ . render ( & primitive_storage, encoder, frame, & clip_bounds) ;
1469+ }
1470+
1471+ render_pass = std:: mem:: ManuallyDrop :: new ( encoder. begin_render_pass (
1472+ & wgpu:: RenderPassDescriptor {
1473+ label : Some ( "iced_wgpu post-blur render pass" ) ,
1474+ color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
1475+ view : frame,
1476+ depth_slice : None ,
1477+ resolve_target : None ,
1478+ ops : wgpu:: Operations {
1479+ load : wgpu:: LoadOp :: Load ,
1480+ store : wgpu:: StoreOp :: Store ,
1481+ } ,
1482+ } ) ] ,
1483+ depth_stencil_attachment : None ,
1484+ timestamp_writes : None ,
1485+ occlusion_query_set : None ,
1486+ multiview_mask : None ,
1487+ } ,
1488+ ) ) ;
1489+ }
1490+
1491+ render_span. finish ( ) ;
1492+ }
1493+
13951494 if !layer. text . is_empty ( ) {
13961495 log:: trace!( "Rendering text at text_layer {}" , text_layer) ;
13971496 text_layer += self . text . render (
0 commit comments