IFACEMETHODIMP CanvasSpriteBatch::Close()
{
    return ExceptionBoundary([&]
    {
        auto deviceContext = m_deviceContext.Close();
        if (!deviceContext)
            return;

        if (m_sprites.empty()) // early out if there's nothing to draw
            return;

        //
        // Sort the sprites
        //

        if (m_sortMode == CanvasSpriteSortMode::Bitmap)
        {
            std::stable_sort(m_sprites.begin(), m_sprites.end(),
            [] (auto const& a, auto const& b)
            {
                return a.Bitmap.Get() < b.Bitmap.Get();
            });
        }

        //
        // Build up a D2D sprite batch from our sprites
        //

        ComPtr<ID2D1SpriteBatch> spriteBatch;
        ThrowIfFailed(deviceContext->CreateSpriteBatch(&spriteBatch));

        assert(m_sprites.size() < std::numeric_limits<uint32_t>::max());

        auto firstSprite = &m_sprites.front();
        auto stride = static_cast<uint32_t>(sizeof(Sprite));

        ThrowIfFailed(spriteBatch->AddSprites(
                          static_cast<uint32_t>(m_sprites.size()),
                          &firstSprite->DestinationRect,
                          &firstSprite->SourceRect,
                          &firstSprite->Color,
                          &firstSprite->Transform,
                          stride,
                          stride,
                          stride,
                          stride));

        //
        // Get the device context into the right state
        //

        auto originalAntialiasMode = deviceContext->GetAntialiasMode();

        if (originalAntialiasMode == D2D1_ANTIALIAS_MODE_PER_PRIMITIVE)
            deviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);

        auto originalUnitMode = deviceContext->GetUnitMode();
        if (originalUnitMode != m_unitMode)
            deviceContext->SetUnitMode(m_unitMode);

        //
        // Draw the sprites - one DrawSpriteBatch call for each bitmap
        //

        // Figure out if we need to quirk the batch size to workaround an issue
        // with older Qualcomm drivers.
        ComPtr<ID2D1Device> d2dDevice;
        deviceContext->GetDevice(&d2dDevice);
        auto device = ResourceManager::GetOrCreate<ICanvasDeviceInternal>(d2dDevice.Get());
        bool quirked = device->IsSpriteBatchQuirkRequired();
        uint32_t maxSpritesPerBatch = quirked ? 256 : std::numeric_limits<uint32_t>::max();

        for (BatchFinder<Sprite> batchFinder(m_sprites, maxSpritesPerBatch); !batchFinder.Done(); batchFinder.FindNext())
        {
            deviceContext->DrawSpriteBatch(
                spriteBatch.Get(),
                batchFinder.CurrentStartIndex(),
                batchFinder.CurrentSpriteCount(),
                batchFinder.CurrentBitmap(),
                m_interpolationMode,
                m_spriteOptions);

            if (quirked)
            {
                // Direct2D will helpfully batch up our DrawSpriteBatch calls - when
                // we're manually unbatching them to avoid limits of the maximum sprites per batch!
                // An explicit Flush here prevents that from happening.
                deviceContext->Flush();
            }
        }

        //
        // Restore the state we may have changed
        //

        if (originalUnitMode != m_unitMode)
            deviceContext->SetUnitMode(originalUnitMode);

        if (originalAntialiasMode == D2D1_ANTIALIAS_MODE_PER_PRIMITIVE)
            deviceContext->SetAntialiasMode(originalAntialiasMode);
    });
}