Bug 1178765 - Part 1: Add backdrop-filter WebRender display items r=gw
authorConnor Brewster <cbrewster@mozilla.com>
Tue, 13 Aug 2019 22:02:37 +0000
changeset 487806 b5d6ed62cda18c51ae303d6b904771850a3d1e03
parent 487805 828e2e9b86e85bbd7e6483c3437d50980433859c
child 487807 abdb4f5f3323cc1474d02a3722741c5d2cea5df7
push id36430
push userdvarga@mozilla.com
push dateWed, 14 Aug 2019 04:09:17 +0000
treeherdermozilla-central@d3deef805f92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1178765
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1178765 - Part 1: Add backdrop-filter WebRender display items r=gw Differential Revision: https://phabricator.services.mozilla.com/D39097
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender_api/src/display_item.rs
gfx/wr/webrender_api/src/display_list.rs
gfx/wr/wrench/src/yaml_frame_reader.rs
gfx/wr/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -339,17 +339,18 @@ struct MOZ_STACK_CLASS StackingContextPa
       : WrStackingContextParams{WrStackingContextClip::None(),
                                 nullptr,
                                 nullptr,
                                 wr::TransformStyle::Flat,
                                 wr::WrReferenceFrameKind::Transform,
                                 nullptr,
                                 /* is_backface_visible = */ true,
                                 /* cache_tiles = */ false,
-                                wr::MixBlendMode::Normal} {}
+                                wr::MixBlendMode::Normal,
+                                /* is_backdrop_root = */ false} {}
 
   void SetPreserve3D(bool aPreserve) {
     transform_style =
         aPreserve ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat;
   }
 
   nsTArray<wr::FilterOp> mFilters;
   nsTArray<wr::WrFilterData> mFilterDatas;
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2080,16 +2080,19 @@ pub struct WrStackingContextParams {
     pub opacity: *const f32,
     pub transform_style: TransformStyle,
     pub reference_frame_kind: WrReferenceFrameKind,
     pub scrolling_relative_to: *const u64,
     pub is_backface_visible: bool,
     /// True if picture caching should be enabled for this stacking context.
     pub cache_tiles: bool,
     pub mix_blend_mode: MixBlendMode,
+    /// True if this stacking context is a backdrop root.
+    /// https://drafts.fxtf.org/filter-effects-2/#BackdropRoot
+    pub is_backdrop_root: bool,
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_stacking_context(
     state: &mut WrState,
     mut bounds: LayoutRect,
     spatial_id: WrSpatialId,
     params: &WrStackingContextParams,
@@ -2201,17 +2204,18 @@ pub extern "C" fn wr_dp_push_stacking_co
                                 params.is_backface_visible,
                                 wr_clip_id,
                                 params.transform_style,
                                 params.mix_blend_mode,
                                 &filters,
                                 &r_filter_datas,
                                 &[],
                                 glyph_raster_space,
-                                params.cache_tiles);
+                                params.cache_tiles,
+                                params.is_backdrop_root);
 
     result
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState,
                                              is_reference_frame: bool) {
     debug_assert!(unsafe { !is_in_render_thread() });
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1306,16 +1306,19 @@ impl<'a> DisplayListFlattener<'a> {
             }
             DisplayItem::StickyFrame(ref info) => {
                 let parent_space = self.get_space(&info.parent_spatial_id);
                 self.flatten_sticky_frame(
                     info,
                     parent_space,
                 );
             }
+            DisplayItem::BackdropFilter(ref info) => {
+                unimplemented!();
+            }
 
             // Do nothing; these are dummy items for the display list parser
             DisplayItem::SetGradientStops |
             DisplayItem::SetFilterOps |
             DisplayItem::SetFilterData |
             DisplayItem::SetFilterPrimitives => {}
 
             DisplayItem::PopReferenceFrame |
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -100,16 +100,17 @@ pub enum DisplayItem {
     Line(LineDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     PushShadow(PushShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
+    BackdropFilter(BackdropFilterDisplayItem),
 
     // Clips
     Clip(ClipDisplayItem),
     ClipChain(ClipChainItem),
 
     // Spaces and Frames that content can be scoped under.
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
@@ -143,16 +144,17 @@ pub enum DebugDisplayItem {
     Line(LineDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     PushShadow(PushShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
+    BackdropFilter(BackdropFilterDisplayItem),
 
     Clip(ClipDisplayItem, Vec<ComplexClipRegion>),
     ClipChain(ClipChainItem, Vec<ClipId>),
 
     ScrollFrame(ScrollFrameDisplayItem, Vec<ComplexClipRegion>),
     StickyFrame(StickyFrameDisplayItem),
     Iframe(IframeDisplayItem),
     PushReferenceFrame(ReferenceFrameDisplayListItem),
@@ -606,16 +608,23 @@ pub struct RadialGradientDisplayItem {
     // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
     // defining the bounds of the item. Needs non-trivial backend changes.
     pub bounds: LayoutRect,
     pub gradient: RadialGradient,
     pub tile_size: LayoutSize,
     pub tile_spacing: LayoutSize,
 }
 
+/// Renders a filtered region of its backdrop
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct BackdropFilterDisplayItem {
+    pub common: CommonItemProperties,
+}
+// IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
+
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub struct ReferenceFrameDisplayListItem {
     pub origin: LayoutPoint,
     pub parent_spatial_id: SpatialId,
     pub reference_frame: ReferenceFrame,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
@@ -647,17 +656,20 @@ pub struct PushStackingContextDisplayIte
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
     pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
     /// True if picture caching should be used on this stacking context.
     pub cache_tiles: bool,
-} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
+    /// True if this stacking context is a backdrop root.
+    pub is_backdrop_root: bool,
+}
+// IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
 
 #[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)]
 pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
@@ -1359,16 +1371,17 @@ impl DisplayItem {
             DisplayItem::SetFilterPrimitives => "set_filter_primitives",
             DisplayItem::RadialGradient(..) => "radial_gradient",
             DisplayItem::Rectangle(..) => "rectangle",
             DisplayItem::ScrollFrame(..) => "scroll_frame",
             DisplayItem::SetGradientStops => "set_gradient_stops",
             DisplayItem::StickyFrame(..) => "sticky_frame",
             DisplayItem::Text(..) => "text",
             DisplayItem::YuvImage(..) => "yuv_image",
+            DisplayItem::BackdropFilter(..) => "backdrop_filter",
         }
     }
 }
 
 macro_rules! impl_default_for_enums {
     ($($enum:ident => $init:expr ),+) => {
         $(impl Default for $enum {
             #[allow(unused_imports)]
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -324,16 +324,19 @@ impl<'a> BuiltDisplayListIter<'a> {
             }
             Peek::NotPeeking => { /* do nothing */ }
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = ItemRange::default();
         self.cur_clip_chain_items = ItemRange::default();
+        self.cur_filters = ItemRange::default();
+        self.cur_filter_primitives = ItemRange::default();
+        self.cur_filter_data.clear();
 
         loop {
             self.next_raw()?;
             match self.cur_item {
                 SetGradientStops |
                 SetFilterOps |
                 SetFilterData |
                 SetFilterPrimitives => {
@@ -624,16 +627,17 @@ impl Serialize for BuiltDisplayList {
                 Real::Border(v) => Debug::Border(v),
                 Real::BoxShadow(v) => Debug::BoxShadow(v),
                 Real::Gradient(v) => Debug::Gradient(v),
                 Real::RadialGradient(v) => Debug::RadialGradient(v),
                 Real::Iframe(v) => Debug::Iframe(v),
                 Real::PushReferenceFrame(v) => Debug::PushReferenceFrame(v),
                 Real::PushStackingContext(v) => Debug::PushStackingContext(v),
                 Real::PushShadow(v) => Debug::PushShadow(v),
+                Real::BackdropFilter(v) => Debug::BackdropFilter(v),
 
                 Real::PopReferenceFrame => Debug::PopReferenceFrame,
                 Real::PopStackingContext => Debug::PopStackingContext,
                 Real::PopAllShadows => Debug::PopAllShadows,
             };
             seq.serialize_element(&serial_di)?
         }
         seq.end()
@@ -725,16 +729,17 @@ impl<'de> Deserialize<'de> for BuiltDisp
                 Debug::Image(v) => Real::Image(v),
                 Debug::YuvImage(v) => Real::YuvImage(v),
                 Debug::Border(v) => Real::Border(v),
                 Debug::BoxShadow(v) => Real::BoxShadow(v),
                 Debug::Gradient(v) => Real::Gradient(v),
                 Debug::RadialGradient(v) => Real::RadialGradient(v),
                 Debug::PushStackingContext(v) => Real::PushStackingContext(v),
                 Debug::PushShadow(v) => Real::PushShadow(v),
+                Debug::BackdropFilter(v) => Real::BackdropFilter(v),
 
                 Debug::PopStackingContext => Real::PopStackingContext,
                 Debug::PopReferenceFrame => Real::PopReferenceFrame,
                 Debug::PopAllShadows => Real::PopAllShadows,
             };
             poke_into_vec(&item, &mut data);
             // the aux data is serialized after the item, hence the temporary
             data.extend(temp.drain(..));
@@ -1230,63 +1235,52 @@ impl DisplayListBuilder {
         clip_id: Option<di::ClipId>,
         transform_style: di::TransformStyle,
         mix_blend_mode: di::MixBlendMode,
         filters: &[di::FilterOp],
         filter_datas: &[di::FilterData],
         filter_primitives: &[di::FilterPrimitive],
         raster_space: di::RasterSpace,
         cache_tiles: bool,
+        is_backdrop_root: bool,
     ) {
-        if filters.len() > 0 {
-            self.push_item(&di::DisplayItem::SetFilterOps);
-            self.push_iter(filters);
-        }
-
-        for filter_data in filter_datas {
-            let func_types = [
-                filter_data.func_r_type, filter_data.func_g_type,
-                filter_data.func_b_type, filter_data.func_a_type];
-            self.push_item(&di::DisplayItem::SetFilterData);
-            self.push_iter(&func_types);
-            self.push_iter(&filter_data.r_values);
-            self.push_iter(&filter_data.g_values);
-            self.push_iter(&filter_data.b_values);
-            self.push_iter(&filter_data.a_values);
-        }
-
-        if !filter_primitives.is_empty() {
-            self.push_item(&di::DisplayItem::SetFilterPrimitives);
-            self.push_iter(filter_primitives);
-        }
+        self.push_filters(filters, filter_datas, filter_primitives);
 
         let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
             origin,
             spatial_id,
             is_backface_visible,
             stacking_context: di::StackingContext {
                 transform_style,
                 mix_blend_mode,
                 clip_id,
                 raster_space,
                 cache_tiles,
+                is_backdrop_root,
             },
         });
 
         self.push_item(&item);
     }
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context(
         &mut self,
         origin: LayoutPoint,
         spatial_id: di::SpatialId,
         is_backface_visible: bool,
     ) {
-        self.push_simple_stacking_context_with_filters(origin, spatial_id, is_backface_visible, &[], &[], &[]);
+        self.push_simple_stacking_context_with_filters(
+            origin,
+            spatial_id,
+            is_backface_visible,
+            &[],
+            &[],
+            &[],
+        );
     }
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context_with_filters(
         &mut self,
         origin: LayoutPoint,
         spatial_id: di::SpatialId,
         is_backface_visible: bool,
@@ -1301,31 +1295,76 @@ impl DisplayListBuilder {
             None,
             di::TransformStyle::Flat,
             di::MixBlendMode::Normal,
             filters,
             filter_datas,
             filter_primitives,
             di::RasterSpace::Screen,
             /* cache_tiles = */ false,
+            /* is_backdrop_root = */ false,
         );
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_item(&di::DisplayItem::PopStackingContext);
     }
 
     pub fn push_stops(&mut self, stops: &[di::GradientStop]) {
         if stops.is_empty() {
             return;
         }
         self.push_item(&di::DisplayItem::SetGradientStops);
         self.push_iter(stops);
     }
 
+    pub fn push_backdrop_filter(
+        &mut self,
+        common: &di::CommonItemProperties,
+        filters: &[di::FilterOp],
+        filter_datas: &[di::FilterData],
+        filter_primitives: &[di::FilterPrimitive],
+    ) {
+        self.push_filters(filters, filter_datas, filter_primitives);
+
+        let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem {
+            common: *common,
+        });
+        self.push_item(&item);
+    }
+
+    pub fn push_filters(
+        &mut self,
+        filters: &[di::FilterOp],
+        filter_datas: &[di::FilterData],
+        filter_primitives: &[di::FilterPrimitive],
+    ) {
+        if filters.len() > 0 {
+            self.push_item(&di::DisplayItem::SetFilterOps);
+            self.push_iter(filters);
+        }
+
+        for filter_data in filter_datas {
+            let func_types = [
+                filter_data.func_r_type, filter_data.func_g_type,
+                filter_data.func_b_type, filter_data.func_a_type];
+            self.push_item(&di::DisplayItem::SetFilterData);
+            self.push_iter(&func_types);
+            self.push_iter(&filter_data.r_values);
+            self.push_iter(&filter_data.g_values);
+            self.push_iter(&filter_data.b_values);
+            self.push_iter(&filter_data.a_values);
+        }
+
+        if !filter_primitives.is_empty() {
+            self.push_item(&di::DisplayItem::SetFilterPrimitives);
+            self.push_iter(filter_primitives);
+        }
+    }
+
     fn generate_clip_index(&mut self) -> di::ClipId {
         self.next_clip_index += 1;
         di::ClipId::Clip(self.next_clip_index - 1, self.pipeline_id)
     }
 
     fn generate_spatial_index(&mut self) -> di::SpatialId {
         self.next_spatial_index += 1;
         di::SpatialId::new(self.next_spatial_index - 1, self.pipeline_id)
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -1533,16 +1533,17 @@ impl YamlFrameReader {
                 "box-shadow" => self.handle_box_shadow(dl, item, &mut info),
                 "iframe" => self.handle_iframe(dl, item, &mut info),
                 "stacking-context" => {
                     self.add_stacking_context_from_yaml(dl, wrench, item, false, &mut info)
                 }
                 "reference-frame" => self.handle_reference_frame(dl, wrench, item),
                 "shadow" => self.handle_push_shadow(dl, item, &mut info),
                 "pop-all-shadows" => self.handle_pop_all_shadows(dl),
+                "backdrop-filter" => self.handle_backdrop_filter(dl, item, &mut info),
                 _ => println!("Skipping unknown item type: {:?}", item),
             }
 
             if pushed_clip {
                 self.clip_id_stack.pop().unwrap();
             }
             if set_clip_id.is_some() {
                 self.clip_id_stack.pop().unwrap();
@@ -1868,16 +1869,17 @@ impl YamlFrameReader {
             .unwrap_or(TransformStyle::Flat);
         let mix_blend_mode = yaml["mix-blend-mode"]
             .as_mix_blend_mode()
             .unwrap_or(MixBlendMode::Normal);
         let raster_space = yaml["raster-space"]
             .as_raster_space()
             .unwrap_or(RasterSpace::Screen);
         let cache_tiles = yaml["cache"].as_bool().unwrap_or(false);
+        let is_backdrop_root = yaml["backdrop-root"].as_bool().unwrap_or(false);
 
         if is_root {
             if let Some(size) = yaml["scroll-offset"].as_point() {
                 let external_id = ExternalScrollId(0, dl.pipeline_id);
                 self.scroll_offsets.insert(external_id, LayoutPoint::new(size.x, size.y));
             }
         }
 
@@ -1892,29 +1894,53 @@ impl YamlFrameReader {
             clip_node_id,
             transform_style,
             mix_blend_mode,
             &filters,
             &filter_datas,
             &filter_primitives,
             raster_space,
             cache_tiles,
+            is_backdrop_root,
         );
 
         if !yaml["items"].is_badvalue() {
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
         }
 
         dl.pop_stacking_context();
 
         if reference_frame_id.is_some() {
             self.spatial_id_stack.pop().unwrap();
             dl.pop_reference_frame();
         }
     }
+
+    fn handle_backdrop_filter(
+        &mut self,
+        dl: &mut DisplayListBuilder,
+        item: &Yaml,
+        info: &mut CommonItemProperties,
+    ) {
+        info.clip_rect = try_intersect!(
+            self.resolve_rect(&item["bounds"]),
+            &info.clip_rect
+        );
+
+        let filters = item["filters"].as_vec_filter_op().unwrap_or(vec![]);
+        let filter_datas = item["filter-datas"].as_vec_filter_data().unwrap_or(vec![]);
+        let filter_primitives = item["filter-primitives"].as_vec_filter_primitive().unwrap_or(vec![]);
+
+        dl.push_backdrop_filter(
+            &info,
+            &filters,
+            &filter_datas,
+            &filter_primitives,
+        );
+    }
 }
 
 impl WrenchThing for YamlFrameReader {
     fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
         let mut should_build_yaml = false;
 
         // If YAML isn't read yet, or watching source file, reload from disk.
         if self.yaml_string.is_empty() || self.watch_source {
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -254,41 +254,22 @@ fn shadow_parameters(shadow: &Shadow) ->
     format!(
         "[{},{}],{},[{}]",
         shadow.offset.x, shadow.offset.y,
         shadow.blur_radius,
         color_to_string(shadow.color)
     )
 }
 
-fn write_stacking_context(
+fn write_filters(
     parent: &mut Table,
-    sc: &StackingContext,
+    name: &str,
+    filter_iter: impl IntoIterator<Item = FilterOp>,
     properties: &SceneProperties,
-    filter_iter: impl IntoIterator<Item = FilterOp>,
-    filter_data_iter: &[TempFilterData],
-    filter_primitive_iter: impl IntoIterator<Item = FilterPrimitive>,
 ) {
-    enum_node(parent, "transform-style", sc.transform_style);
-
-    let raster_space = match sc.raster_space {
-        RasterSpace::Local(scale) => {
-            format!("local({})", scale)
-        }
-        RasterSpace::Screen => {
-            "screen".to_owned()
-        }
-    };
-    str_node(parent, "raster-space", &raster_space);
-
-    // mix_blend_mode
-    if sc.mix_blend_mode != MixBlendMode::Normal {
-        enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
-    }
-    // filters
     let mut filters = vec![];
     for filter in filter_iter {
         match filter {
             FilterOp::Identity => { filters.push(Yaml::String("identity".into())) }
             FilterOp::Blur(x) => { filters.push(Yaml::String(format!("blur({})", x))) }
             FilterOp::Brightness(x) => { filters.push(Yaml::String(format!("brightness({})", x))) }
             FilterOp::Contrast(x) => { filters.push(Yaml::String(format!("contrast({})", x))) }
             FilterOp::Grayscale(x) => { filters.push(Yaml::String(format!("grayscale({})", x))) }
@@ -319,19 +300,24 @@ fn write_stacking_context(
                 filters.push(Yaml::String("component-transfer".to_string()))
             }
             FilterOp::Flood(color) => {
                 filters.push(Yaml::String(format!("flood({})", color_to_string(color))))
             }
         }
     }
 
-    yaml_node(parent, "filters", Yaml::Array(filters));
+    yaml_node(parent, name, Yaml::Array(filters));
+}
 
-    // filter datas
+fn write_filter_datas(
+    parent: &mut Table,
+    name: &str,
+    filter_data_iter: &[TempFilterData],
+) {
     let mut filter_datas = vec![];
     for filter_data in filter_data_iter {
         let func_types = filter_data.func_types.iter().map(|func_type| {
             match func_type {
                 ComponentTransferFuncType::Identity => { Yaml::String("Identity".to_string()) }
                 ComponentTransferFuncType::Table => { Yaml::String("Table".to_string()) }
                 ComponentTransferFuncType::Discrete => { Yaml::String("Discrete".to_string()) }
                 ComponentTransferFuncType::Linear => { Yaml::String("Linear".to_string()) }
@@ -356,19 +342,24 @@ fn write_stacking_context(
             Yaml::Array(r_values),
             Yaml::Array(g_values),
             Yaml::Array(b_values),
             Yaml::Array(a_values),
         ].to_vec();
         filter_datas.push(Yaml::Array(avec));
     }
 
-    yaml_node(parent, "filter-datas", Yaml::Array(filter_datas));
+    yaml_node(parent, name, Yaml::Array(filter_datas));
+}
 
-    // filter primitives
+fn write_filter_primitives(
+    parent: &mut Table,
+    name: &str,
+    filter_primitive_iter: impl IntoIterator<Item = FilterPrimitive>,
+) {
     let mut filter_primitives = vec![];
     for filter_primitive in filter_primitive_iter {
         let mut table = new_table();
         match filter_primitive.kind {
             FilterPrimitiveKind::Identity(identity_primitive) => {
                 yaml_node(&mut table, "type", Yaml::String("identity".into()));
                 filter_input_node(&mut table, "in", identity_primitive.input);
             }
@@ -408,17 +399,47 @@ fn write_stacking_context(
                 yaml_node(&mut table, "type", Yaml::String("component-transfer".into()));
                 filter_input_node(&mut table, "in", component_transfer_primitive.input);
             }
         }
         enum_node(&mut table, "color-space", filter_primitive.color_space);
         filter_primitives.push(Yaml::Hash(table));
     }
 
-    yaml_node(parent, "filter-primitives", Yaml::Array(filter_primitives));
+    yaml_node(parent, name, Yaml::Array(filter_primitives));
+}
+
+fn write_stacking_context(
+    parent: &mut Table,
+    sc: &StackingContext,
+    properties: &SceneProperties,
+    filter_iter: impl IntoIterator<Item = FilterOp>,
+    filter_data_iter: &[TempFilterData],
+    filter_primitive_iter: impl IntoIterator<Item = FilterPrimitive>,
+) {
+    enum_node(parent, "transform-style", sc.transform_style);
+
+    let raster_space = match sc.raster_space {
+        RasterSpace::Local(scale) => {
+            format!("local({})", scale)
+        }
+        RasterSpace::Screen => {
+            "screen".to_owned()
+        }
+    };
+    str_node(parent, "raster-space", &raster_space);
+
+    // mix_blend_mode
+    if sc.mix_blend_mode != MixBlendMode::Normal {
+        enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
+    }
+
+    write_filters(parent, "filters", filter_iter, properties);
+    write_filter_datas(parent, "filter-datas", filter_data_iter);
+    write_filter_primitives(parent, "filter-primitives", filter_primitive_iter);
 }
 
 #[cfg(target_os = "macos")]
 fn native_font_handle_to_yaml(
     rsrc: &mut ResourceGenerator,
     handle: &NativeFontHandle,
     parent: &mut yaml_rust::yaml::Hash,
     path_opt: &mut Option<PathBuf>,
@@ -1335,16 +1356,24 @@ impl YamlFrameWriter {
                     yaml_node(&mut v, "vertical-offset-bounds", Yaml::Array(vertical));
 
                     let applied = vec![
                         Yaml::Real(item.previously_applied_offset.x.to_string()),
                         Yaml::Real(item.previously_applied_offset.y.to_string()),
                     ];
                     yaml_node(&mut v, "previously-applied-offset", Yaml::Array(applied));
                 }
+                DisplayItem::BackdropFilter(item) => {
+                    str_node(&mut v, "type", "backdrop-filter");
+                    common_node(&mut v, clip_id_mapper, &item.common);
+
+                    write_filters(&mut v, "filters", base.filters(), &scene.properties);
+                    write_filter_datas(&mut v, "filter-datas", base.filter_datas());
+                    write_filter_primitives(&mut v, "filter-primitives", base.filter_primitives());
+                }
 
                 DisplayItem::PopReferenceFrame |
                 DisplayItem::PopStackingContext => return,
 
                 DisplayItem::SetGradientStops |
                 DisplayItem::SetFilterOps |
                 DisplayItem::SetFilterData |
                 DisplayItem::SetFilterPrimitives => panic!("dummy item yielded?"),