Tableaux typés - Données binaires dans le navigateur

Ilmari Heikkinen

Introduction

Les tableaux typés sont un ajout relativement récent aux navigateurs, né de la nécessité d'avoir un moyen efficace de gérer les données binaires dans WebGL. Un tableau typé est un bloc de mémoire avec une vue typée, comme le font les tableaux en C. Étant donné qu'un tableau typé est basé sur une mémoire brute, le moteur JavaScript peut transmettre la mémoire directement aux bibliothèques natives sans avoir à convertir minutieusement les données en représentation native. Par conséquent, les tableaux typés sont beaucoup plus performants que les tableaux JavaScript pour transmettre des données à WebGL et à d'autres API qui traitent des données binaires.

Les vues de tableaux typés se comportent comme des tableaux à type unique pour un segment d'un ArrayBuffer. Il existe des vues pour tous les types numériques habituels, avec des noms descriptifs tels que Float32Array, Float64Array, Int32Array et Uint8Array. Une vue spéciale a également remplacé le type de tableau de pixels dans ImageData de Canvas: Uint8ClampedArray.

DataView est le deuxième type de vue. Il est conçu pour gérer des données hétérogènes. Au lieu d'une API semblable à un tableau, l'objet DataView vous fournit une API get/set pour lire et écrire des types de données arbitraires à des décalages d'octets arbitraires. DataView est idéal pour lire et écrire des en-têtes de fichiers et d'autres données de type struct.

Principes de base de l'utilisation des tableaux typés

Vues de tableaux typés

Pour utiliser des tableaux typés, vous devez créer un ArrayBuffer et une vue. Le moyen le plus simple est de créer une vue de tableau typé de la taille et du type souhaités.

// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];

Il existe plusieurs types de vues d'array typées. Elles partagent toutes la même API. Par conséquent, une fois que vous savez utiliser l'une d'entre elles, vous savez à peu près toutes les utiliser. Dans l'exemple suivant, je vais créer une vue de tableau typé pour chacune des vues existantes.

// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);

// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);

// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);

Le dernier est un peu spécial. Il limite les valeurs d'entrée entre 0 et 255. Cela est particulièrement pratique pour les algorithmes de traitement d'image Canvas, car vous n'avez plus besoin de limiter manuellement vos calculs de traitement d'image pour éviter de dépasser la plage de 8 bits.

Par exemple, voici comment appliquer un facteur gamma à une image stockée dans un Uint8Array. Pas très joli:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

Avec Uint8ClampedArray, vous pouvez ignorer le forçage manuel:

pixels[i] *= gamma;

L'autre méthode consiste à créer d'abord un ArrayBuffer, puis à créer des vues qui y pointent. Les API qui récupèrent des données externes traitent généralement des ArrayBuffers. C'est donc ainsi que vous obtenez une vue d'un tableau typé.

var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);

Vous pouvez également avoir plusieurs vues du même ArrayBuffer.

var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.

Pour copier un tableau typé dans un autre tableau typé, le moyen le plus rapide est d'utiliser la méthode de définition du tableau typé. Pour une utilisation semblable à memcpy, créez des Uint8Arrays dans les tampons des vues et utilisez set pour copier les données.

function memcpy(dst, dstOffset, src, srcOffset, length) {
  var dstU8 = new Uint8Array(dst, dstOffset, length);
  var srcU8 = new Uint8Array(src, srcOffset, length);
  dstU8.set(srcU8);
};

DataView

Pour utiliser des ArrayBuffers contenant des données de types hétérogènes, le moyen le plus simple est d'utiliser un DataView pour le tampon. Supposons qu'un format de fichier comporte un en-tête avec un entier non signé 8 bits, suivi de deux entiers 16 bits, puis d'un tableau de charge utile de nombres à virgule flottante 32 bits. Il est possible de lire cela avec des vues de tableaux typés, mais c'est un peu pénible. Avec un DataView, nous pouvons lire l'en-tête et utiliser une vue de tableau typée pour le tableau de nombres à virgule flottante.

var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
  vectors[i] = dv.getFloat32(off);
}

Dans l'exemple ci-dessus, toutes les valeurs que je lis sont en ordre de grandeur. Si les valeurs du tampon sont en ordre octets de basse endianité, vous pouvez transmettre le paramètre littleEndian facultatif au getter:

...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...

Notez que les vues de tableaux typés sont toujours dans l'ordre d'octet natif. Cela permet de les rendre plus rapides. Vous devez utiliser une DataView pour lire et écrire des données où l'endianness pose problème.

DataView propose également des méthodes permettant d'écrire des valeurs dans des tampons. Ces sétateurs sont nommés de la même manière que les getters, "set" suivi du type de données.

dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5

Discussion sur l'endianité

L'endianness, ou ordre des octets, est l'ordre dans lequel les nombres multi-octets sont stockés dans la mémoire de l'ordinateur. Le terme big-endian décrit une architecture de processeur qui stocke l'octet le plus significatif en premier, et little-endian l'octet le moins significatif en premier. L'endianness utilisé dans une architecture de processeur donnée est complètement arbitraire. Il existe de bonnes raisons de choisir l'un ou l'autre. En fait, certains processeurs peuvent être configurés pour prendre en charge à la fois les données big-endian et little-endian.

Pourquoi devez-vous vous soucier de l'endianité ? La raison est simple. Lorsque vous lisez ou écrivez des données à partir du disque ou du réseau, vous devez spécifier l'ordre des octets. Cela garantit que les données sont interprétées correctement, quel que soit l'endianness du processeur qui les utilise. Dans notre monde de plus en plus interconnecté, il est essentiel de prendre en charge correctement tous les types d'appareils, qu'ils soient big-endian ou little-endian, qui peuvent être amenés à travailler avec des données binaires provenant de serveurs ou d'autres pairs sur le réseau.

L'interface DataView est spécialement conçue pour lire et écrire des données à partir et vers des fichiers et le réseau. DataView gère les données avec un ordre d'octets spécifié. L'endianité, grande ou petite, doit être spécifiée pour chaque accès de chaque valeur, ce qui garantit des résultats cohérents et corrects lors de la lecture ou de l'écriture de données binaires, quelle que soit l'endianité du processeur sur lequel le navigateur s'exécute.

En règle générale, lorsque votre application lit des données binaires à partir d'un serveur, vous devez les analyser une fois pour les convertir en structures de données que votre application utilise en interne. DataView doit être utilisé pendant cette phase. Il n'est pas recommandé d'utiliser les vues d'array typé multi-octets (Int16Array, Uint16Array, etc.) directement avec des données extraites via XMLHttpRequest, FileReader ou toute autre API d'entrée/sortie, car les vues d'array typé utilisent l'endianité native du processeur. Nous reviendrons sur ce point.

Examinons quelques exemples simples. Le format de fichier BMP Windows était le format standard de stockage des images au début de Windows. La documentation associée au lien ci-dessus indique clairement que toutes les valeurs entières du fichier sont stockées au format little-endian. Voici un extrait de code qui analyse le début de l'en-tête BMP à l'aide de la bibliothèque DataStream.js qui accompagne cet article:

function parseBMP(arrayBuffer) {
  var stream = new DataStream(arrayBuffer, 0,
    DataStream.LITTLE_ENDIAN);
  var header = stream.readUint8Array(2);
  var fileSize = stream.readUint32();
  // Skip the next two 16-bit integers
  stream.readUint16();
  stream.readUint16();
  var pixelOffset = stream.readUint32();
  // Now parse the DIB header
  var dibHeaderSize = stream.readUint32();
  var imageWidth = stream.readInt32();
  var imageHeight = stream.readInt32();
  // ...
}

Voici un autre exemple, tiré de la démonstration de rendu HDR du projet d'exemples WebGL. Cette démonstration télécharge des données à virgule flottante brutes en little-endian représentant des textures à plage dynamique élevée, et doit les importer dans WebGL. Voici l'extrait de code qui interprète correctement les valeurs à virgule flottante sur toutes les architectures de processeur. Supposons que la variable "arrayBuffer" soit un ArrayBuffer qui vient d'être téléchargé depuis le serveur via XMLHttpRequest:

var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
  data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
  tempArray[jj] =
    data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
  gl.RGB, gl.FLOAT, tempArray);

La règle d'or est la suivante: lorsque vous recevez des données binaires du serveur Web, effectuez une seule passe avec une DataView. Lisez les valeurs numériques individuelles et stockez-les dans une autre structure de données, un objet JavaScript (pour de petites quantités de données structurées) ou une vue de tableau typée (pour de grands blocs de données). Vous vous assurez ainsi que votre code fonctionne correctement sur tous les types de processeurs. Utilisez également DataView pour écrire des données dans un fichier ou sur le réseau, et veillez à spécifier correctement l'argument littleEndian aux différentes méthodes set afin de générer le format de fichier que vous créez ou utilisez.

N'oubliez pas que toutes les données transmises sur le réseau ont implicitement un format et un ordre d'octets (au moins pour les valeurs multi-octets). Assurez-vous de définir et de documenter clairement le format de toutes les données que votre application envoie sur le réseau.

API du navigateur qui utilisent des tableaux saisis

Je vais vous donner un bref aperçu des différentes API de navigateur qui utilisent actuellement des tableaux typés. La sélection actuelle comprend WebGL, Canvas, l'API Web Audio, les requêtes XHR, les WebSockets, les Web Workers, l'API Media Source et les API File. Dans la liste des API, vous pouvez voir que les tableaux typés sont bien adaptés aux tâches multimédias sensibles aux performances, ainsi qu'au transfert de données de manière efficace.

WebGL

Les premiers tableaux typés ont été utilisés dans WebGL, où ils servent à transmettre des données de tampon et des données d'image. Pour définir le contenu d'un objet de tampon WebGL, utilisez l'appel gl.bufferData() avec un tableau typé.

var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);

Les tableaux typés sont également utilisés pour transmettre des données de texture. Voici un exemple de base de transmission du contenu de la texture à l'aide d'un tableau typé.

var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
  gl.TEXTURE_2D, // target
  0, // mip level
  gl.RGBA, // internal format
  16, 16, // width and height
  0, // border
  gl.RGBA, //format
  gl.UNSIGNED_BYTE, // type
  pixels // texture data
);

Vous avez également besoin de tableaux typés pour lire les pixels à partir du contexte WebGL.

var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Canevas 2D

Récemment, l'objet Canvas ImageData a été conçu pour fonctionner avec la spécification des tableaux typés. Vous pouvez désormais obtenir une représentation des pixels d'un élément de canevas sous forme de tableaux typés. Cela est utile, car vous pouvez désormais également créer et modifier des tableaux de pixels de canevas sans avoir à manipuler l'élément de canevas.

var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray

XMLHttpRequest2

XMLHttpRequest a bénéficié d'un coup de pouce de la part des tableaux typés. Vous pouvez désormais recevoir une réponse de tableau typé au lieu d'avoir à analyser une chaîne JavaScript dans un tableau typé. Cela est très pratique pour transmettre directement les données extraites aux API multimédias et pour analyser les fichiers binaires extraits du réseau.

Il vous suffit de définir le responseType de l'objet XMLHttpRequest sur "arraybuffer".

xhr.responseType = 'arraybuffer';

N'oubliez pas que vous devez tenir compte des problèmes d'endianité lorsque vous téléchargez des données à partir du réseau. Consultez la section sur l'ordre des octets ci-dessus.

API File

Le FileReader peut lire le contenu des fichiers en tant qu'ArrayBuffer. Vous pouvez ensuite associer des vues de tableaux typées et des vues de données au tampon pour manipuler son contenu.

reader.readAsArrayBuffer(file);

Vous devez également garder à l'esprit l'endianité. Pour en savoir plus, consultez la section sur l'ordre des octets.

Objets transférables

Les objets transférables dans postMessage permettent de transmettre des données binaires à d'autres fenêtres et Web Workers beaucoup plus rapidement. Lorsque vous envoyez un objet à un worker en tant qu'objet transférable, il devient inaccessible dans le thread d'envoi et le worker destinataire en devient le propriétaire. Cela permet une implémentation hautement optimisée où les données envoyées ne sont pas copiées, mais que la propriété de la matrice typée est transférée au destinataire.

Pour utiliser des objets transférables avec des Web Workers, vous devez utiliser la méthode webkitPostMessage sur le worker. La méthode webkitPostMessage fonctionne de la même manière que postMessage, mais elle prend deux arguments au lieu d'un seul. Le deuxième argument ajouté est un tableau d'objets que vous souhaitez transférer au nœud de calcul.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Pour récupérer les objets du nœud de calcul, celui-ci peut les renvoyer au thread principal de la même manière.

webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);

Aucune copie, youpi !

API Media Source

Récemment, les éléments multimédias ont également bénéficié des avantages des tableaux typés sous la forme de l'API Media Source. Vous pouvez transmettre directement un tableau typé contenant des données vidéo à un élément vidéo à l'aide de webkitSourceAppend. L'élément vidéo ajoute alors les données vidéo après la vidéo existante. SourceAppend est idéal pour les interstitiels, les playlists, le streaming et d'autres utilisations où vous souhaitez lire plusieurs vidéos à l'aide d'un seul élément vidéo.

video.webkitSourceAppend(uint8Array);

WebSockets binaires

Vous pouvez également utiliser des tableaux typés avec WebSockets pour éviter de concaténer toutes vos données. Idéal pour écrire des protocoles efficaces et réduire le trafic réseau.

socket.binaryType = 'arraybuffer';

Ouf ! C'est la fin de l'examen de l'API. Passons maintenant aux bibliothèques tierces pour la gestion des tableaux typés.

Bibliothèques tierces

jDataView

jDataView implémente un shim DataView pour tous les navigateurs. DataView était auparavant une fonctionnalité réservée à WebKit, mais elle est désormais compatible avec la plupart des autres navigateurs. L'équipe de développeurs Mozilla est en train de publier un correctif pour activer DataView dans Firefox.

Eric Bidelman, membre de l'équipe Chrome Developer Relations, a écrit un petit exemple de lecteur de tags ID3 MP3 qui utilise jDataView. Voici un exemple d'utilisation tiré de l'article de blog:

var dv = new jDataView(arraybuffer);

// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
  var title = dv.getString(30, dv.tell());
  var artist = dv.getString(30, dv.tell());
  var album = dv.getString(30, dv.tell());
  var year = dv.getString(4, dv.tell());
} else {
  // no ID3v1 data found.
}

stringencoding

Travailler avec des chaînes dans des tableaux typés est un peu pénible pour le moment, mais la bibliothèque stringencoding peut vous aider. Stringencoding implémente la spécification d'encodage de chaîne de tableau typé proposée. Il s'agit donc également d'un bon moyen de se faire une idée de ce qui vous attend.

Voici un exemple d'utilisation de base de la fonction stringencoding:

var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

BitView.js

J'ai écrit une petite bibliothèque de manipulation de bits pour les tableaux typés appelée BitView.js. Comme son nom l'indique, il fonctionne comme la vue de données, mais avec des bits. Avec BitView, vous pouvez obtenir et définir la valeur d'un bit à un décalage de bits donné dans un ArrayBuffer. BitView propose également des méthodes pour stocker et charger des entiers de 6 et 12 bits à des décalages de bits arbitraires.

Les entiers 12 bits sont utiles pour travailler avec les coordonnées d'écran, car les écrans ont tendance à comporter moins de 4 096 pixels sur la dimension la plus longue. En utilisant des entiers 12 bits au lieu d'entiers 32 bits, vous obtenez une réduction de 62% de la taille. Pour un exemple plus extrême, je travaillais avec des fichiers de forme qui utilisent des nombres à virgule flottante 64 bits pour les coordonnées, mais je n'avais pas besoin de cette précision, car le modèle ne devait être affiché qu'à la taille de l'écran. Le passage à des coordonnées de base 12 bits avec des deltas 6 bits pour encoder les modifications par rapport à la coordonnée précédente a réduit la taille du fichier d'un dixième. Pour en savoir plus, cliquez ici.

Voici un exemple d'utilisation de BitView.js:

var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.

bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.

bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.

DataStream.js

L'un des aspects les plus intéressants des tableaux typés est qu'ils facilitent la gestion des fichiers binaires en JavaScript. Au lieu d'analyser une chaîne caractère par caractère et de convertir manuellement les caractères en nombres binaires, vous pouvez désormais obtenir un ArrayBuffer avec XMLHttpRequest et le traiter directement à l'aide d'un DataView. Vous pouvez ainsi facilement charger un fichier MP3 et lire les balises de métadonnées à utiliser dans votre lecteur audio, par exemple. Vous pouvez également charger un fichier de forme et le convertir en modèle WebGL. Vous pouvez également lire les balises EXIF d'un fichier JPEG et les afficher dans votre application de diaporama.

Le problème avec les XHR ArrayBuffer est que la lecture de données ressemblant à des structures à partir du tampon est un peu pénible. DataView est utile pour lire quelques nombres à la fois de manière endian-safe. Les vues de tableaux typés sont utiles pour lire des tableaux de nombres endian natifs alignés sur la taille des éléments. Nous avons ressenti le besoin d'un moyen de lire des tableaux et des structures de données de manière pratique et compatible avec l'endianité. Saisissez DataStream.js.

DataStream.js est une bibliothèque d'arrays typés qui lit et écrit des scalaires, des chaînes, des tableaux et des structures de données à partir d'ArrayBuffers de manière semblable à un fichier.

Exemple de lecture dans un tableau de nombres à virgule flottante à partir d'un ArrayBuffer:

// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
  f32[i] = dv.getFloat32(i*4, littleEndian);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);

DataStream.js est particulièrement utile pour lire des données plus complexes. Supposons que vous disposiez d'une méthode qui lit les repères JPEG:

// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
  var obj = {};
  obj.tag = dv.getUint16(i);
  i += 2;
  obj.length = dv.getUint16(i);
  i += 2;
  obj.data = new Uint8Array(obj.length - 2);
  for (var j=0; j<obj.data.length; j++,i++) {
    obj.data[j] = dv.getUint8(i);
  }
  objs.push(obj);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
  var obj = {};
  obj.tag = ds.readUint16();
  obj.length = ds.readUint16();
  obj.data = ds.readUint8Array(obj.length - 2);
  objs.push(obj);
}

Vous pouvez également utiliser la méthode DataStream.readStruct pour lire des structures de données. La méthode readStruct reçoit un tableau de définition de structure contenant les types des membres de la structure. Il dispose de fonctions de rappel pour gérer les types complexes, et gère également les tableaux de données et les structures imbriquées:

// with DataStream.readStruct
ds.readStruct([
  'objs', ['[]', [ // objs: array of tag,length,data structs
    'tag', 'uint16',
    'length', 'uint16',
    'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
  '*'] // read in as many struct as there are
]);

Comme vous pouvez le constater, la définition de la structure est un tableau plat de paires [nom, type]. Les structures imbriquées sont créées à l'aide d'un tableau pour le type. Les tableaux sont définis à l'aide d'un tableau à trois éléments, dont le deuxième élément correspond au type d'élément du tableau et le troisième élément à la longueur du tableau (sous la forme d'un nombre, d'une référence au champ lu précédemment ou d'une fonction de rappel). Le premier élément de la définition du tableau n'est pas utilisé.

Les valeurs possibles pour le type sont les suivantes:

Number types

Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.

  'uint8' -- 8-bit unsigned int
  'uint16' -- 16-bit unsigned int
  'uint32' -- 32-bit unsigned int
  'int8' -- 8-bit int
  'int16' -- 16-bit int
  'int32' -- 32-bit int
  'float32' -- 32-bit float
  'float64' -- 64-bit float

String types

  'cstring' -- ASCII string terminated by a zero byte.
  'string:N' -- ASCII string of length N.
  'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
  'u16string:N' -- UCS-2 string of length N in DataStream endianness.
  'u16stringle:N' -- UCS-2 string of length N in little-endian.
  'u16stringbe:N' -- UCS-2 string of length N in big-endian.

Complex types

  [name, type, name_2, type_2, ..., name_N, type_N] -- Struct

  function(dataStream, struct) {} -- Callback function to read and return data.

  {get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
  -- Getter/setter functions to reading and writing data. Handy for using the
     same struct definition for both reading and writing.

  ['', type, length] -- Array of given type and length. The length can be either
                        a number, a string that references a previously-read
                        field, or a callback function(struct, dataStream, type){}.
                        If length is set to '*', elements are read from the
                        DataStream until a read fails.

Pour voir un exemple concret de lecture des métadonnées JPEG, cliquez ici. La démonstration utilise DataStream.js pour lire la structure au niveau des balises du fichier JPEG (avec une analyse EXIF) et jpg.js pour décoder et afficher l'image JPEG en JavaScript.

Historique des tableaux typés

Les tableaux typés ont vu le jour au début de l'implémentation de WebGL, lorsque nous avons constaté que le transfert de tableaux JavaScript au pilote graphique entraînait des problèmes de performances. Avec les tableaux JavaScript, la liaison WebGL devait allouer un tableau natif et le remplir en parcourant le tableau JavaScript et en convertissant chaque objet JavaScript du tableau au type natif requis.

Pour résoudre le goulot d'étranglement de la conversion de données, Vladimir Vukicevic de Mozilla a écrit CanvasFloatArray: un tableau de nombres à virgule flottante de style C avec une interface JavaScript. Vous pouvez désormais modifier le CanvasFloatArray en JavaScript et le transmettre directement à WebGL sans avoir à effectuer de travail supplémentaire dans la liaison. Dans les itérations suivantes, CanvasFloatArray a été renommé WebGLFloatArray, puis Float32Array et divisé en ArrayBuffer de base et en vue Float32Array typée pour accéder au tampon. Des types ont également été ajoutés pour d'autres tailles d'entiers et de nombres à virgule flottante, ainsi que pour les variantes signées/non signées.

Considérations de conception

Dès le départ, la conception des tableaux typés a été motivée par la nécessité de transmettre efficacement des données binaires aux bibliothèques natives. C'est pourquoi les vues d'un tableau typé fonctionnent sur des données alignées dans l'ordre de bytes natif du processeur hôte. Ces décisions permettent à JavaScript d'atteindre des performances maximales lors d'opérations telles que l'envoi de données de sommet à la carte graphique.

DataView est spécialement conçu pour les E/S de fichiers et de réseau, où les données ont toujours un endianness spécifié et ne sont pas nécessairement alignées pour des performances maximales.

La répartition de la conception entre l'assemblage de données en mémoire (à l'aide des vues d'array typées) et les E/S (à l'aide de DataView) a été consciente. Les moteurs JavaScript modernes optimisent fortement les vues d'un tableau typé et obtiennent de hautes performances pour les opérations numériques avec celui-ci. Cette décision de conception a permis d'atteindre les niveaux de performances actuels des vues d'array typées.

Références