Skip to content

Commit

Permalink
Added new AVP::AnimationOptimization property (#6269)
Browse files Browse the repository at this point in the history
* Added new AVP::AnimationCacheMode API

* Fixed build break and added MUX_PUBLIC_V2

* Renamed InstantiateAnimations to CreateAnimations, reordered args in IAnimatedVisualSource3 method

* Addressed comments after discussion

* Stop DestroyAnimations callback after CreateAnimations is called

* Upate animated visual player to speicially implement IAnimatedVisualPlayer2 because it was not doing so due to the DeriveFromPanelHelper_base implementation.

* Use a using statement instead of explicit redirection.

* Regenerated LottieLogo file

* Added Rs5+ check

* Removed comment

* Added another rs5+ check

Co-authored-by: Stephen Peters <stpete@microsoft.com>
  • Loading branch information
aborziak-ms and StephenLPeters authored Jan 25, 2022
1 parent 62d9a6e commit 8d59449
Show file tree
Hide file tree
Showing 9 changed files with 3,107 additions and 3,853 deletions.
130 changes: 126 additions & 4 deletions dev/AnimatedVisualPlayer/AnimatedVisualPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ void AnimatedVisualPlayer::AnimationPlay::Start()
// Subscribe to the batch completed event.
m_batchCompletedToken = m_batch.Completed([this](winrt::IInspectable const&, winrt::CompositionBatchCompletedEventArgs const&)
{
if (m_owner)
{
// If optimization is set to Resources - destroy animations immediately after player stops.
if (m_owner->AnimationOptimization() == winrt::PlayerAnimationOptimization::Resources)
{
m_owner->DestroyAnimations();
}
}

// Complete the play when the batch completes.
//
// The "this" pointer is guaranteed to be valid because:
Expand Down Expand Up @@ -622,12 +631,20 @@ winrt::IAsyncAction AnimatedVisualPlayer::PlayAsync(double fromProgress, double
co_return;
}

// Make sure that animations are created.
CreateAnimations();

// Used to detect reentrance.
const auto version = ++m_playAsyncVersion;

// Cause any other plays to return.
// Complete m_nowPlaying if it is still running.
// Identical to Stop() call but without destroying the animations.
// WARNING - this call may cause reentrance via the IsPlaying DP.
Stop();
if (m_nowPlaying)
{
m_progressPropertySet.InsertScalar(L"Progress", static_cast<float>(m_currentPlayFromProgress));
m_nowPlaying->Complete();
}

if (version != m_playAsyncVersion)
{
Expand Down Expand Up @@ -707,6 +724,9 @@ void AnimatedVisualPlayer::SetProgress(double progress)
return;
}

// Make sure that animations are created.
CreateAnimations();

auto clampedProgress = std::clamp(static_cast<float>(progress), 0.0F, 1.0F);

// WARNING: Reentrance via IsPlaying DP may occur from this point down to the end of the method
Expand All @@ -725,6 +745,11 @@ void AnimatedVisualPlayer::SetProgress(double progress)
{
m_nowPlaying->Complete();
}

// If optimization is set to Resources - destroy annimations immediately.
if (AnimationOptimization() == winrt::PlayerAnimationOptimization::Resources) {
DestroyAnimations();
}
}

// Public API.
Expand Down Expand Up @@ -755,6 +780,81 @@ void AnimatedVisualPlayer::OnAutoPlayPropertyChanged(
}
}

void AnimatedVisualPlayer::OnAnimationOptimizationPropertyChanged(
winrt::DependencyPropertyChangedEventArgs const& args)
{
if (!SharedHelpers::IsRS5OrHigher())
{
return;
}

auto optimization = unbox_value<winrt::PlayerAnimationOptimization>(args.NewValue());

if (m_nowPlaying)
{
// If there is something in play right now we should not create/destroy animations.
return;
}

if (optimization == winrt::PlayerAnimationOptimization::Resources)
{
DestroyAnimations();
}
else if (optimization == winrt::PlayerAnimationOptimization::Latency)
{
CreateAnimations();
}
}

void AnimatedVisualPlayer::CreateAnimations() {
m_createAnimationsCounter++;

if (m_isAnimationsCreated)
{
return;
}

// Check if current animated visual supports creating animations and create them.
if (const auto& animatedVisual = m_animatedVisual.get())
{
if (const auto& animatedVisual2 = m_animatedVisual.try_as<winrt::IAnimatedVisual2>())
{
animatedVisual2.CreateAnimations();
m_isAnimationsCreated = true;
}
}
}

void AnimatedVisualPlayer::DestroyAnimations() {
if (!m_isAnimationsCreated || m_animatedVisual == nullptr || !SharedHelpers::IsRS5OrHigher())
{
return;
}

// Call RequestCommit to make sure that previous compositor calls complete before destroying animations.
// RequestCommitAsync is available only for RS4+
m_rootVisual.Compositor().RequestCommitAsync().Completed(
[&, createAnimationsCounter = m_createAnimationsCounter](auto, auto) {
// Check if there was any CreateAnimations call after DestroyAnimations.
// We should not destroy animations in this case,
// they will be destroyed by the following DestroyAnimations call.
if (createAnimationsCounter != m_createAnimationsCounter) {
return;
}

// Check if current animated visual supports destroyig animations.
if (const auto& animatedVisual = m_animatedVisual.get())
{
if (const auto& animatedVisual2 = m_animatedVisual.try_as<winrt::IAnimatedVisual2>())
{
animatedVisual2.DestroyAnimations();
m_isAnimationsCreated = false;
}
}
}
);
}

void AnimatedVisualPlayer::OnFallbackContentPropertyChanged(
winrt::DependencyPropertyChangedEventArgs const& args)
{
Expand Down Expand Up @@ -845,8 +945,30 @@ void AnimatedVisualPlayer::UpdateContent()
}

winrt::IInspectable diagnostics{};
auto animatedVisual = source.TryCreateAnimatedVisual(m_rootVisual.Compositor(), diagnostics);
m_animatedVisual.set(animatedVisual);
winrt::IAnimatedVisual animatedVisual;

const bool createAnimations = AnimationOptimization() == winrt::PlayerAnimationOptimization::Latency;

if (auto source3 = source.try_as<winrt::IAnimatedVisualSource3>())
{
animatedVisual = source3.TryCreateAnimatedVisual(m_rootVisual.Compositor(), diagnostics, createAnimations);
m_isAnimationsCreated = createAnimations;
m_animatedVisual.set(animatedVisual);
}
else
{
animatedVisual = source.TryCreateAnimatedVisual(m_rootVisual.Compositor(), diagnostics);
m_isAnimationsCreated = true;

// m_animatedVisual should be updated before DestroyAnimations call
m_animatedVisual.set(animatedVisual);

// Destroy animations if we don't need them.
// Old IAnimatedVisualSource interface always creates them.
if (!createAnimations) {
DestroyAnimations();
}
}

if (!animatedVisual)
{
Expand Down
11 changes: 10 additions & 1 deletion dev/AnimatedVisualPlayer/AnimatedVisualPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
// Derive from DeriveFromPanelHelper_base so that we get access to Children collection
// in Panel. The Children collection holds the fallback content.
struct AnimatedVisualPlayer:
public ReferenceTracker<AnimatedVisualPlayer, DeriveFromPanelHelper_base, winrt::AnimatedVisualPlayer>,
public ReferenceTracker<AnimatedVisualPlayer, DeriveFromPanelHelper_base, winrt::AnimatedVisualPlayer, winrt::IAnimatedVisualPlayer2>,
public AnimatedVisualPlayerProperties
{
using AnimatedVisualPlayerProperties::AnimationOptimization;
friend class AnimatedVisualPlayerProperties;

AnimatedVisualPlayer();
Expand Down Expand Up @@ -83,6 +84,8 @@ struct AnimatedVisualPlayer:

void OnAutoPlayPropertyChanged(winrt::DependencyPropertyChangedEventArgs const& args);

void OnAnimationOptimizationPropertyChanged(winrt::DependencyPropertyChangedEventArgs const& args);

void OnFallbackContentPropertyChanged(winrt::DependencyPropertyChangedEventArgs const& args);

void OnPlaybackRatePropertyChanged(winrt::DependencyPropertyChangedEventArgs const& args);
Expand All @@ -104,6 +107,9 @@ struct AnimatedVisualPlayer:
void OnHiding();
void OnUnhiding();

void CreateAnimations();
void DestroyAnimations();

//
// Initialized by the constructor.
//
Expand Down Expand Up @@ -141,4 +147,7 @@ struct AnimatedVisualPlayer:
// This is used to differentiate the first Loaded event (when the element has never been
// unloaded) from later Loaded events.
bool m_isUnloaded{ false };

bool m_isAnimationsCreated{ false };
uint32_t m_createAnimationsCounter = 0;
};
38 changes: 38 additions & 0 deletions dev/AnimatedVisualPlayer/AnimatedVisualPlayer.idl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ interface IAnimatedVisual
Windows.Foundation.TimeSpan Duration{ get; };
};

[MUX_PUBLIC]
[webhosthidden]
interface IAnimatedVisual2 requires IAnimatedVisual
{
void CreateAnimations();
void DestroyAnimations();
};

[MUX_PUBLIC]
[webhosthidden]
interface IAnimatedVisualSource
Expand All @@ -28,6 +36,16 @@ interface IDynamicAnimatedVisualSource
event Windows.Foundation.TypedEventHandler<IDynamicAnimatedVisualSource, Object> AnimatedVisualInvalidated;
};

[MUX_PUBLIC]
[webhosthidden]
interface IAnimatedVisualSource3
{
IAnimatedVisual2 TryCreateAnimatedVisual(
Windows.UI.Composition.Compositor compositor,
out Object diagnostics,
Boolean createAnimations);
};

[MUX_INTERNAL]
[webhosthidden]
interface ISelfPlayingAnimatedVisual
Expand All @@ -43,6 +61,14 @@ interface ISelfPlayingAnimatedVisual
void SetSize(Windows.Foundation.Size size);
};

[MUX_PUBLIC]
[webhosthidden]
enum PlayerAnimationOptimization
{
Latency,
Resources
};

[MUX_PUBLIC]
[webhosthidden]
[contentproperty("Source")]
Expand All @@ -68,6 +94,13 @@ unsealed runtimeclass AnimatedVisualPlayer
[MUX_PROPERTY_CHANGED_CALLBACK(TRUE)]
Double PlaybackRate;

[MUX_PUBLIC_V2]
{
[MUX_DEFAULT_VALUE("winrt::PlayerAnimationOptimization::Latency")]
[MUX_PROPERTY_CHANGED_CALLBACK(TRUE)]
PlayerAnimationOptimization AnimationOptimization;
}

Windows.UI.Composition.CompositionObject ProgressObject{ get; };

[MUX_DEFAULT_VALUE("winrt::Stretch::Uniform")]
Expand All @@ -90,6 +123,11 @@ unsealed runtimeclass AnimatedVisualPlayer
static Windows.UI.Xaml.DependencyProperty PlaybackRateProperty{ get; };
static Windows.UI.Xaml.DependencyProperty SourceProperty{ get; };
static Windows.UI.Xaml.DependencyProperty StretchProperty{ get; };

[MUX_PUBLIC_V2]
{
static Windows.UI.Xaml.DependencyProperty AnimationOptimizationProperty{ get; };
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public void TestCleanup()
TestCleanupHelper.Cleanup();
}

[TestMethod]
public void SettingAnimationOptimizationDoesNotCrash()
{
using (var setup = new TestSetupHelper("AnimatedVisualPlayer Tests"))
{
var button = Button("SwitchCacheModeButton");
button.Click();
Wait.ForIdle();
button.Click();
}
}

[TestMethod]
public void AccessibilityTest()
{
Expand Down
4 changes: 4 additions & 0 deletions dev/AnimatedVisualPlayer/TestUI/AnimatedVisualPlayerPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@
</StackPanel.Resources>
<TextBlock Text="Actions" Style="{ThemeResource StandardGroupHeader}"/>
<Button x:Name="PlayButton" Click="PlayButton_Click">Play forwards from 0 to 1</Button>
<Button x:Name="PlayHalfButton" Click="PlayHalfButton_Click">Play forwards from 0.0 to 0.5</Button>
<Button x:Name="ToZeroKeyframeAnimationPlayButton" Click="ToZeroKeyframeAnimationPlayButton_Click">Play forwards from 0.35 to 0</Button>
<Button x:Name="AroundTheEndAnimationPlayButton" Click="AroundTheEndAnimationPlayButton_Click">Play forwards from 0.35 to 0.3</Button>
<Button x:Name="FromOneKeyframeAnimationPlayButton" Click="FromOneKeyframeAnimationPlayButton_Click">Play forwards from 1 to 0.35</Button>
<Button x:Name="SetProgressButton" Click="SetProgressButton_Click">Set progress to 0.4</Button>
<Button x:Name="SwitchCacheModeButton" Click="SwitchCacheModeButton_Click">Switch cache mode</Button>
<Button x:Name="StopButton" Click="StopButton_Click">Stop</Button>
<Button x:Name="ReverseNegativePlaybackRateAnimationPlayButton" Click="ReverseNegativePlaybackRateAnimationPlayButton_Click">Play backwards from 1 to 0.5 then forwards from 0.5 to 1</Button>
<Button x:Name="ReversePositivePlaybackRateAnimationPlayButton" Click="ReversePositivePlaybackRateAnimationPlayButton_Click">Play forwards from 0 to 0.5 then backwards from 0.5 to 0</Button>
<Button x:Name="FallenBackButton" Click="FallenBackButton_ClickAsync">Fall back</Button>
Expand Down
35 changes: 33 additions & 2 deletions dev/AnimatedVisualPlayer/TestUI/AnimatedVisualPlayerPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async void Play(double from, double to, TextBox statusTextBox)
IsPlayingTextBoxBeforePlaying.Text = Player.IsPlaying.ToString();

// Start playing and concurrently get the IsPlaying state.
Task task1 = Player.PlayAsync(0, 1, false).AsTask();
Task task1 = Player.PlayAsync(from, to, false).AsTask();
Task task2 = GetIsPlayingAsync();

// Wait for playing to finish.
Expand All @@ -99,6 +99,13 @@ void PlayButton_Click(object sender, RoutedEventArgs e)
PlayForward(0, 1, ProgressTextBox);
}

// Play from 0 to 1.
void PlayHalfButton_Click(object sender, RoutedEventArgs e)
{
IsPlayingTextBoxBeforePlaying.Text = Player.IsPlaying.ToString();
PlayForward(0, 0.5, ProgressTextBox);
}

// Play forwards from 0.35 to 0.
void ToZeroKeyframeAnimationPlayButton_Click(object sender, RoutedEventArgs e)
{
Expand All @@ -117,12 +124,36 @@ void FromOneKeyframeAnimationPlayButton_Click(object sender, RoutedEventArgs e)
PlayForward(1, 0.35, FromOneKeyframeAnimationProgressTextBox);
}

void SetProgressButton_Click(object sender, RoutedEventArgs e)
{
Player.SetProgress(0.4);
}

void SwitchCacheModeButton_Click(object sender, RoutedEventArgs e)
{
if (Player.AnimationOptimization == Microsoft.UI.Xaml.Controls.PlayerAnimationOptimization.Latency)
{
Player.AnimationOptimization = Microsoft.UI.Xaml.Controls.PlayerAnimationOptimization.Resources;
SwitchCacheModeButton.Content = "Switch Optimization (Current: Resources)";
}
else if (Player.AnimationOptimization == Microsoft.UI.Xaml.Controls.PlayerAnimationOptimization.Resources)
{
Player.AnimationOptimization = Microsoft.UI.Xaml.Controls.PlayerAnimationOptimization.Latency;
SwitchCacheModeButton.Content = "Switch Optimization (Current: Latency)";
}
}

void StopButton_Click(object sender, RoutedEventArgs e)
{
Player.Stop();
}

// Play backwards from 1 to 0.5 then forwards from 0.5 to 1.
async void ReverseNegativePlaybackRateAnimationPlayButton_Click(object sender, RoutedEventArgs e)
{
// Start playing backwards from 1 to 0.
Player.PlaybackRate = -1;
Task task1 = Player.PlayAsync(0, 1, false).AsTask();
Task task1 = Player.PlayAsync(0.0, 1.0, false).AsTask();

// Reverse direction after half of the animation duration.
Task task2 = DelayForHalfAnimationDurationThenReverse();
Expand Down
Loading

0 comments on commit 8d59449

Please sign in to comment.