Freigeben über


Compositor-Uhr

Übersicht

Die Compositor-Uhr-API bietet Statistiken und Bildfrequenzsteuerung, um Inhalte auf dem Bildschirm reibungslos, schnellstmöglich und in einer Vielzahl von Hardwarekonfigurationen zu präsentieren. In der Tradition wurde dies von DirectX-APIs verarbeitet. Diese sind jedoch stark an feste Aktualisierungsrate und Einzelne Anzeigekonfigurationen geknüpft. Hier sehen Sie beispielsweise einen vereinfachten Pseudocode, der zeigt, wie Apps in der Regel erstellt werden, um mit der Anzeigeaktualisierungsrate zu zeichnen.

void GameLoop()
{
    CreateRenderingObjects();
    auto pSwapChain = CreateSwapChain();

    while (pSwapChain->WaitForVerticalBlank())
    {
        ProcessInput();
        RenderFrame(pSwapChain);
        pSwapChain->Present();
    }
}

Bei dieser Art von Schleife wird davon ausgegangen, dass ein einzelner vertikaler leerer Rhythmus (vblank) vorliegt. Es ist nicht klar, was die Anwendung tun soll, wenn sich das Fenster über zwei Monitore erstreckt, deren Scanout außerhalb der Phase liegt, oder die insgesamt unterschiedliche Frequenzen aufweisen. Tatsächlich verwendet die DXGI-Swapchain-API immer den Rhythmus des primären Monitors, unabhängig davon, in welchem Fenster die Anwendung angezeigt wird. Dies führt zu Problemen für Anwendungen, die eine reibungslose Darstellung auf allen Monitoren wünschen. Ein reales Beispiel ist die Videowiedergabe auf einem sekundären Monitor, der eine andere Aktualisierung als der primäre Monitor aufweist. ein Szenario, das seit der Einführung mehrerer Monitore vorhanden ist; und es wirkt sich unverhältnismäßig auf Gamer aus, die tendenziell über einen 60Hz-Monitor für die sekundäre Benutzeroberfläche und einen viel höheren Frequenzmonitor (144 + Hz) für Spiele verfügen.

Das zweite häufige Problem besteht darin, die Bildfrequenz basierend auf der Computerleistung anzupassen. Dies ist typisch für Videowiedergabeanwendungen, die wissen möchten, ob die Videoframes vom Benutzer zum erwarteten Zeitpunkt gesehen werden oder ob Störungen die Präsentation ungleichmäßig machen, in der Hoffnung, die Präsentation für eine bessere Leistung anzupassen. Beispielsweise kann ein Videostreamingdienst zu einem Stream mit niedrigerer Qualität wechseln, wenn der Computer nicht in der Lage ist, die gewünschte Bildfrequenz bei höchster Qualität aufrechtzuerhalten. Auch dies wird von DXGI-APIs behandelt und ist daher von der gleichen Architektur- und API-Offenlegungsbeschränkung betroffen.

Schließlich bietet die API Anwendungen die Möglichkeit, an einem neuen Feature zur Erhöhung der Bildfrequenz mit dem Namen Dynamische Aktualisierungsrate teilzunehmen, bei dem das System mit einer relativ niedrigen Aktualisierungsrate für normale Vorgänge ( z. B. 60Hz) ausgeführt wird, es jedoch bis zu einer höheren Frequenz (z. B. 120 Hz) beschleunigt, wenn eine Anwendung bestimmte latenzempfindliche Vorgänge ausführt, z. B. Freihandeingaben mit einem Eingabestift. oder das Verschieben von Toucheingaben. Die Boost-Funktion ist vorhanden, da die Ausführung mit der hohen Frequenz 100 % der Zeit aus Sicht des Stromverbrauchs unerschwinglich ist. Gleichzeitig ist aufgrund der gleichen Einschränkungen der DXGI-API das Ändern der Aktualisierungsrate der Anzeige zu beliebigen Zeiten normalerweise teuer, was Benachrichtigungen über änderungsfähige Übertragungen an alle Anwendungen und die Kosten für alle Anwendungen, die Code ausführen, um auf die Änderung zu reagieren. Daher führt das Feature zur Erhöhung der Aktualisierungsrate eine einfache Konfigurationsänderung durch, die keine Benachrichtigungen ausgibt, daher aber von den meisten Anwendungen abstrahiert werden muss, die weiterhin glauben, dass das System mit der niedrigeren Häufigkeit ausgeführt wird. Diese Virtualisierung funktioniert, indem Anwendungen nur alle anderen vblank oder alle drei vblanks oder ein anderes ganzzahliges Intervall ausgegeben werden, sodass die Anwendung eine effektive Aktualisierungsrate erkennt, die ein ganzzahliger Bruchteil der tatsächlichen Häufigkeit ist. Dies ermöglicht es, den vorhandenen vblank-Mechanismus ohne zusätzliche Kosten zu verwenden, um eine vollkommen regelmäßige niedrigere Frequenz zu generieren. Das ausgerichtete vblank wird durch einen dynamischen Aktualisierungsratenmodus im Betriebssystem (OS) dargestellt, z. B. 60Hz/120Hz. Beachten Sie, dass das Boost-Feature folglich nur funktioniert, um eine höhere Frequenz zu erhöhen, niemals zu einer niedrigeren, da es nicht genauso billig ist, künstliche Vblanks einzufügen, wie echte Vblanks zu ignorieren.

Mit der Compositor-Uhr-API kann Ihre Anwendung nicht nur anfordern, dass das System in den Boost-Modus wechselt oder diesen verlässt, sondern auch die tatsächliche Aktualisierungsrate in diesem Modus beobachten, sodass Sie Inhalte mit der höheren Häufigkeit präsentieren können.

API

Die API besteht aus drei Teilen. Die erste bietet einen anzeigeunabhängigen Heartbeat für Anwendungen, die auf mehreren Monitoren mit Bildfrequenz dargestellt werden sollen. Die zweite ermöglicht Es Anwendungen, eine Frequenzverstärkung mit dynamischer Aktualisierungsrate anzufordern. Die dritte bietet Statistiken über das Verhalten der Systemkompositions-Engine, die für jede einzelne Anzeige getrennt sind.

Jeder Teil der API beeinflusst oder beobachtet den Arbeitszyklus des Systemkompositors. Dieser Arbeitszyklus ist ein regelmäßiger Rhythmus, der einen CompositorFrame pro Zyklus erzeugt. Dieser Zyklus kann je nach Systemworkload, Anzahl der Anzeigen und anderen Faktoren an der Anzeige von vblanks ausgerichtet sein.

Warten auf die Komponitoruhr

Der Zweck dieses Signals besteht darin, die Verwendung der IDXGIOutput::WaitForVBlank-Methode zu ersetzen und gleichzeitig eine höhere Flexibilität bei verschiedenen Aktualisierungsraten zu bieten und Die Nutzungsmuster für Entwickler zu vereinfachen. Wie bei WaitForVBlank muss das System wissen, ob eine Anwendung auf dieses Signal wartet oder nicht, damit das System, wenn keine Anwendungen warten, das Video Karte anweisen kann, den vertikalen leeren Interrupt zu deaktivieren.

Dies ist für die Energieverwaltung von entscheidender Bedeutung, sodass die Architektur der API als Wartefunktionsaufruf beschränkt wird, anstatt ein Ereignis zu akzeptieren oder zurückzugeben (das Grafiksystem kann nicht bestimmen, ob darauf gewartet wird). Auf dieser niedrigen Ebene wird erwartet, dass Anwendungen diese API verwenden, um Renderingthreads zu steuern, die von allgemeinen UI-Threads getrennt sind, ähnlich wie IDXGIOutput::WaitForVBlank traditionell verwendet wird.

Wie in der Übersicht erwähnt, gibt es mehrere Aspekte, die die Compositoruhr berücksichtigen kann, die WaitForVBlank nicht kann.

  • Nächstes vertikales Leerzeichen, wenn die Komponitoruhr nicht unbedingt von der primären Anzeige stammt.
  • Aktivieren von Anwendungen mit variablen Raten auf Displays, die die dynamische Aktualisierungsrate unterstützen.
  • Aktivieren von Anwendungen für anwendungsdefinierte Ereignisse.

Im Allgemeinen wird erwartet, dass viele Anwendungen mit der Compositoruhr synchronisiert bleiben möchten, um die beste Zeit für ihre Frames zu erzielen. Einige Ausnahmen können jedoch Medienframeworks und Spiele sein, die auf dem vertikalen Leerzeichen einer bestimmten Anzeige aktiviert werden müssen.

Verarbeiten der Verwendung mit der Compositoruhr

Anwendungen werden derzeit über den DXGI-Mechanismus an jedem vertikalen Leerzeichen aktiviert, weisen jedoch häufig andere Ereignisse auf, für die sie ebenfalls reaktivieren müssen. Anstatt diese Ereignisse separat zu behandeln, kann die Compositoruhr Handles für mehrere Ereignisse verwenden und im nächsten Frame und bei jedem Auslösen der Ereignisse signalisieren. Die Anwendung kann dann von einem Signal aus reaktivieren, wobei sie das Ereignis kennt, das sie ausgelöst hat.

Zyklus für Compositoruhrereignisse

Die Komponitoruhr wird immer am vertikalen Leerzeichen eines Monitors oder auf einem anderen Timer aktiviert. Wenn sich der Compositor im Ruhezustand befindet, die Anzeige aber noch aktualisiert wird, wird dieses Signal weiterhin am Vblank der primären Anzeige ausgelöst.

C++-Beispiel

void GameLoop(HANDLE hQuitGameEvent)
{
    DWORD waitResult;

    CreateRenderingObjects();
    auto pSwapChain = CreateSwapChain();

    do
    {
        // Do all of the work for a single frame
        ProcessInput();
        RenderFrame(pSwapChain);
        pSwapChain->Present();

        // Wait for the compositor heartbeat before starting a new frame
        waitResult = DCompositionWaitForCompositorClock(1, &hQuitGameEvent, INFINITE);

        // If we get WAIT_RESULT_0+count it means the compositor clock ticked,
        // and we should render another frame. Our count is one, as we're
        // passing only one extra handle. Otherwise, either we got a failure or
        // another thread signaled our "quit" event, and in either case we want
        // to exit the loop
    } while (waitResult == WAIT_OBJECT_0 + 1);
}

Boost-Compositor-Uhr

Wenn die Quelle der Compositoruhr die dynamische Aktualisierungsrate unterstützt (diese Funktion ist in den erweiterten Anzeigeeinstellungen aktiviert; nur für Bildschirme mit variabler Aktualisierungsrate mit Unterstützungstreibern verwendbar) kann das System dynamisch zwischen zwei Sätzen wechseln. Es gibt einen unverboosten Modus, der in der Regel 60Hz beträgt, und eine Boosted-Rate, die normalerweise bei 120Hz um das 2-fache höher ist. Diese höhere Aktualisierungsrate sollte verwendet werden, um latenzabhängige Inhalte wie digitale Freihandeingaben zu verbessern. Das folgende Diagramm zeigt, wie das System zwischen der Ausführung mit einer Basisrate von 60 Hz (Flip 1) und dann für 6 Frames (2-7) mit digitaler Freihandzeit bei 120Hz wechselt. Sobald die digitale Freihandeingabe nicht mehr aktualisiert wird, wechselt das System zurück in einen 60Hz-Modus.

Hier sehen Sie eine Abbildung der dynamischen Framerate für die Verstärkung.

Aktualisierungsrate bei flip2 erhöht; freihand endet durch flip8, und die Rate wird auf 60Hz zurückgegeben.

Und hier erfahren Sie, wie DWM Boost-Anforderungen verarbeitet.

Flussdiagramm, das zeigt, wie DWM Boost-Anforderungen verarbeitet

Wenn eine Anwendung, die einen Boost anfordert, beendet wird, werden auch die Boost-Anforderungen der App beendet. Anwendungen, die immer noch mit mehreren Boostanforderungen aktiv sind, können die Referenzanzahl überprüfen, um zu bestimmen, wie oft die Boost-Funktion aufzuheben ist. Boost-Aufrufe sind vollständig kompatibel, auch wenn sich das System nicht im Dynamischen Aktualisierungsratenmodus befindet, in dem der Boost-Multiplikator 1-fach wäre.

C++-Beispiel

In diesem Beispiel wird WM_TOUCH verarbeitet, um die Aktualisierungsrate zu erhöhen, wenn diese Anwendung Toucheingaben empfängt, mit der Absicht, eine reibungslosere, hochfrequente Touchverschiebung zu ermöglichen. Eine komplexere Anwendung kann zuerst die Gestenerkennung ausführen und nur dann verstärken, wenn eine Verschiebung erkannt wird.

int g_activeTouchPoints = 0;

LRESULT OnTouch(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    LRESULT result = 0;
    UINT inputCount = LOWORD(wParam);
    auto hTouchInput = reinterpret_cast<HTOUCHINPUT>(lParam);

    // Allocate room for touch data (assume throwing new)
    auto pInputs = new TOUCHINPUT[inputCount];

    if (GetTouchInputInfo(hTouchInput, inputCount, pInputs, sizeof(TOUCHINPUT)))
    {
        for (int index = 0; index < inputCount; index++)
        {
            auto& touchInput = pInputs[index];

            // The first time we receive a touch down, boost the compositor
            // clock so we do our stuff at high frequency. Once the last touch
            // up happens, return to the base frequency
            if (touchInput.dwFlags & TOUCHEVENTF_DOWN)
            {
                if (!g_activeTouchPoints)
                {
                    // We're going from zero to one active points -- boost 
                    DCompositionBoostCompositorClock(true);
                }

                g_activeTouchPoints++;
            }
            else if (touchInput.dwFlags && TOUCHEVENTWF_UP)
            {
                g_activeTouchPoints--;

                if (g_activeTouchPoints == 0)
                {
                    DCompositionBoostCompositorClock(false);
                }
            }

            // Perform other normal touch processing here...
        }

        // We handled the window message; close the handle
        CloseTouchInputHandle(hTouchInput);
    }
    else
    {
        // We couldn't handle the message; forward it to the system
        result = DefWindowProc(hWnd, WM_TOUCH, wParam, lParam);
    }

    delete[] pInputs;
    return result;
}

Framestatistiken

Hinweis

Wir erwarten, dass Anwendungen das Framestatistikfeature hauptsächlich für Telemetriedaten und nicht für die Anpassung von Inhalten verwenden.

Windows-Anwendungen übermitteln häufig Inhalte an den Compositor, der an einer Vielzahl von Stellen auf Anzeigeadaptern und Bildschirmen angezeigt wird. Wir rendern nicht immer auf einem Bildschirm, weshalb wir in dieser API Ziele verwenden. Anstatt sich auf eine einzelne Statistik zu verlassen, die darstellt, wenn ein Frame auf den Bildschirm trifft, bietet DCompositionGetTargetStatistics Framestatistiken für jeden Compositor-Frame, wenn er jedes Ziel erreicht. Der Compositor arbeitet in regelmäßigen Abständen, was auf einem Vblank auftreten kann oder nicht. Dies bedeutet, dass die Anwendung, das Framework oder die Telemetrie alles berücksichtigen können, wenn eine Anzeige dupliziert wird oder ein Inhalt an mehreren Stellen angezeigt wird. Diese Compositorframes bieten jedoch unvollständige Informationen zu Frames, die nicht zusammengesetzt sind, z. B. in iflip (independent flip) in einer Swapchain.

Als Beispiel für die Verwendung basiert die neue Media Foundation-Infrastruktur, die auf der Kompositions-Swapchain basiert, sowohl auf DCompositionGetStatistics als auch auf DCompositionGetTargetStatistics , um die Qualität der zusammengesetzten Präsentation mithilfe von Telemetriedaten zu bestimmen. Zusätzlich zu dieser API rufen sie eine separate API auf, wenn sich ihre Frames in iflip befinden und nicht zum Compositor wechseln.

Für bestimmte Verwendungen wird erwartet, dass Anwendungen IDCompositionDevice::GetFrameStatistics verwenden, um eine Schätzung zu erhalten, wann der nächste Compositorframe kommt, indem DCOMPOSITION_FRAME_STATISTICS:nextEstimatedFrameTime überprüft wird.

Zunächst fragt die Anwendung den letzten Frame ab, der sich auf die status der Darstellung des Frames durch verschiedene Ausdrücke bezieht. Die Anwendung verfügt entweder über eine vorhandene frameId , die von der Kompositions-Swapchain bereitgestellt wird, oder zukünftige Schnittstellen, zu denen sie Informationen benötigen, oder sie kann DCompositionGetFrameId aufrufen, um die neueste COMPOSITION_FRAME_ID der angegebenen COMPOSITION_FRAME_ID_TYPE abzurufen.

  • COMPOSITION_FRAME_ID_CREATED. Der Compositor hat mit der Arbeit am Frame begonnen.
  • COMPOSITION_FRAME_ID_CONFIRMED. Die Frame-ID, in der die CPU-Arbeit abgeschlossen ist und alle Geschenke stattgefunden haben.
  • COMPOSITION_FRAME_ID_COMPLETED. Die GPU-Arbeit ist für alle Ziele abgeschlossen, die einem Frame zugeordnet sind.

Hinweis

COMPOSITION_Frame_ID nimmt monoton zu; daher können frühere Compositorframes daraus abgeleitet werden.

Als Nächstes fragt die Anwendung grundlegende Informationen zum Kompositionsrahmen und eine Liste der targetIDsab, die Teil des Frames sind, indem DCompositionGetStatistics aufgerufen wird. Wenn die Anwendung schließlich Zielinformationen benötigt, verwendet sie DCompositionGetTargetStatistics , um Informationen für die angegebene frameId und targetId abzurufen.

C++-Beispiel

Das folgende Beispiel zeigt eine parallele Auflistung von Framestatistiken aus der API, die dann in der TargetFrameRate-Funktion zusammengefasst werden, um abzuleiten, welche Framerate über einer Reihe von Frames lag. Auch hier wird diese Art von Code in Telemetriedaten oder in Frameworks und nicht in einer Anwendung erwartet.

class FrameStatisticsCollector
{
private:
    // Collect at most 4 target monitors
    static constexpr UINT sc_maxTargetCount = 4;

    struct CompositionTargetStats
    {
        COMPOSITION_FRAME_ID frameId;
        COMPOSITION_FRAME_STATS frameStats;

        COMPOSITION_TARGET_ID targetId;
        COMPOSITION_TARGET_STATS targetStats;
    };

    UINT64 m_qpcFrequency;
    COMPOSITION_FRAME_ID m_lastCollectedFrameId = 0;
    std::vector<CompositionTargetStats> m_targetStats;

public:
    FrameStatisticsCollector()
    {
        QueryPerformanceFrequency(&m_qpcFrequency);
        m_lastCollectedFrameId = CurrentFrameId();
    }

    // Queries the compositor clock statistics API to determine the last frame
    // completed by the composition engine
    COMPOSITION_FRAME_ID CurrentFrameId() const
    {
        COMPOSITION_FRAME_ID frameId;
        if (FAILED(_DCompositionGetFrameId(frameIdType, &frameId)))
        {
            frameId = 0;
        }

        return frameId;
    }

    // Queries the system to get information about the latest composition frames
    void CollectStats()
    {
        COMPOSITION_FRAME_ID currentFrameId = CurrentFrameId(COMPOSITION_FRAME_ID_COMPLETED);

        while (m_active && (currentFrameId > m_endFrameId))
        {
            auto newEndFrameId = m_endFrameId + 1;

            COMPOSITION_FRAME_STATS frameStats = {};
            COMPOSITION_TARGET_ID targetIds[sc_maxTargetCount] = {};
            UINT targetCount;

            hr = _DCompositionGetStatistics(newEndFrameId,
                &frameStats,
                _countof(targetIds),
                targetIds,
                &targetCount);
            if (SUCCEEDED(hr))
            {
                // We track up to sc_maxTargetCount targets per frame
                targetCount = min<UINT>(targetCount, _countof(targetIds));

                for (UINT uIndex = 0; uIndex < targetCount; uIndex++)
                {
                    COMPOSITION_TARGET_STATS targetStats = {};
                    hr = DCompositionGetTargetStatistics(newEndFrameId,
                        &targetIds[uIndex],
                        &targetStats);
                    if (SUCCEEDED(hr))
                    {
                        CompositionTargetStats compTargetStats = { newEndFrameId,
                                                                  frameStats,
                                                                  targetIds[uIndex],
                                                                  targetStats };

                        m_compTargetStats.push_back(compTargetStats);
                    }
                    else
                    {
                        m_active = false;
                    }
                }

                m_endFrameId = newEndFrameId;
            }
            else
            {
                m_active = false;
            }
        }
    }

    // Compute the frame rate for the given composition target in frames per
    // second, over the specified frame interval based on historical statistics
    // data
    float TargetFrameRate(
        _const COMPOSITION_TARGET_ID& targetId,
        COMPOSITION_FRAME_ID beginFrameId,
        COMPOSITION_FRAME_ID endFrameId)  const
    {
        UINT frameCount = 0;
        UINT64 beginTime = 0;
        UINT64 endTime = 0;

        for (const auto& stats : m_compTargetStats)
        {
            if ((stats.frameId >= beginFrameId) && (stats.frameId <= endFrameId))
            {
                if (stats.frameId == beginFrameId)
                {
                    beginTime = stats.frameStats.startTime;
                }

                if (stats.frameId == endFrameId)
                {
                    endTime = stats.frameStats.startTime +
                        stats.frameStats.framePeriod;
                }

                if ((stats.targetId == targetId) &&
                    (stats.targetStats.presentTime != 0))
                {
                    frameCount++;
                }
            }
        }

        if ((beginTime != 0) &&
            (endTime != 0) &&
            (endTime > beginTime) &&
            (frameCount != 0))
        {
            auto seconds = static_cast<float>(endTime - beginTime) /
                static_cast<float>(m_qpcFrequency);

            return static_cast<float>(frameCount) / seconds;
        }
        else
        {
            return 0.0f;
        }
    }
};

Glossar

  • Ziel: Eine Bitmap, in die die Kompositions-Engine die visuelle Struktur rastert. Diese Bitmap ist in der Regel eine Anzeige.
  • Compositorframe. Ein Compositor-Arbeitszyklus – dies ist nicht unbedingt ein Vblank.