Remarque
L’accès à cette page requiert une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page requiert une autorisation. Vous pouvez essayer de modifier des répertoires.
Aperçu
L’API horloge compositor offre des statistiques et un contrôle de fréquence d’images pour présenter du contenu à l’écran en douceur, à la cadence la plus rapide possible et sur une variété de configurations matérielles. Traditionnellement, cela a été géré par les API DirectX. Mais ceux-ci ont des liens forts avec le taux d’actualisation fixe et les configurations d’affichage unique. Par exemple, voici un élément simplifié de pseudo-code montrant comment les applications sont généralement créées pour dessiner au taux d’actualisation d’affichage.
void GameLoop()
{
CreateRenderingObjects();
auto pSwapChain = CreateSwapChain();
while (pSwapChain->WaitForVerticalBlank())
{
ProcessInput();
RenderFrame(pSwapChain);
pSwapChain->Present();
}
}
Dans ce type de boucle, l’hypothèse est qu’il existe une seule cadence verticale vide (vblank). Il n’est pas clair ce que l’application doit faire si sa fenêtre dépasse deux moniteurs dont l’analyse est hors phase, ou qui ont des fréquences différentes. En fait, l’API DXGI swapchain utilise toujours la cadence du moniteur principal, quelle que soit la fenêtre sur laquelle l’application s’affiche. Cela provoque des problèmes pour les applications qui souhaitent une présentation fluide sur tous les moniteurs. Un exemple réel est la lecture vidéo sur un moniteur secondaire qui a une actualisation différente de celle du réplica principal ; un scénario qui existait depuis l’introduction de plusieurs moniteurs ; et il affecte disproportionnéement les joueurs, qui ont tendance à avoir un moniteur de 60Hz pour l’interface utilisateur secondaire, et une fréquence beaucoup plus élevée (144+Hz) pour les jeux.
Le deuxième problème courant est celui de l’ajustement de la fréquence d’images en fonction des performances de l’ordinateur. Il s’agit généralement d’applications de lecture vidéo, qui veulent savoir si les images vidéo sont vues par l’utilisateur à l’heure prévue, ou si des problèmes font la présentation inégale, dans l’espoir d’ajuster la présentation pour de meilleures performances. Par exemple, un service de streaming vidéo peut basculer vers un flux de qualité inférieure si l’ordinateur n’est pas capable de maintenir la fréquence d’images souhaitée à la plus haute qualité. Cela est également géré par les API DXGI et est donc affecté par la même limitation d’exposition de l’ARCHITECTURE et de l’API.
Enfin, l’API offre aux applications la possibilité de participer à une nouvelle fonctionnalité de boosting de fréquence d’images appelée Taux d’actualisation dynamique, dans laquelle le système s’exécute à un taux d’actualisation relativement faible pour les opérations normales, par exemple 60Hz, mais il accélère jusqu’à une fréquence plus élevée ( par exemple, 120Hz) lorsqu’une application effectue certaines opérations sensibles à la latence, telles que l’entrée manuscrite avec un stylet, ou mouvement panoramique tactile. La fonctionnalité d’augmentation existe, car l’exécution à haute fréquence 100% du temps est prohibitive d’un point de vue de consommation d’énergie. En même temps, en raison des mêmes limitations de l’API DXGI, le basculement du taux d’actualisation de l’affichage à des moments arbitraires est normalement coûteux, impliquant des notifications de modification du mode de diffusion sur toutes les applications et les coûts de toutes ces applications exécutant du code pour répondre à la modification. Par conséquent, la fonctionnalité d’amélioration du taux d’actualisation effectue une modification de configuration légère qui n’émet aucune notification, mais, par conséquent, doit être extraite de la plupart des applications, ce qui continue de croire que le système s’exécute à la fréquence inférieure. Cette virtualisation fonctionne en émettant uniquement des applications toutes les autres vblank, ou toutes les trois vblanks, ou tout autre intervalle entier, afin que l’application voit un taux d’actualisation effectif qui est une fraction entière de la fréquence réelle. Cela permet au mécanisme vblank existant d’être utilisé sans coût supplémentaire pour générer une fréquence normalement inférieure. Le vblank aligné est représenté par un mode de fréquence d’actualisation dynamique dans le système d’exploitation, tel que 60Hz/120Hz. Notez que, par conséquent, la fonctionnalité boost fonctionne uniquement pour augmenter jusqu’à une fréquence plus élevée, jamais à une fréquence inférieure, car il n’est pas tout aussi bon marché d’insérer des vblanks artificiels car il est d’ignorer les vblanks réels.
L’API horloge compositor permet à votre application non seulement de demander au système d’entrer ou de quitter le mode boost, mais également d’observer le taux d’actualisation réel lorsqu’il est en mode, afin que vous puissiez présenter du contenu à la fréquence supérieure.
L’API
Il existe trois parties à l’API. La première offre une pulsation indépendante de l’affichage pour les applications qui souhaitent présenter à la fréquence d’images sur plusieurs moniteurs. La deuxième permet aux applications de demander une augmentation de fréquence avec le taux d’actualisation dynamique. La troisième offre des statistiques sur le comportement du moteur de composition système, séparées pour chaque affichage individuel.
Chaque partie de l’API influence ou observe le cycle de travail du compositeur système. Ce cycle de travail est une cadence régulière qui produit une trame compositor par cycle. Ce cycle peut ou non être aligné pour afficher des vblanks, en fonction de la charge de travail système, du nombre d’affichages et d’autres facteurs.
Attendez l’horloge du compositeur
L’objectif de ce signal est de remplacer l’utilisation du IDXGIOutput ::WaitForVBlank méthode, tout en offrant une plus grande flexibilité dans différents taux d’actualisation et en simplifiant les modèles d’utilisation pour les développeurs. Comme avec WaitForVBlank, le système doit savoir si une application attend ce signal ou non, de sorte qu’en l’absence d’applications attendent que le système puisse diriger la carte vidéo pour désactiver l’interruption vide verticale.
Cela est essentiel pour la gestion de l’alimentation, ce qui limite l’architecture de l’API à être un appel de fonction d’attente, plutôt que d’accepter ou de retourner un événement (le système graphique ne peut pas déterminer s’il est attendu ou non). À ce bas niveau, les applications sont censées utiliser cette API pour contrôler les threads de rendu, séparés des threads d’interface utilisateur à usage général, comme IDXGIOutput ::WaitForVBlank est traditionnellement utilisé.
Comme mentionné dans la vue d’ensemble, il existe plusieurs aspects que l’horloge compositor peut prendre en charge pour cette WaitForVBlank ne peut pas.
- Vide vertical suivant lorsque l’horloge du compositeur n’est pas nécessairement source de l’affichage principal.
- Réveil des applications à des taux variables sur les écrans qui prennent en charge le taux d’actualisation dynamique.
- Réveil des applications pour les événements définis par l’application.
En règle générale, il est attendu que de nombreuses applications souhaitent rester synchronisées avec l’horloge du compositeur pour optimiser leur temps ; mais certaines exceptions peuvent inclure des frameworks multimédias et des jeux qui doivent se réveiller sur le vide vertical d’un affichage spécifique.
Gérer l’utilisation avec l’horloge compositor
Les applications se réveillent actuellement à chaque vide vertical par le biais du mécanisme de DXGI, mais ont souvent d’autres événements pour lesquels ils doivent également se réveiller. Au lieu de gérer ces événements séparément, l’horloge du compositeur peut prendre des handles pour plusieurs événements, et signaler sur l’image suivante et chaque fois que les événements se déclenchent. L’application peut ensuite se réveiller d’un signal, sachant l’événement qui l’a provoqué à se réveiller.
Cycle pour les événements d’horloge de compositeur
L’horloge du compositeur se réveille toujours à l’espace vertical d’un moniteur ou sur un autre minuteur. Lorsque le compositeur est endormi, mais que l’affichage est toujours mis à jour, ce signal est toujours déclenché sur le vblank de l’affichage principal.
Exemple C++
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 clock
Lorsque la source de l’horloge du compositeur prend en charge le taux d’actualisation dynamique (cette fonctionnalité est activée dans les paramètres d’affichage avancés ; utilisable uniquement sur les affichages de taux d’actualisation variable avec les pilotes de support), le système peut basculer dynamiquement entre deux taux. Il existe un mode non optimisé, qui sera généralement de 60Hz et un taux boosté qui est généralement 2x plus élevé à 120Hz. Ce taux d’actualisation plus élevé doit être utilisé pour améliorer le contenu sensible à la latence, tel que l’entrée manuscrite numérique. Le diagramme ci-dessous montre comment le système bascule entre l’exécution à un débit de base de 60Hz (retourne 1), puis pour 6 images (2 à 7) avec l’encre numérique chronométrée à 120Hz. Enfin, une fois que l’encre numérique n’est plus mise à jour, le système passe en mode 60Hz.
Voici une illustration de la vitesse de trame dynamique pour l’amélioration.
Et voici comment DWM gère les demandes de boost.
diagramme de flux 
Si une application demandant un boost est arrêtée, les demandes de boost de l’application sont également arrêtées. Les applications qui sont toujours actives avec plusieurs requêtes boost peuvent vérifier le nombre de références pour déterminer le nombre de fois pour annuler l’activation. Les appels de boosting sont entièrement compatibles, même si le système n’est pas en mode taux d’actualisation dynamique, où le multiplicateur d’augmentation serait 1x.
Exemple C++
Cet exemple traite WM_TOUCH pour améliorer le taux d’actualisation chaque fois que cette application reçoit une entrée tactile, avec l’intention de fournir une expérience de panoramique tactile plus fluide et à haute fréquence. Une application plus sophistiquée peut d’abord effectuer la reconnaissance des mouvements et augmenter uniquement si un panoramique est détecté.
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;
}
Statistiques de trame
Note
Nous nous attendons à ce que les applications utilisent principalement la fonctionnalité de statistiques d’images pour la télémétrie, et non pour l’ajustement du contenu.
Les applications Windows envoient souvent du contenu au compositeur qui s’affiche dans divers emplacements entre les adaptateurs d’affichage et les écrans. Nous ne nous rendons pas toujours à un écran, c’est pourquoi, dans cette API, nous utilisons des cibles . Au lieu de compter sur une statistique unique pour représenter lorsqu’une image a atteint l’écran, DCompositionGetTargetStatistics offre des statistiques d’images pour chaque frame compositor Frame lorsqu’elle atteint chaque cible. Le compositeur fonctionne régulièrement, ce qui peut se produire sur un vblank ou non. Cela signifie que si un affichage est dupliqué ou qu’un contenu est affiché à plusieurs emplacements, l’application, l’infrastructure ou la télémétrie peuvent prendre en compte tout cela. Toutefois, ces trames de compositeur fournissent des informations incomplètes sur les images qui ne sont pas composées, telles que dans iflip (retournement indépendant) sur une chaîne d’échange.
Par exemple, la nouvelle infrastructure Media Foundation basée sur la chaîne d’échange de composition repose sur les deux DCompositionGetStatistics et DCompositionGetTargetStatistics pour déterminer la qualité de la présentation composée par le biais de la télémétrie. En plus de cette API, ils appellent une API distincte lorsque leurs images sont en iflip et ne vont pas au compositeur.
Pour certaines utilisations, nous nous attendons à ce que les applications utilisent IDCompositionDevice ::GetFrameStatistics pour recevoir une estimation de l’heure à laquelle le prochain frame de compositeur viendra en vérifiant DCOMPOSITION_FRAME_STATISTICS ::nextEstimatedFrameTime.
Tout d’abord, l’application interroge la dernière image relative à l’état de la présentation de l’image par le biais de différentes expressions. L’application dispose d’un frameId existant fourni par la chaîne d’échange de composition, ou des interfaces futures à propos de laquelle il souhaite des informations, ou il peut appeler DCompositionGetFrameId pour récupérer la COMPOSITION_FRAME_ID la plus récente du COMPOSITION_FRAME_ID_TYPEspécifié.
- COMPOSITION_FRAME_ID_CREATED. Le compositeur a commencé à travailler sur le cadre.
- COMPOSITION_FRAME_ID_CONFIRMED. ID d’image dans lequel le travail du processeur est terminé et tous les présentations ont eu lieu.
- COMPOSITION_FRAME_ID_COMPLETED. Le travail GPU est effectué pour toutes les cibles associées à une trame.
Note
COMPOSITION_Frame_ID augmente de façon monotonique ; ainsi, les trames de compositeur précédentes peuvent être déduites à partir de celle-ci.
Ensuite, l’application demande des informations de base sur le cadre de composition et une liste de targetIds qui font partie de l’image, en appelant DCompositionGetStatistics. Enfin, si l’application requiert des informations par cible, elle utilise DCompositionGetTargetStatistics récupérer des informations pour l’id de frameId et le targetId spécifiés.
Exemple C++
L’exemple suivant montre une collection propagée de statistiques d’images de l’API, qui sont ensuite résumées dans la fonction TargetFrameRate pour déduire ce que la fréquence d’images était sur un ensemble d’images. Là encore, ce type de code est attendu dans la télémétrie ou dans les frameworks, plutôt que dans une application.
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;
}
}
};
Glossaire
- cible . Bitmap dans laquelle le moteur de composition rastérise l’arborescence visuelle. Cette bitmap est généralement un affichage.
- cadre compositor. Un cycle de travail compositor , ce n’est pas nécessairement un vblank.