Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new AVP::AnimationOptimization property #6269

Merged
merged 12 commits into from
Jan 25, 2022
Merged
131 changes: 127 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,82 @@ void AnimatedVisualPlayer::OnAutoPlayPropertyChanged(
}
}

void AnimatedVisualPlayer::OnAnimationOptimizationPropertyChanged(
winrt::DependencyPropertyChangedEventArgs const& args)
{
// We do not support animated visuals below RS5, so nothing to do.
aborziak-ms marked this conversation as resolved.
Show resolved Hide resolved
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)
{
return;
}

// Call RequestCommit to make sure that previous compositor calls complete before destroying animations.
// RequestCommitAsync is available only for RS4+ but m_animatedVisual is not null guarantees that we are at least RS5+
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this the case? Don't the visuals get created at startup? and then I could call destroy animations on this and hit this code path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destroy and Create are private inside AVP, we are exposing only AnimationOptimization property

m_rootVisual.Compositor().RequestCommitAsync().Completed(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestCommitAsync() is only available on Rs4+ we need to gracefully handle the case where this API isn't available on RS2 and RS3.

[&, 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 +946,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