From 1518c25a77e1d716d441bdf3f2d3beaa63b66e58 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 27 Oct 2017 14:38:43 -0500 Subject: [PATCH] servo: Merge #19042 - stylo: Switch to Servo style backend for compositor animations (from BorisChiou:stylo/animation/compositor); r= These are inter-dependent patches for bug 1340005. We add two FFIs to create the AnimationValues of Opacity and Transform because we support these two properties on the compositor. Besides, we factor out some common function in glue.rs, so we can use the same code path for both main-thread and compositor-thread animations. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix [Bug 1340005](https://bugzilla.mozilla.org/show_bug.cgi?id=1340005). - [X] These changes do not require tests because we have automatic test coverage on Gecko already, and Servo doesn't support compositor animation for now. Source-Repo: https://github.com/servo/servo Source-Revision: 4e2c0e3277dda5d8bf462602268bbc1ec8ea26f7 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 66c1dd18d57495d78729a669ec255c087b42c5e1 --- .../style/gecko/generated/bindings.rs | 23 +- .../gecko_bindings/sugar/ns_css_value.rs | 6 + .../components/style/properties/gecko.mako.rs | 63 +++- servo/ports/geckolib/glue.rs | 313 +++++++++++------- 4 files changed, 275 insertions(+), 130 deletions(-) diff --git a/servo/components/style/gecko/generated/bindings.rs b/servo/components/style/gecko/generated/bindings.rs index 3f1fe7210763..3eb3c5229134 100644 --- a/servo/components/style/gecko/generated/bindings.rs +++ b/servo/components/style/gecko/generated/bindings.rs @@ -2618,12 +2618,20 @@ extern "C" { RawServoAnimationValueBorrowed) -> f32; } +extern "C" { + pub fn Servo_AnimationValue_Opacity(arg1: f32) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValueBorrowed, list: *mut RefPtr); } +extern "C" { + pub fn Servo_AnimationValue_Transform(list: *const nsCSSValueSharedList) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_AnimationValue_DeepEqual(arg1: RawServoAnimationValueBorrowed, @@ -2768,9 +2776,22 @@ extern "C" { RawGeckoAnimationPropertySegmentBorrowed, computed_timing: RawGeckoComputedTimingBorrowed, - iteration_composite: + iter_composite: IterationCompositeOperation); } +extern "C" { + pub fn Servo_ComposeAnimationSegment(animation_segment: + RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: + RawServoAnimationValueBorrowedOrNull, + last_value: + RawServoAnimationValueBorrowedOrNull, + iter_composite: + IterationCompositeOperation, + progress: f64, + current_iteration: u64) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_DeclarationBlock_PropertyIsSet(declarations: RawServoDeclarationBlockBorrowed, diff --git a/servo/components/style/gecko_bindings/sugar/ns_css_value.rs b/servo/components/style/gecko_bindings/sugar/ns_css_value.rs index 8c1d83c8ffda..2a930b5cd1a4 100644 --- a/servo/components/style/gecko_bindings/sugar/ns_css_value.rs +++ b/servo/components/style/gecko_bindings/sugar/ns_css_value.rs @@ -23,6 +23,12 @@ impl nsCSSValue { unsafe { mem::zeroed() } } + /// Returns true if this nsCSSValue is none. + #[inline] + pub fn is_none(&self) -> bool { + self.mUnit == nsCSSUnit::eCSSUnit_None + } + /// Returns this nsCSSValue value as an integer, unchecked in release /// builds. pub fn integer_unchecked(&self) -> i32 { diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index cb0866df0c44..16a55c72689f 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -3081,6 +3081,13 @@ fn static_assert() { }; unsafe { + use gecko_bindings::structs::nsCSSKeyword; + use values::computed::Angle; + + let get_array_angle = || -> Angle { + bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, 1).get_angle() + }; + match transform_function { ${computed_operation_arm("Matrix", "matrix3d", ["number"] * 16)} ${computed_operation_arm("Skew", "skew", ["angle"] * 2)} @@ -3092,23 +3099,59 @@ fn static_assert() { ["list"] * 2 + ["percentage"])} ${computed_operation_arm("AccumulateMatrix", "accumulatematrix", ["list"] * 2 + ["percentage_to_integer"])} - _ => panic!("We shouldn't set any other transform function types"), + // FIXME: Bug 1391145 will introduce new types for these keywords. For now, we + // temporarily don't use |computed_operation_arm| because these are special cases + // for compositor animations when we use Gecko style backend on the main thread, + // and I don't want to add too many special cases in |computed_operation_arm|. + // + // Note: Gecko only converts translate and scale into the corresponding primitive + // functions, so we still need to handle the following functions. + nsCSSKeyword::eCSSKeyword_skewx => { + ComputedOperation::Skew(get_array_angle(), Angle::zero()) + }, + nsCSSKeyword::eCSSKeyword_skewy => { + ComputedOperation::Skew(Angle::zero(), get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatex => { + ComputedOperation::Rotate(1.0, 0.0, 0.0, get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatey => { + ComputedOperation::Rotate(0.0, 1.0, 0.0, get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatez | nsCSSKeyword::eCSSKeyword_rotate => { + ComputedOperation::Rotate(0.0, 0.0, 1.0, get_array_angle()) + }, + _ => panic!("{:?} is not an acceptable transform function", transform_function), } } } pub fn clone_transform(&self) -> longhands::transform::computed_value::T { - use properties::longhands::transform::computed_value; - if self.gecko.mSpecifiedTransform.mRawPtr.is_null() { - return computed_value::T(None); + return longhands::transform::computed_value::T(None); } let list = unsafe { (*self.gecko.mSpecifiedTransform.to_safe().get()).mHead.as_ref() }; - let result = list.map(|list| { - list.into_iter() - .map(|value| Self::clone_single_transform_function(value)) - .collect() - }); - computed_value::T(result) + Self::clone_transform_from_list(list) + } + pub fn clone_transform_from_list(list: Option< &structs::root::nsCSSValueList>) + -> longhands::transform::computed_value::T { + let result = match list { + Some(list) => { + let vec: Vec<_> = list + .into_iter() + .filter_map(|value| { + // Handle none transform. + if value.is_none() { + None + } else { + Some(Self::clone_single_transform_function(value)) + } + }) + .collect(); + if !vec.is_empty() { Some(vec) } else { None } + }, + _ => None, + }; + longhands::transform::computed_value::T(result) } ${impl_transition_time_value('delay', 'Delay')} diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index 901a06bfe147..4c841c71daa6 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -67,6 +67,7 @@ use style::gecko_bindings::bindings::RawGeckoServoAnimationValueListBorrowed; use style::gecko_bindings::bindings::RawGeckoServoAnimationValueListBorrowedMut; use style::gecko_bindings::bindings::RawGeckoServoStyleRuleListBorrowedMut; use style::gecko_bindings::bindings::RawServoAnimationValueBorrowed; +use style::gecko_bindings::bindings::RawServoAnimationValueBorrowedOrNull; use style::gecko_bindings::bindings::RawServoAnimationValueMapBorrowedMut; use style::gecko_bindings::bindings::RawServoAnimationValueStrong; use style::gecko_bindings::bindings::RawServoAnimationValueTableBorrowed; @@ -411,6 +412,157 @@ pub extern "C" fn Servo_AnimationValues_ComputeDistance(from: RawServoAnimationV from_value.compute_squared_distance(to_value).map(|d| d.sqrt()).unwrap_or(-1.0) } +/// Compute one of the endpoints for the interpolation interval, compositing it with the +/// underlying value if needed. +/// An None returned value means, "Just use endpoint_value as-is." +/// It is the responsibility of the caller to ensure that |underlying_value| is provided +/// when it will be used. +fn composite_endpoint( + endpoint_value: Option<&RawOffsetArc>, + composite: CompositeOperation, + underlying_value: Option<&AnimationValue>, +) -> Option { + match endpoint_value { + Some(endpoint_value) => { + match composite { + CompositeOperation::Add => { + underlying_value + .expect("We should have an underlying_value") + .animate(endpoint_value, Procedure::Add).ok() + }, + CompositeOperation::Accumulate => { + underlying_value + .expect("We should have an underlying value") + .animate(endpoint_value, Procedure::Accumulate { count: 1 }) + .ok() + }, + _ => None, + } + }, + None => underlying_value.map(|v| v.clone()), + } +} + +/// Accumulate one of the endpoints of the animation interval. +/// A returned value of None means, "Just use endpoint_value as-is." +fn accumulate_endpoint( + endpoint_value: Option<&RawOffsetArc>, + composited_value: Option, + last_value: &AnimationValue, + current_iteration: u64 +) -> Option { + debug_assert!(endpoint_value.is_some() || composited_value.is_some(), + "Should have a suitable value to use"); + + let count = current_iteration; + match composited_value { + Some(endpoint) => { + last_value + .animate(&endpoint, Procedure::Accumulate { count }) + .ok() + .or(Some(endpoint)) + }, + None => { + last_value + .animate(endpoint_value.unwrap(), Procedure::Accumulate { count }) + .ok() + }, + } +} + +/// Compose the animation segment. We composite it with the underlying_value and last_value if +/// needed. +/// The caller is responsible for providing an underlying value and last value +/// in all situations where there are needed. +fn compose_animation_segment( + segment: RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: Option<&AnimationValue>, + last_value: Option<&AnimationValue>, + iteration_composite: IterationCompositeOperation, + current_iteration: u64, + total_progress: f64, + segment_progress: f64, +) -> AnimationValue { + // Extract keyframe values. + let raw_from_value; + let keyframe_from_value = if !segment.mFromValue.mServo.mRawPtr.is_null() { + raw_from_value = unsafe { &*segment.mFromValue.mServo.mRawPtr }; + Some(AnimationValue::as_arc(&raw_from_value)) + } else { + None + }; + + let raw_to_value; + let keyframe_to_value = if !segment.mToValue.mServo.mRawPtr.is_null() { + raw_to_value = unsafe { &*segment.mToValue.mServo.mRawPtr }; + Some(AnimationValue::as_arc(&raw_to_value)) + } else { + None + }; + + let mut composited_from_value = composite_endpoint(keyframe_from_value, + segment.mFromComposite, + underlying_value); + let mut composited_to_value = composite_endpoint(keyframe_to_value, + segment.mToComposite, + underlying_value); + + debug_assert!(keyframe_from_value.is_some() || composited_from_value.is_some(), + "Should have a suitable from value to use"); + debug_assert!(keyframe_to_value.is_some() || composited_to_value.is_some(), + "Should have a suitable to value to use"); + + // Apply iteration composite behavior. + if iteration_composite == IterationCompositeOperation::Accumulate && current_iteration > 0 { + let last_value = last_value.unwrap_or_else(|| { + underlying_value.expect("Should have a valid underlying value") + }); + + composited_from_value = accumulate_endpoint(keyframe_from_value, + composited_from_value, + last_value, + current_iteration); + composited_to_value = accumulate_endpoint(keyframe_to_value, + composited_to_value, + last_value, + current_iteration); + } + + // Use the composited value if there is one, otherwise, use the original keyframe value. + let from = composited_from_value.as_ref().unwrap_or_else(|| keyframe_from_value.unwrap()); + let to = composited_to_value.as_ref().unwrap_or_else(|| keyframe_to_value.unwrap()); + + if segment.mToKey == segment.mFromKey { + return if total_progress < 0. { from.clone() } else { to.clone() }; + } + + match from.animate(to, Procedure::Interpolate { progress: segment_progress }) { + Ok(value) => value, + _ => if segment_progress < 0.5 { from.clone() } else { to.clone() }, + } +} + +#[no_mangle] +pub extern "C" fn Servo_ComposeAnimationSegment( + segment: RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: RawServoAnimationValueBorrowedOrNull, + last_value: RawServoAnimationValueBorrowedOrNull, + iteration_composite: IterationCompositeOperation, + progress: f64, + current_iteration: u64 +) -> RawServoAnimationValueStrong { + let underlying_value = AnimationValue::arc_from_borrowed(&underlying_value).map(|v| &**v); + let last_value = AnimationValue::arc_from_borrowed(&last_value).map(|v| &**v); + let result = compose_animation_segment(segment, + underlying_value, + last_value, + iteration_composite, + current_iteration, + progress, + progress); + Arc::new(result).into_strong() +} + #[no_mangle] pub extern "C" fn Servo_AnimationCompose(raw_value_map: RawServoAnimationValueMapBorrowedMut, base_values: RawServoAnimationValueTableBorrowed, @@ -461,125 +613,31 @@ pub extern "C" fn Servo_AnimationCompose(raw_value_map: RawServoAnimationValueMa return; } - // Extract keyframe values. - let raw_from_value; - let keyframe_from_value = if !segment.mFromValue.mServo.mRawPtr.is_null() { - raw_from_value = unsafe { &*segment.mFromValue.mServo.mRawPtr }; - Some(AnimationValue::as_arc(&raw_from_value)) + let raw_last_value; + let last_value = if !last_segment.mToValue.mServo.mRawPtr.is_null() { + raw_last_value = unsafe { &*last_segment.mToValue.mServo.mRawPtr }; + Some(&**AnimationValue::as_arc(&raw_last_value)) } else { None }; - let raw_to_value; - let keyframe_to_value = if !segment.mToValue.mServo.mRawPtr.is_null() { - raw_to_value = unsafe { &*segment.mToValue.mServo.mRawPtr }; - Some(AnimationValue::as_arc(&raw_to_value)) - } else { - None - }; - - // Composite with underlying value. - // A return value of None means, "Just use keyframe_value as-is." - let composite_endpoint = |keyframe_value: Option<&RawOffsetArc>, - composite_op: CompositeOperation| -> Option { - match keyframe_value { - Some(keyframe_value) => { - match composite_op { - CompositeOperation::Add => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.as_ref().unwrap().animate(keyframe_value, Procedure::Add).ok() - }, - CompositeOperation::Accumulate => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value - .as_ref() - .unwrap() - .animate(keyframe_value, Procedure::Accumulate { count: 1 }) - .ok() - }, - _ => None, - } - }, - None => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.clone() - }, - } - }; - let mut composited_from_value = composite_endpoint(keyframe_from_value, segment.mFromComposite); - let mut composited_to_value = composite_endpoint(keyframe_to_value, segment.mToComposite); - - debug_assert!(keyframe_from_value.is_some() || composited_from_value.is_some(), - "Should have a suitable from value to use"); - debug_assert!(keyframe_to_value.is_some() || composited_to_value.is_some(), - "Should have a suitable to value to use"); - - // Apply iteration composite behavior. - if iteration_composite == IterationCompositeOperation::Accumulate && - computed_timing.mCurrentIteration > 0 { - let raw_last_value; - let last_value = if !last_segment.mToValue.mServo.mRawPtr.is_null() { - raw_last_value = unsafe { &*last_segment.mToValue.mServo.mRawPtr }; - &*AnimationValue::as_arc(&raw_last_value) - } else { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.as_ref().unwrap() - }; - - // As with composite_endpoint, a return value of None means, "Use keyframe_value as-is." - let apply_iteration_composite = |keyframe_value: Option<&RawOffsetArc>, - composited_value: Option| - -> Option { - let count = computed_timing.mCurrentIteration; - match composited_value { - Some(endpoint) => { - last_value - .animate(&endpoint, Procedure::Accumulate { count }) - .ok() - .or(Some(endpoint)) - }, - None => { - last_value - .animate(keyframe_value.unwrap(), Procedure::Accumulate { count }) - .ok() - }, - } - }; - - composited_from_value = apply_iteration_composite(keyframe_from_value, - composited_from_value); - composited_to_value = apply_iteration_composite(keyframe_to_value, - composited_to_value); - } - - // Use the composited value if there is one, otherwise, use the original keyframe value. - let from_value = composited_from_value.as_ref().unwrap_or_else(|| keyframe_from_value.unwrap()); - let to_value = composited_to_value.as_ref().unwrap_or_else(|| keyframe_to_value.unwrap()); - let progress = unsafe { Gecko_GetProgressFromComputedTiming(computed_timing) }; - if segment.mToKey == segment.mFromKey { - if progress < 0. { - value_map.insert(property, from_value.clone()); - } else { - value_map.insert(property, to_value.clone()); - } - return; - } - - let pos = unsafe { - Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag) - }; - if let Ok(value) = from_value.animate(to_value, Procedure::Interpolate { progress: pos }) { - value_map.insert(property, value); - } else if pos < 0.5 { - value_map.insert(property, from_value.clone()); + let position = if segment.mToKey == segment.mFromKey { + // Note: compose_animation_segment doesn't use this value + // if segment.mFromKey == segment.mToKey, so assigning |progress| directly is fine. + progress } else { - value_map.insert(property, to_value.clone()); - } + unsafe { Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag) } + }; + + let result = compose_animation_segment(segment, + underlying_value.as_ref(), + last_value, + iteration_composite, + computed_timing.mCurrentIteration, + progress, + position); + value_map.insert(property, result); } macro_rules! get_property_id_from_nscsspropertyid { @@ -634,9 +692,9 @@ pub extern "C" fn Servo_Shorthand_AnimationValues_Serialize(shorthand_property: } #[no_mangle] -pub extern "C" fn Servo_AnimationValue_GetOpacity(value: RawServoAnimationValueBorrowed) - -> f32 -{ +pub extern "C" fn Servo_AnimationValue_GetOpacity( + value: RawServoAnimationValueBorrowed +) -> f32 { let value = AnimationValue::as_arc(&value); if let AnimationValue::Opacity(opacity) = **value { opacity @@ -646,9 +704,17 @@ pub extern "C" fn Servo_AnimationValue_GetOpacity(value: RawServoAnimationValueB } #[no_mangle] -pub extern "C" fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValueBorrowed, - list: *mut structs::RefPtr) -{ +pub extern "C" fn Servo_AnimationValue_Opacity( + opacity: f32 +) -> RawServoAnimationValueStrong { + Arc::new(AnimationValue::Opacity(opacity)).into_strong() +} + +#[no_mangle] +pub extern "C" fn Servo_AnimationValue_GetTransform( + value: RawServoAnimationValueBorrowed, + list: *mut structs::RefPtr +) { let value = AnimationValue::as_arc(&value); if let AnimationValue::Transform(ref servo_list) = **value { let list = unsafe { &mut *list }; @@ -665,6 +731,15 @@ pub extern "C" fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValu } } +#[no_mangle] +pub extern "C" fn Servo_AnimationValue_Transform( + list: *const nsCSSValueSharedList +) -> RawServoAnimationValueStrong { + let list = unsafe { (&*list).mHead.as_ref() }; + let transform = style_structs::Box::clone_transform_from_list(list); + Arc::new(AnimationValue::Transform(transform)).into_strong() +} + #[no_mangle] pub extern "C" fn Servo_AnimationValue_DeepEqual(this: RawServoAnimationValueBorrowed, other: RawServoAnimationValueBorrowed)