Skip to main content

Introduction

Cet article offre une vue d’ensemble des API DirectStorage pour les consoles XBOX Series X|S seulement. Consultez DirectStorage sur ordinateur de bureau pour obtenir des détails sur DirectStorage sur ordinateur de bureau. Les plus récents périphériques de stockage NVMe connectés au moyen d’un bus PCIe peuvent atteindre des niveaux très élevés de débit et d’IOPS (requêtes d’E/S par seconde). La surcharge des API Win32 signifie que, même si la bande passante de stockage disponible peut être utilisée, en tirer parti pourrait entraîner une utilisation du processeur inacceptablement élevée. C’est particulièrement vrai lorsque la charge de travail se compose d’un grand nombre de petites requêtes. Les API DirectStorage sont conçues pour éliminer la majeure partie de la surcharge du système d’exploitation en interagissant étroitement avec le matériel NVMe sous-jacent. Cela permet d’obtenir une bande passante plus élevée avec une utilisation moindre du processeur. L’objectif est de permettre le traitement de jusqu’à 50 000 requêtes par seconde tout en utilisant au plus 10 % d’un seul cœur de processeur.

Problèmes existants

Le contenu des jeux devient de plus en plus volumineux à mesure que le besoin de ressources à plus haute résolution augmente à chaque génération de consoles. Le matériel et le logiciel XBOX One existants présentent plusieurs limitations qui nuisent à la capacité des développeurs à transférer les données du disque dur vers la mémoire pour ce contenu de nouvelle génération.
  • Utilisation élevée du processeur
    • Les API Win32 existantes pourraient nécessiter un cœur de processeur complet en surcharge.
    • Cela dépend du nombre de requêtes du titre.
  • Bande passante maximale insuffisante du disque
  • Impossibilité de prioriser les requêtes de disque
    • Sans la capacité de prioriser les requêtes du titre, il peut être difficile de créer un système de diffusion en continu réactif.
  • Impossibilité d’annuler les requêtes de disque
    • Sans la capacité d’annuler des requêtes, il peut être difficile de créer un système de lecture spéculative.
  • Aucune décompression accélérée par le matériel
    • Sans décompression accélérée par le matériel, la décompression logicielle peut exiger beaucoup de ressources processeur.
L’ensemble d’API DirectStorage règle directement chacun de ces problèmes. L’effet global est une augmentation radicale des performances du système de fichiers XBOX.

Utilisation du processeur

Le principal objectif de conception de DirectStorage est de permettre à un titre de soutenir 50 000 IOPS en n’utilisant qu’entre 5 % et 10 % d’un seul cœur de processeur. Cela permet au titre d’obtenir la bande passante maximale du sous-système de stockage NVMe, tout en permettant au processeur d’être utilisé pour d’autres besoins du titre. DirectStorage ajoute également la prise en charge de la décompression matérielle. Chaque requête de lecture peut être acheminée directement du lecteur NVMe vers le bloc de décompression matérielle intégré. Cela élimine le besoin pour un titre de consacrer des ressources processeur à la décompression.

Modèle de pipeline en file d’attente

DirectStorage utilise une méthode par lots, dans laquelle plusieurs requêtes sont ajoutées à une file d’attente. Plus tard, la file d’attente est vidée vers l’étape suivante du pipeline. Cela réduit immédiatement le coût processeur global d’une transition entre les étapes du pipeline. Avec l’ensemble d’API Win32 existant, il y a une transition pour chaque requête. Les files d’attente DirectStorage utilisent des algorithmes sans verrou pour réduire la contention. Le titre contrôle le moment où chaque file d’attente est vidée. Dans de nombreux cas avec les API Win32, les données du disque pourraient devoir être copiées dans une autre mémoire tampon. Dans certains cas, les données pourraient devoir être copiées plus d’une fois. DirectStorage élimine ce problème en mappant la mémoire tampon de destination fournie par le titre directement dans chaque couche du pipeline. Le matériel écrit directement dans la mémoire tampon fournie par le titre. Ces changements contribuent à des réductions importantes de la surcharge du processeur.

Décompression

La capacité du matériel à décompresser les données a été accrue. Il peut maintenant gérer une plus grande variété de formats, à une vitesse supérieure à celle à laquelle le sous-système NVMe peut fournir les données. De plus, DirectStorage prend en charge la décompression sur place, ce qui élimine le besoin de gérer des mémoires tampons distinctes pour les données compressées et décompressées. Le matériel prend en charge BCPACK et DEFLATE, et offre la capacité de réorganiser (swizzle) le contenu final. Ces formats ne sont pas mutuellement exclusifs. Il est possible d’appliquer les trois aux données. Cela donne au titre la capacité de choisir la méthode qui lui offre le meilleur taux de compression et les meilleures performances. Différentes ressources peuvent utiliser différents paramètres de compression et de réorganisation.

Profondeur de file d’attente

La recommandation précédente était de ne maintenir que 12 à 16 requêtes asynchrones en cours à la fois sur un disque rotatif. Aller au-delà n’apportait aucun avantage sur les performances, et aller en deçà nuisait considérablement aux performances. Cela amenait les titres à effectuer du travail supplémentaire pour équilibrer leurs requêtes de lecture en attente afin de rester dans la cible recommandée. Avec l’objectif de DirectStorage de permettre à un titre d’atteindre 50 000 IOPS, notre recommandation a changé. Le titre n’a plus besoin d’essayer d’équilibrer le travail en attente et la profondeur de file d’attente. Le titre devrait soumettre toutes ses requêtes en attente. Il n’y a aucun avantage à retenir certaines requêtes. Dans de nombreux cas, retenir des requêtes pourrait nuire aux performances, car le matériel est bloqué en attendant de nouvelles requêtes. Le système d’exploitation doit encore, dans certains cas, diviser les grandes requêtes de lecture en plusieurs requêtes plus petites (par exemple, pour gérer la fragmentation du disque). Toutefois, cela a été pris en compte dans l’architecture de DirectStorage. L’objectif de conception de 50 000 IOPS est basé sur le nombre d’opérations d’E/S du titre et non sur les requêtes finales envoyées au matériel.

Notification

Dans l’architecture Win32, une quantité importante de surcharge est consacrée aux notifications de fin de lecture. Le titre peut interroger une structure OVERLAPPED, attendre un handle d’Event associé, ou effectuer une lecture bloquante synchrone. Globalement, cela augmente les besoins en ressources de chaque requête de lecture. DirectStorage conserve les deux concepts asynchrones de notification tout en ajoutant une troisième méthode. Les lectures bloquantes synchrones ne sont pas prises en charge dans DirectStorage. Il est possible pour un titre d’implémenter son propre système. Toutefois, ce n’est pas recommandé. La première méthode asynchrone est implémentée au moyen d’un bloc d’état qui est défini lorsque les requêtes associées sont terminées. Le titre peut interroger le bloc au besoin pour déterminer quand les lectures sont terminées. C’est semblable à la méthode Win32 qui consiste à interroger une structure OVERLAPPED pour vérifier la fin. La deuxième méthode asynchrone utilise un objet Event Windows pour signaler la fin. C’est semblable à l’utilisation d’une structure OVERLAPPED avec un objet Event correspondant. Le titre peut utiliser la méthode WaitForSingleObject pour suspendre le thread appelant jusqu’à ce que les opérations de lecture soient terminées. La troisième méthode asynchrone est implémentée au moyen d’un ID3D12Fence. Le titre peut se suspendre en attendant la clôture ou interroger la clôture au besoin. Il y a également l’avantage que le GPU peut utiliser la clôture pour être notifié directement des requêtes terminées. Le système de notification de DirectStorage n’est pas lié à une seule requête de lecture. Il s’agit d’une entrée placée dans la file d’attente, qui est signalée lorsque toutes les requêtes de lecture précédentes sont terminées. Cela donne au titre le contrôle sur le degré de granularité dont il a besoin pour les notifications. Les notifications sont toujours signalées dans l’ordre de la file d’attente. La file d’attente peut être considérée comme une file FIFO (premier entré, premier sorti). Le titre n’a qu’à interroger la dernière notification pertinente. Toutes les requêtes mises en file d’attente précédemment sont garanties d’être terminées.

Décompression de mémoire à mémoire

DirectStorage fournit un type de file d’attente permettant d’invoquer le matériel de décompression avec une source de décompression en mémoire plutôt qu’un fichier sur disque. Cela permet d’utiliser le matériel de décompression si la ressource compressée ne provient pas d’un fichier ou si elle a été lue précédemment et conservée en mémoire comme cache. Une file d’attente dont la source est la mémoire n’accepte que des requêtes dont la source est la mémoire, et une file d’attente dont la source est un fichier n’accepte que des requêtes dont la source est un fichier. Si aucune option de décompression n’est spécifiée dans une requête dont la source est la mémoire, le matériel de décompression peut également agir comme moteur de copie DMA. Bien que DirectStorage garantisse que les notifications de fin sont dans l’ordre, DirectStorage ne fournit aucune garantie quant au moment où le traitement des requêtes commence. Par conséquent, il ne doit y avoir aucune dépendance de données entre les requêtes en attente. Autrement dit, la destination de la requête A ne peut pas servir de source à la requête B, à moins que la requête B ne soit mise en file d’attente après la fin de la requête A. Les files d’attente dont la source est la mémoire doivent être créées avec la priorité en temps réel. De plus, les requêtes en temps réel dont la source est la mémoire sont toujours traitées par le matériel de décompression avant les requêtes dont la source est le disque qui nécessitent une décompression. Si les files d’attente dont la source est le disque ne contiennent aucune requête de décompression, les deux types de files d’attente sont traités entièrement en parallèle sans affecter l’autre type.

Priorité

DirectStorage permet d’attribuer un niveau de priorité à chaque file d’attente. Chaque entrée de la file d’attente hérite de la priorité de la file d’attente. Quatre niveaux de priorité différents sont fournis : temps réel, élevée, normale et basse. Les requêtes sont traitées selon un tourniquet pondéré. Par exemple, traiter X requêtes de priorité élevée avant de traiter une requête de priorité normale. Traiter Y requêtes de priorité normale avant de traiter une requête de priorité basse. La pondération des priorités est calculée en fonction de la taille de chaque requête. La pondération par défaut entre chaque priorité est d’environ 10x. Cela signifie que pour chaque 1 Ko de requête de priorité basse, 10 Ko de requête de priorité moyenne et 100 Ko de requête de priorité élevée auraient été traités. Les requêtes de lecture Win32 existantes sont acheminées par le même système de priorité. Toutes les requêtes Win32 sont considérées comme étant de priorité normale. Les files d’attente dont la source est la mémoire doivent être créées avec la priorité en temps réel.

Annulation

Chaque requête de lecture DirectStorage est associée à un masque de 64 bits fourni par le titre. Cela permet de prendre en charge l’annulation des requêtes de lecture en attente. Le titre peut annuler les requêtes qui correspondent à un ensemble précis d’indicateurs dans le masque. Même avec la prise en charge de l’annulation, il est toujours possible qu’une requête de lecture soit traitée par le matériel. La requête d’annulation du titre est une tentative au mieux. Si la requête est déjà en cours de traitement par le matériel, elle ne peut pas être annulée. Comme la requête d’annulation est effectuée au mieux, le titre doit attendre d’être notifié que le traitement de la requête de lecture est terminé. Le titre ne peut libérer aucune des ressources nécessaires avant de recevoir une notification ultérieure dans la file d’attente. Toutefois, pendant ce temps, de nouvelles requêtes correspondant aux indicateurs qui ont été utilisés dans une requête d’annulation précédente peuvent être mises en file d’attente et ne seront pas annulées. Lorsqu’une requête annulée se termine, elle est considérée comme une réussite, même si elle a été annulée et n’a pas produit le résultat complet. Autrement dit, si une annulation est tentée sur une requête, le titre ne peut plus utiliser le résultat de la requête potentiellement annulée une fois celle-ci terminée.

Garantie

Les consoles XBOX One et XBOX One S avaient une garantie minimale de 40 Mo/s. La console XBOX One X a augmenté la garantie minimale à 60 Mo/s. Ces chiffres sont bien inférieurs aux limites réelles du matériel, qui se situent autour de 130 Mo/s. Cela était entièrement dû à la surcharge causée par le système d’exploitation. DirectStorage élimine la majeure partie de la surcharge causée par le système d’exploitation. Cela permet une garantie minimale plus proche des limites du matériel. La nouvelle garantie de performance minimale est de 2,0 Go/s sur une fenêtre de 250 ms pour les données brutes. L’utilisation de la décompression sur le contenu permettra d’augmenter encore la bande passante finale. Les futures consoles XBOX prendront en charge l’ajout de lecteurs dynamiques installables par l’utilisateur qui sont également basés sur NVMe. La même garantie de performance minimale que celle fournie pour le lecteur interne est également fournie pour le lecteur installable par l’utilisateur.

Vue d’ensemble de l’API

Les interfaces DirectStorage suivent le même modèle que les interfaces Direct3D. Le titre obtient d’abord une fabrique singleton. La fabrique sert à créer des files d’attente de requêtes et à ouvrir des fichiers; chacun de ces objets est mappé directement au matériel. Les requêtes individuelles sont ensuite mises dans une file d’attente pour être soumises au matériel.

IDStorageFactoryX

IDStorageFactoryX est l’interface principale pour créer des files d’attente, ouvrir des fichiers et soumettre des requêtes en attente. L’objet IDStorageFactoryX possède les méthodes suivantes.
  • OpenFile
    • Crée un objet IDStorageFileX , qui représente un fichier.
  • CreateQueue
    • Crée un objet IDStorageQueueX. Sert à créer des requêtes de lecture.
  • CreateStatusArray
    • Crée un objet IDStorageStatusArray, qui gère les indicateurs d’état de fin.
  • SetCPUAffinity
    • Limite le travail de DirectStorage effectué hors du thread appelant à un ensemble de cœurs de processeur défini par le titre.
    • REMARQUE DirectStorage essaie d’effectuer la majeure partie du travail dans le thread appelant. Le travail hors du thread appelant ne se produit que lorsqu’il ne peut pas être effectué dans le thread appelant. Voici des exemples.
      • Le pipeline de ressources sous-jacent est plein pendant IDStorageQueueX::Submit, et les requêtes de la file d’attente ne peuvent pas toutes être poussées vers l’avant. Les requêtes restantes seront traitées plus tard, lorsque les ressources se libéreront, et ce traitement est effectué dans un thread de travail DirectStorage.
      • Traitement de la fin des requêtes dans ID3DFence ou IDStorageStatusArray.
  • SetDebugFlags
    • Contrôle si DirectStorage effectue des validations supplémentaires au moment de la mise en file d’attente des requêtes pour faciliter le débogage.
  • SetStagingBufferSize
    • Définit la taille de la mémoire tampon intermédiaire utilisée pour stocker temporairement le contenu chargé à partir du périphérique de stockage avant qu’il soit déchiffré/décompressé. Si seules des files d’attente dont la source est la mémoire sont utilisées, la mémoire tampon intermédiaire peut avoir une taille de 0.

IDStorageFactoryX1

L’interface IDStorageFactoryX1 étend l’interface IDStorageFactoryX avec une méthode GetStats.
  • GetStats
    • Obtient les statistiques de DirectStorage. Cette fonction peut servir à intégrer DirectStorage aux pipelines de diagnostic et de télémétrie existants. Elle effectue un traitement minimal et peut donc être appelée souvent. Les statistiques n’incluent pas les opérations d’E/S de fichiers Win32.

IDStorageFactoryX2

L’interface IDStorageFactoryX2 étend l’interface `IDStorageFactoryX1 avec une méthode CreateQueue1.
  • CreateQueue1
    • Crée un objet IDStorageQueueX2. Utilise une nouvelle structure pour permettre des options supplémentaires lors de la création de la file d’attente, y compris la capacité de remplacer la fonctionnalité de soumission automatique d’une file d’attente.

IDStorageFileX

Tous les fichiers doivent d’abord être ouverts par DirectStorage au moyen de l’objet IDStorageFactoryX. C’est l’équivalent de l’utilisation de CreateFile dans les interfaces API Win32. Les fichiers sont ouverts avec l’autorisation FILE_SHARED_READ. Au besoin, les titres peuvent ouvrir simultanément le fichier au moyen des API Win32, à condition que les autorisations appropriées soient respectées. Pendant le développement, les déploiements libres et empaquetés sont pris en charge. Les fichiers sont fermés soit en appelant explicitement la fonction Close sur l’objet fichier, soit lorsque la dernière référence à l’objet IDStorageFileX correspondant est libérée. Toutefois, toutes les opérations d’E/S en cours doivent être terminées avant que le fichier puisse être fermé. Cela signifie que les deux méthodes de fermeture d’un fichier bloquent jusqu’à ce que toutes les opérations d’E/S en cours sur ce fichier soient terminées. Le jeu peut obtenir un handle Win32 vers un fichier représenté par un objet IDStorageFileX en appelant la fonction GetHandle. Le handle est ouvert avec les autorisations GENERIC_READ et le mode de partage FILE_SHARE_READ. Il peut servir à interroger la taille du fichier, etc. Le handle doit être fermé avec CloseHandle() lorsqu’il n’est plus nécessaire.

IDStorageQueueX

Les requêtes de lecture sont soumises au NVMe au moyen d’un objet IDStorageQueueX. Toutefois, les requêtes ne sont pas soumises au périphérique tant que le titre n’appelle pas Submit sur la file d’attente, ou tant qu’une des méthodes Enqueue n’a pas rempli plus de la moitié de la capacité de la file d’attente depuis la dernière soumission, déclenchant une soumission automatique. Les soumissions sont traitées comme une seule transition vers l’étape suivante du pipeline. Cela permet au titre de contrôler le moment où se produit le coût processeur de la transition entre le titre et le noyau. Les objets IDStorageQueueX possèdent quatre propriétés.
  • SourceType
    • Spécifie si la file d’attente peut recevoir des requêtes dont la source est un fichier ou des requêtes dont la source est la mémoire.
  • Priority
    • La priorité de toutes les requêtes soumises à la file d’attente : temps réel, élevée, normale ou basse.
    • Les files d’attente dont la source est la mémoire doivent être créées avec la priorité en temps réel.
    • Les requêtes sont traitées selon un tourniquet pondéré basé sur la priorité.
    • Les requêtes Win32 sont traitées au niveau de priorité normale.
  • Capacity
    • Le nombre maximal de requêtes en attente que la file d’attente peut contenir.
    • Tenter de mettre une requête en file d’attente lorsque la file d’attente est à pleine capacité bloque jusqu’à ce que des entrées soient terminées par le matériel.
    • La quantité de mémoire nécessaire pour une file d’attente correspond approximativement à la capacité de la file d’attente multipliée par la taille d’une DSTORAGE_REQUEST.
  • Name
    • Sert uniquement à faciliter le débogage. Le nom n’est utilisé par aucune partie du code DirectStorage, mais il est visible dans les outils de développement, comme PIX (rubrique sous NDA).
Le matériel traite les requêtes de façon asynchrone pour obtenir le débit le plus élevé. Toutefois, contrairement à Win32, le titre est notifié de la fin dans l’ordre FIFO. À la notification de fin, il est garanti que toutes les requêtes précédentes adressées à la même file d’attente sont également terminées.

IDStorageQueueX1

L’interface IDStorageQueueX1 étend l’interface IDStorageQueueX avec la méthode EnqueueSetEvent.

IDStorageQueueX2

L’interface IDStorageQueueX2 étend l’interface `IDStorageQueueX1 avec la méthode CreateQueue1. Et une propriété supplémentaire.
  • Options
    • Indicateurs servant à contrôler le comportement de la file d’attente, y compris la désactivation de la soumission automatique.

EnqueueRequest

Cette interface est fonctionnellement identique à l’interface Win32 ReadFile. Des requêtes de lecture individuelles sont créées et soumises à la file d’attente. La principale différence est que DirectStorage permet de mettre de nombreuses requêtes en file d’attente avant la soumission, prend en charge la décompression matérielle et prend en charge l’annulation. Les requêtes possèdent plusieurs propriétés principales. Source de la requête Selon la combinaison de Options.SourceType et de Options.SourceIsPhysicalPages, DirectStorage utilise l’un des trois groupes de propriétés suivants pour spécifier l’emplacement des données sources.
  • File et FileOffset
    • Ce groupe est utilisé lorsque Options.SourceType est DSTORAGE_REQUEST_SOURCE_FILE
    • File est préalablement ouvert avec IDStorageFactoryX::OpenFile.
    • FileOffset doit être aligné sur 16 octets si la décompression est utilisée, ou n’a aucune exigence d’alignement si la décompression n’est pas utilisée.
      • Il s’agit d’un changement majeur par rapport à Win32, qui exigeait un alignement de 4 Kio dans le fichier pour les lectures asynchrones.
  • Source
    • Ce groupe est utilisé lorsque Options.SourceType est DSTORAGE_REQUEST_SOURCE_MEMORY et que Options.SourceIsPhysicalPages est FALSE.
    • La mémoire tampon contenant les données à décompresser.
  • SourcePageArray et SourcePageOffset
    • Ce groupe est utilisé lorsque Options.SourceType est DSTORAGE_REQUEST_SOURCE_MEMORY et que Options.SourceIsPhysicalPages est TRUE.
    • Semblable à Source, mais fournit la mémoire tampon source sous la forme d’un tableau de pages physiques de 64 Ko et du décalage en octets dans la première page.
    • Les pages physiques de 64 Ko peuvent être allouées par XMemAllocatePhysicalPages.
SourceSize
  • Taille, en octets, des données sources à lire, à partir d’une mémoire tampon ou d’un fichier.
IntermediateSize
  • Lorsque la décompression zlib et la décompression BCPACK sont toutes deux activées dans cette requête, IntermediateSize sert à spécifier la taille intermédiaire vers laquelle les données sources sont décompressées par zlib (et à partir de laquelle elles sont décompressées par BCPACK).
  • Sinon, elle doit être définie à 0.
Destination de la requête Selon Options.DestinationIsPhysicalPages, DirectStorage utilise l’un des deux groupes de propriétés suivants pour spécifier l’emplacement de la destination.
  • Destination
    • Ce groupe est utilisé lorsque Options.DestinationIsPhysicalPages est FALSE.
    • La mémoire tampon de destination pour les données finales chargées.
    • La décompression s’effectue au moyen de mémoires tampons internes partagées et peut être considérée comme une décompression sur place.
  • DestinationPageArray et DestinationPageOffset
    • Ce groupe est utilisé lorsque Options.DestinationIsPhysicalPages est TRUE.
    • Semblable à Destination, mais fournit la mémoire tampon de destination sous la forme d’un tableau de pages physiques de 64 Ko et du décalage en octets dans la première page.
    • Les pages physiques de 64 Ko peuvent être allouées par XMemAllocatePhysicalPages.
DestinationSize
  • Taille attendue, en octets, du contenu final chargé. La destination doit contenir suffisamment d’espace pour l’opération.
  • La taille doit être égale à SourceSize lorsque la décompression n’est pas utilisée, ou supérieure à SourceSize lorsque la décompression est utilisée.
CancellationTag
  • Une balise arbitraire de 64 bits définie par le titre.
  • Cette balise sert de masque pour les requêtes d’annulation.
Name
  • Chaîne facultative pour faciliter le débogage. Le nom peut apparaître dans les outils de développement, comme PIX (rubrique sous NDA), ou dans l’enregistrement d’erreur obtenu à partir de IDStorageQueueX::RetrieveErrorRecord. La chaîne Name doit être accessible pendant toute la durée de vie de la requête.
Options
  • ZlibDecompress
    • Indique que les données doivent être décompressées au moyen de la norme de décompression RFC 1950.
  • BcpackMode
    • Indique quel mode de BCPACK doit être utilisé pour décompresser les données.
    • None est une option valide et signifie que les données ne sont pas compressées avec BCPACK.
  • SwizzleMode
    • Indique comment les données finales doivent être réorganisées (swizzle) en mémoire.
  • DestinationIsPhysicalPages
    • Indique que la mémoire tampon de destination est spécifiée au moyen de DestinationPageArray et de DestinationPageOffset plutôt que de Destination.
  • SourceType
    • Une requête peut avoir comme source la mémoire, et possède alors les propriétés Source/ SourcePageArray et SourcePageOffset, ou avoir comme source un fichier, et possède alors les propriétés File/FileOffset.
  • SourceIsPhysicalPages
    • Indique que la mémoire tampon source est spécifiée au moyen de SourcePageArray et de SourcePageOffset plutôt que de Source.

EnqueueStatus/EnqueueSignal/EnqueueSetEvent

Les requêtes peuvent être mises en file d’attente et traitées comme une série de requêtes liées. Cela se fait en mettant en file d’attente une notification lorsque le traitement atteint un certain point dans la file d’attente. Les notifications ne sont traitées que lorsque toutes les requêtes de lecture précédentes sont terminées. Cela garantit que les données de toutes les requêtes précédentes sont immédiatement disponibles. Le titre dispose de deux méthodes d’interrogation et d’une méthode d’attente pour la notification. Le titre peut insérer un objet ID3D12Fence, un objet IDStorageStatusArrayX ou une opération de définition d’événement. Le ID3D12Fence se comporte comme prévu pour un objet ID3D12Fence. Un thread du titre peut attendre un Event, le processeur peut interroger la clôture, et le GPU peut interroger la clôture. L’objet IDStorageStatusArrayX permet au processeur d’interroger la fin et d’accéder aux éventuels échecs de lecture. La méthode EnqueueSetEvent permet à un thread du titre d’attendre l’événement spécifié plutôt que d’effectuer une interrogation. Cela diffère de ID3D12Fence::SetEventOnCompletion, car l’implémentation XBOX de ID3D12Fence::SetEventOnCompletion tourne en boucle sur la clôture jusqu’à ce qu’elle soit signalée, consommant ainsi le thread matériel du processeur jusqu’au signal, alors que EnqueueSetEvent permet au thread du titre d’utiliser WaitForSingleObject/WaitForMultipleObjects pour céder le processeur à d’autres threads jusqu’à ce que l’événement soit signalé. Comme mentionné précédemment, toutes les requêtes se terminent dans l’ordre, même si le matériel sous-jacent décide de les réorganiser pour des raisons de performances. Les notifications ne sont pas signalées tant que toutes les requêtes mises en file d’attente précédemment dans la file d’attente ne sont pas terminées.

Décompression

La décompression est gérée par du matériel dédié. Cela élimine la surcharge du processeur liée aux algorithmes de décompression traditionnels. DirectStorage alloue un bloc fixe de mémoire pendant l’initialisation pour l’utiliser comme mémoire tampon de travail pour la décompression. Cela permet la décompression sur place et élimine le besoin de conserver à la fois les données compressées et décompressées en mémoire en même temps. Le matériel de décompression prend en charge trois modes de fonctionnement. Les modes ne sont pas mutuellement exclusifs, de sorte que toute combinaison de modes peut être spécifiée. Les modes de décompression sont appliqués dans l’ordre suivant : DEFLATE, BCPACK et Swizzle.
  • ZLibDecompress
  • BCPack
    • BCPack est un codeur entropique personnalisé conçu spécifiquement pour les données BCn. En général, cela signifie que les extrémités de couleur sont séparées des indices de palette (c’est-à-dire les poids) et compressées au moyen d’un algorithme rANS.
  • Swizzle
    • Les modes Swizzle et de brassage (shuffle) peuvent fournir des optimisations supplémentaires dans les pipelines de contenu.
Lorsque des données à entropie élevée sont compressées, il est possible que la compression augmente en fait la taille. À l’inverse, la décompression correspondante réduira la taille. DirectStorage ne permet pas une décompression qui réduit la taille, et il revient au titre de détecter les données à entropie élevée qui sont incompressibles et d’éviter de compresser ces ressources. Pour plus de détails, consultez le guide Optimiser le contenu compressé avec DirectStorage et XBTC (rubrique sous NDA).

Mémoire tampon intermédiaire

DirectStorage utilise en interne une mémoire tampon pour placer temporairement tout le contenu lu à partir du stockage NVMe brut avant d’effectuer des opérations comme le déchiffrement et la décompression. Cette mémoire tampon intermédiaire permet au lecteur NVMe et au circuit de déchiffrement/décompression de travailler en pipeline, en parallèle. Sa taille par défaut est de 32 Mio, et elle est allouée lorsque le premier pointeur de fabrique DirectStorage est récupéré. Si un titre utilise DirectStorage uniquement pour des opérations de décompression de mémoire à mémoire, la mémoire tampon intermédiaire n’est pas nécessaire, et SetStagingBufferSize peut être appelée pour définir la taille de la mémoire tampon intermédiaire à 0. La version actuelle de DirectStorage prend en charge des tailles de mémoire tampon intermédiaire de 0, 16, 20, 24, 28 et 32 Mio. Une taille inférieure à la valeur par défaut permettra au titre d’économiser de la mémoire, mais pourrait nuire aux performances de lecture globales. REMARQUE : SetStagingBufferSize ne peut être appelée que lorsqu’aucun objet IDStorageQueueX ni aucun objet IDStorageFileX n’existe; sinon, elle génère une erreur.

CancelRequestsWithTag

DirectStorage prend en charge l’annulation des requêtes. Chaque requête est associée à une balise de 64 bits définie par le titre. Son but est de servir de masque de bits pour déterminer les requêtes à annuler. Le titre fournit un masque et une valeur pour l’annulation. La file d’attente tente d’annuler toutes les requêtes qui correspondent au critère : tag & mask == value. L’annulation est une opération effectuée au mieux. Selon l’emplacement de la requête dans le pipeline, il pourrait être impossible de l’annuler. Par exemple, la requête pourrait être en cours de décompression par le matériel, ce qui ne peut pas être annulé. L’API retourne immédiatement et ne bloque pas en attendant que toutes les requêtes annulées soient traitées. Les titres doivent attendre qu’une notification ultérieure dans la file d’attente soit signalée avant de libérer les ressources associées aux requêtes annulées. Il faut prendre soin d’éviter d’ajouter des requêtes à une file d’attente au moment même où CancelRequestsWithTag est appelée. Dans ce cas, le comportement n’est pas défini. Toutefois, les requêtes ajoutées à la file d’attente après le retour de l’appel à CancelRequestsWithTag ne seront pas annulées, même si elles correspondent aux critères. Seules les requêtes mises en file d’attente précédemment seront annulées.

GetErrorEvent/RetrieveErrorRecord

Si une lecture cause une erreur, cette lecture est marquée comme terminée. Les notifications ultérieures dans la file d’attente ne sont pas empêchées d’être signalées. La notification des erreurs est gérée au moyen d’un objet Event associé à la file d’attente, qui peut être récupéré avec GetErrorEvent. Le titre peut utiliser WaitForSingleObject sur l’Event retourné par GetErrorEvent. Si l’événement est signalé, le titre peut obtenir la première erreur survenue depuis le dernier appel à la fonction RetrieveErrorRecord en appelant la fonction RetrieveErrorRecord. L’enregistrement d’erreur retourné par RetrieveErrorRecord ne contient que les données de la première requête ayant échoué dans la file d’attente depuis le dernier appel à RetrieveErrorRecord. Les données de l’enregistrement d’erreur ne sont pas définies si l’Event d’erreur n’est pas signalé ou si les données ont déjà été récupérées.

Query

Obtient des renseignements sur la file d’attente. Cela comprend les structures DSTORAGE_QUEUE_DESC ou DSTORAGE_QUEUE_DESC1 utilisées pour la création de la file d’attente, ainsi que le nombre d’emplacements vides et le nombre d’entrées qui doivent être mises en file d’attente pour déclencher une soumission automatique.

Meilleures pratiques

Les mêmes conseils de meilleures pratiques donnés pour Win32 s’appliquent également à DirectStorage. Les seuils de performance optimale ont changé radicalement.

Tailles de lecture

Le conseil d’origine pour un disque rotatif était de lire par blocs d’au moins 128 Kio. Les performances continuent d’augmenter avec des tailles de bloc plus grandes. Les blocs de 512 Kio offraient les meilleures performances. L’absence de pièces mobiles dans le NVMe crée un seuil beaucoup plus bas. Les performances de lecture font un grand bond à partir de lectures de 32 Kio et commencent à plafonner à 64 Kio. Lire des blocs plus grands n’augmente pas les performances. Cela signifie qu’il faut moins d’efforts pour fusionner les données en blocs plus grands dans le package afin d’obtenir des performances optimales. Au-delà de 512 Kio, si la décompression est utilisée, préférez des requêtes plus petites mais plus nombreuses en parallèle plutôt qu’une seule requête énorme. Une seule requête énorme forcera la sérialisation de la décompression, tandis que plusieurs requêtes simultanées permettent à plusieurs unités matérielles de décompression de travailler en parallèle et d’atteindre le plein débit. Dans la version d’octobre 2022 du Microsoft Game Development Kit (GDK), la taille maximale d’une seule requête est passée de 32 Mio comme destination à 1 Gio pour l’utilisation combinée de la mémoire (source plus destination). Cette augmentation vise à faciliter le portage à partir d’autres API de stockage qui permettent de grandes tailles de lecture. Mais la recommandation de taille précédente pour atteindre le débit maximal demeure la même, car les grandes requêtes ne permettent pas à plusieurs unités matérielles de décompression de travailler en parallèle.

Ordre

Auparavant, avec les disques rotatifs, des efforts étaient consacrés à ordonner l’emplacement des lectures sur le disque. La situation idéale était de lire à partir d’emplacements séquentiels sur le disque. Cela réduisait au minimum le mouvement de la tête du disque, ce qui éliminait le temps de recherche comme facteur. Cela pouvait augmenter les performances d’un ordre de grandeur. Il y avait même un avantage à soumettre des lectures aléatoires triées par emplacement, et dans certains cas, elles étaient 2 fois plus rapides. Il est toujours utile d’ordonner les requêtes de lecture de façon aussi séquentielle que possible sur un lecteur NVMe. Le lecteur NVMe lit par blocs alignés sur 64 Kio. Pour cette raison, de la bande passante peut être gaspillée à lire des sections inutilisées d’un bloc de 64 Kio. Si une requête de lecture ne porte que sur 4 Kio, 60 Kio de bande passante seront gaspillés. Le NVMe réutilisera ces 60 Kio supplémentaires pour satisfaire d’autres requêtes en attente, si possible. Par exemple, si vous avez deux lectures séquentielles, une de 32 Kio suivie d’une de 8 Kio, il n’y a toujours qu’une seule lecture de 64 Kio à partir du lecteur.

Gestion des files d’attente

La recommandation avec les disques rotatifs était d’avoir une taille de file d’attente entre 12 et 16. Il n’y avait aucun avantage à une profondeur de file d’attente plus grande, et la baisse de performances causée par l’utilisation de profondeurs de file d’attente plus petites était importante. La spécification NVMe indique qu’un lecteur NVMe doit prendre en charge plusieurs files d’attente d’une profondeur allant jusqu’à 65 536 entrées par file d’attente. DirectStorage prend en charge cette exigence, permettant ainsi au titre de soumettre des milliers de requêtes à la fois. Auparavant, avec les disques rotatifs, les titres mettaient en mémoire tampon les requêtes en attente pour maintenir une profondeur de file d’attente dans la plage de 12 à 16. La recommandation pour DirectStorage est de ne pas mettre les requêtes en mémoire tampon, mais de les mettre en file d’attente dès leur création. L’ensemble du système est un pipeline, et la mise en mémoire tampon par le titre créera des bulles dans le pipeline, ce qui peut nuire gravement aux performances. Une autre recommandation consiste à créer une file d’attente dont la capacité est au moins quatre fois supérieure au plus grand nombre de requêtes créées par image. Cela devrait offrir suffisamment de capacité pour que de nouvelles requêtes puissent être ajoutées sans blocage, en attendant que les requêtes existantes se terminent.

Gestion des notifications

En général, moins il y a de requêtes de notification ajoutées à la file d’attente, mieux c’est. La recommandation est de trouver un équilibre entre les besoins du titre et le maintien au minimum des requêtes de notification mises en file d’attente. Mettre une notification en file d’attente après chaque requête ne fera que nuire aux performances globales en raison de la surcharge accrue du traitement de ces notifications. Un exemple pourrait être le regroupement par contenu. Par exemple, les textures SFS, les besoins du terrain (par exemple, maillage et texture) et les besoins des acteurs (par exemple, maillage, texture et animation). Cela permet de lier une seule notification à la disponibilité de toutes les ressources nécessaires pour créer un objet. Le choix entre l’utilisation d’un ID3D12Fence ou d’un tableau d’état dépend des besoins du titre. Les données doivent-elles être utilisées immédiatement par le GPU? Est-il acceptable que le thread de vérification soit suspendu jusqu’à ce que les lectures soient terminées? Une interrogation périodique est-elle suffisante parce que les données ne peuvent être traitées qu’à certains moments de l’image?

Considérations

Avec la possibilité d’un nombre beaucoup plus élevé de requêtes en cours, il faut prendre soin d’éviter de créer des goulots d’étranglement dans d’autres parties du titre. Les coûts de gestion des requêtes par le titre pourraient rapidement dépasser les économies réalisées par DirectStorage. La recommandation est d’examiner tout le code de soutien par requête et de déterminer ce qui peut être réduit. Chaque requête exige-t-elle l’allocation d’un nouveau bloc de mémoire?
  • Le système de mémoire a une surcharge pour trouver un nouveau bloc et mettre à jour les listes internes.
  • Envisagez de réutiliser les blocs de mémoire autant que possible.
Un verrou est-il nécessaire pour mettre à jour les gestionnaires?
  • Cela crée plus de contention à mesure que plus de mises à jour sont effectuées.
  • Envisagez de vous passer de verrous autant que possible.
Le chargement spéculatif est-il utilisé?
  • L’annulation est prise en charge, ce qui pourrait entraîner la création de plus de requêtes.
  • Toutefois, de la mémoire doit être disponible pour la spéculation.
  • Envisagez des limites strictes pour les seuils de spéculation.

Voir aussi

DirectStorage Utilisation et détails internes de DirectStorage (rubrique sous NDA) Optimiser le contenu compressé avec DirectStorage et XBTC (rubrique sous NDA) Analyser les performances de DirectStorage (rubrique sous NDA)
Last modified on October 6, 2026