in winrt/lib/xaml/CanvasAnimatedControl.cpp [995:1279]
bool CanvasAnimatedControl::Tick(
CanvasSwapChain* swapChain,
bool areResourcesCreated)
{
EventWrite_CanvasAnimatedControl_Tick_Start();
auto tickEnd = MakeScopeWarden([] { EventWrite_CanvasAnimatedControl_Tick_Stop(); });
RenderTarget* renderTarget = GetCurrentRenderTarget();
if (IsSuspended())
return false;
if (!IsLoaded())
return false;
//
// Access shared state that's shared between the UI thread and the
// update/render thread. This is done in one place in order to hold the
// lock for as little time as possible.
//
Color clearColor;
Size currentSize;
float currentDpi;
GetClearColorSizeAndDpi(&clearColor, ¤tSize, ¤tDpi);
auto lock = Lock(m_sharedStateMutex);
m_sharedState.SizeSeenByGameLoop = currentSize;
m_sharedState.IsInTick = true;
auto tickEnd2 = MakeScopeWarden(
[&]
{
if (!lock.owns_lock())
lock.lock();
m_sharedState.IsInTick = false;
});
m_stepTimer.SetTargetElapsedTicks(m_sharedState.TargetElapsedTime);
m_stepTimer.SetFixedTimeStep(m_sharedState.IsStepTimerFixedStep);
if (m_sharedState.ShouldResetElapsedTime)
{
m_stepTimer.ResetElapsedTime();
}
bool deviceNeedsReCreationWithNewOptions = m_sharedState.DeviceNeedsReCreationWithNewOptions;
m_sharedState.DeviceNeedsReCreationWithNewOptions = false;
m_sharedState.ShouldResetElapsedTime = false;
// If the opacity has changed then the swap chain will need to be
// recreated before we can draw.
if (renderTarget->Target &&
GetAlphaModeFromClearColor(clearColor) != renderTarget->AlphaMode)
{
// This will cause the update/render thread to stop, giving the UI
// thread an opportunity to recreate the swap chain.
return false;
}
// If the device needs to be re-created with different options, this
// needs to happen before we can draw.
if (deviceNeedsReCreationWithNewOptions)
{
return false;
}
bool forceDraw = m_sharedState.NeedsDraw;
// At this point we know that we're going to handle the NeedsDraw (unless
// there's some kind of failure) so we can reset this flag now. This is
// particularly relevant for the ClearColor, since this indicates that we've
// 'consumed' that color.
m_sharedState.NeedsDraw = false;
bool invalidated = m_sharedState.Invalidated;
// If resources are created then we know that we'll be able to process the
// Invalidated request, so we can reset this flag now.
if (areResourcesCreated)
m_sharedState.Invalidated = false;
// Handle Paused state
bool isPaused = m_sharedState.IsPaused;
int64_t timeSpentPaused = 0;
if (!isPaused)
{
timeSpentPaused = m_sharedState.TimeSpentPaused;
if (areResourcesCreated)
{
m_sharedState.TimeWhenPausedWasSet = 0;
m_sharedState.TimeSpentPaused = 0;
}
}
// We update, but forego drawing if the control is not in a visible state.
// Force-draws, like those due to device lost, are not performed either.
// Drawing behavior resumes when the control becomes visible once again.
bool isVisible = IsVisible();
//
// Copy out the list of async actions, to avoid retaining the lock while
// they are being fired.
//
// The async actions are only executed after resources have been created.
// This means that:
//
// - the actions can assume that there's a valid device and
// - there's no risk of them running concurrently with
// CreateResources.
//
std::vector<ComPtr<AnimatedControlAsyncAction>> pendingActions;
if (areResourcesCreated)
std::swap(pendingActions, m_sharedState.PendingAsyncActions);
lock.unlock();
//
// Run any async actions
//
if (!pendingActions.empty())
{
IssueAsyncActions(pendingActions);
// One of the async actions may have changed the shared state, in which case
// we want to respond immediately.
if (!invalidated)
{
auto lock2 = Lock(m_sharedStateMutex);
invalidated = m_sharedState.Invalidated;
if (areResourcesCreated)
m_sharedState.Invalidated = false;
}
}
//
// Now do the update/render for this tick
//
UpdateResult updateResult{};
EventWrite_CanvasAnimatedControl_Update_Start(areResourcesCreated, isPaused);
if (areResourcesCreated && !isPaused)
{
bool forceUpdate = false;
if (!m_hasUpdated)
{
// For the first update we reset the timer. This handles the
// possibility of there being a long delay between construction and
// the first update.
m_stepTimer.ResetElapsedTime();
forceUpdate = true;
}
updateResult = Update(forceUpdate, timeSpentPaused);
m_hasUpdated |= updateResult.Updated;
}
EventWrite_CanvasAnimatedControl_Update_Stop(updateResult.Updated);
//
// We only ever Draw/Present if an Update has actually happened. This
// results in us waiting until the next vblank to update.
// This is desireable since using Present to wait for the vsync can
// result in missed frames.
//
bool drew = false;
if ((updateResult.Updated || forceDraw || invalidated) && isVisible)
{
bool zeroSizedTarget = currentSize.Width <= 0 || currentSize.Height <= 0;
// A dpi change doesn't matter on a zero-sized target.
bool dpiChangedOnNonZeroSizedTarget = renderTarget->Dpi != currentDpi && !zeroSizedTarget;
bool sizeChanged = renderTarget->Size != currentSize;
//
// If the control's size or dpi has changed then the swapchain's buffers
// need to be resized as appropriate.
//
if (sizeChanged || dpiChangedOnNonZeroSizedTarget)
{
if (zeroSizedTarget || !renderTarget->Target)
{
//
// Switching between zero and non-zero sized rendertargets requires calling
// CanvasSwapChainPanel::put_SwapChain, so must be done on the UI thread.
// We must stop the update/render thread to allow this to happen.
//
return false;
}
else if (dpiChangedOnNonZeroSizedTarget)
{
//
// A DPI change should stop and start the render thread, and
// raise a CreateResources.
//
return false;
}
else
{
// This can be done on the update/render thread because:
//
// - no XAML methods are called
//
// - the current render target won't be updated by the UI thread
// while the update/render thread is running
//
ThrowIfFailed(renderTarget->Target->ResizeBuffersWithWidthAndHeightAndDpi(currentSize.Width, currentSize.Height, currentDpi));
//
// The size and dpi fields of the render target object represent the real, committed state of the render
// target, while currentSize/currentDpi represent the thing last requested by the app.
//
renderTarget->Size = currentSize;
renderTarget->Dpi = currentDpi;
}
}
if (renderTarget->Target)
{
bool invokeDrawHandlers = (areResourcesCreated && (m_hasUpdated || invalidated));
EventWrite_CanvasAnimatedControl_Draw_Start(invokeDrawHandlers, updateResult.IsRunningSlowly);
Draw(renderTarget->Target.Get(), clearColor, invokeDrawHandlers, updateResult.IsRunningSlowly);
EventWrite_CanvasAnimatedControl_Draw_Stop();
EventWrite_CanvasAnimatedControl_Present_Start();
ThrowIfFailed(renderTarget->Target->Present());
EventWrite_CanvasAnimatedControl_Present_Stop();
drew = true;
}
}
//
// The call to Present() usually blocks until a previous frame has been
// composed into the scene. The happens because the swap chain has a
// limited number of buffers available, and Present() won't return until
// there's a buffer available to draw the next frame on.
//
// In some cases the Present() may return quickly. For example, there may
// already be a buffer free because the GPU is starting to catch up with the
// GPU. In fixed time step mode this will result in the next Tick happening
// soon enough that there is no work to do, and so we don't do a
// Draw/Present.
//
// Without the Present() call to block the CPU the game loop thread ends up
// busy waiting, pegging a CPU, drawing more power and draining the battery
// on a mobile device. This is undesireable!
//
// To prevent this from happening we call WaitForVerticalBlank to delay the
// next tick.
//
// Some caveats here:
//
// - software devices do not support WaitForVerticalBlank. In this case
// the CanvasSwapChain does a Sleep(0).
//
// - if there's no swap chain (eg the window is invisible) then we just
// sleep
//
if (!drew || !m_stepTimer.IsFixedTimeStep())
{
EventWrite_CanvasAnimatedControl_WaitForVerticalBlank_Start();
if (swapChain)
{
ThrowIfFailed(swapChain->WaitForVerticalBlank());
}
else
{
GetAdapter()->Sleep(static_cast<DWORD>(StepTimer::TicksToMilliseconds(StepTimer::DefaultTargetElapsedTime)));
}
EventWrite_CanvasAnimatedControl_WaitForVerticalBlank_Stop();
}
return areResourcesCreated && !isPaused;
}