Array digitati - Dati binari nel browser

Ilmari Heikkinen

Introduzione

Gli array typed sono un'aggiunta relativamente recente ai browser, nati dall'esigenza di disporre di un modo efficiente di gestire i dati binari in WebGL. Un array digitato è una placca di memoria con una visualizzazione digitata, molto simile al funzionamento degli array in C. Poiché un array typed è supportato da memoria non elaborata, il motore JavaScript può passare la memoria direttamente alle librerie native senza dover convertire faticosamente i dati in una rappresentazione nativa. Di conseguenza, gli array digitati offrono prestazioni molto migliori degli array JavaScript per passare dati a WebGL e ad altre API che trattano dati binari.

Le viste array tipiche funzionano come array di tipo singolo per un segmento di un ArrayBuffer. Esistono viste per tutti i tipi numerici consueti, con nomi autodescrittivi come Float32Array, Float64Array, Int32Array e Uint8Array. È presente anche una vista speciale che ha sostituito il tipo di array di pixel in ImageData: Uint8ClampedArray di Canvas.

DataView è il secondo tipo di vista ed è destinato alla gestione di dati eterogenei. Anziché avere un'API di tipo array, l'oggetto DataView fornisce un'API get/set per leggere e scrivere tipi di dati arbitrari con offset di byte arbitrari. DataView è ideale per leggere e scrivere intestazioni di file e altri dati simili a struct.

Nozioni di base sull'utilizzo degli array digitati

Visualizzazioni array inserite

Per utilizzare Array digitati, è necessario creare un ArrayBuffer e una visualizzazione. Il modo più semplice è creare una visualizzazione array digitata delle dimensioni e del tipo desiderati.

// 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];

Esistono vari tipi di visualizzazioni di array tipo. condividono tutte la stessa API, quindi una volta che sai come usarne una, sai praticamente come usarle tutte. Nel prossimo esempio creerò una di ciascuna vista array digitata attualmente esistente.

// 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);

L'ultimo è un po' speciale, limita i valori di input tra 0 e 255. Ciò è particolarmente utile per gli algoritmi di elaborazione delle immagini di Canvas, poiché ora non è più necessario bloccare manualmente i calcoli matematici di elaborazione delle immagini per evitare di superare l'intervallo degli 8 bit.

Ad esempio, ecco come applicare un fattore gamma a un'immagine archiviata in Uint8Array. Non molto carino:

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

Con Uint8ClampedArray puoi saltare il blocco manuale:

pixels[i] *= gamma;

L'altro modo per creare viste array tipiche consiste nel creare prima un ArrayBuffer e poi creare viste che puntano ad esso. Le API che ti ricevono dati esterni di solito sono in Arraybus, quindi è in questo modo che ottieni una visualizzazione array typeizzata per queste.

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);

Puoi anche avere più visualizzazioni per lo stesso ArrayBuffer.

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

Per copiare un array digitato in un altro array digitato, il modo più rapido consiste nell'utilizzare il metodo dell'insieme di array digitati. Per un utilizzo simile al memcpy, crea Uint8Arrays nei buffer delle viste e utilizza set per copiare i dati.

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

Per utilizzare ArrayBuffers che contengono dati con tipi eterogenei, il modo più semplice è utilizzare un DataView nel buffer. Supponiamo di avere un formato di file con un'intestazione con un int senza firma a 8 bit seguito da due int a 16 bit, seguiti da un array di payload di valori mobili a 32 bit. È possibile leggere questo testo con le visualizzazioni array digitate, ma un po' un problema. Con un oggetto DataView possiamo leggere l'intestazione e utilizzare una vista array typeizzata per l'array float.

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);
}

Nell'esempio precedente, tutti i valori che leggo sono big-endian. Se i valori nel buffer sono low-endian, puoi passare il parametro facoltativo fewEndian al getter:

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

Tieni presente che le visualizzazioni degli array digitati seguono sempre l'ordine di byte nativo. Questo serve a renderle veloci. Dovresti usare un componente DataView per leggere e scrivere dati in cui la resistenza sarà un problema.

Il componente DataView offre anche metodi per scrivere valori nei buffer. Questi setter vengono denominati nello stesso modo dei getter, ovvero "set" seguito dal tipo di dati.

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

Una discussione sull'adeguatezza

L'endianness, o ordine dei byte, è l'ordine in cui i numeri multibyte sono memorizzati nella memoria del computer. Il termine big-endian descrive un'architettura della CPU che memorizza per primo il byte più significativo; little-endian, il byte meno significativo per primo. L'endianità utilizzata in una determinata architettura della CPU è del tutto arbitraria; ci sono buoni motivi per sceglierne una. Di fatto, alcune CPU possono essere configurate per supportare sia dati big-endian che low-endian.

Perché devi temere la pericolosità? Il motivo è semplice. Durante la lettura o la scrittura di dati dal disco o dalla rete, è necessario specificare la terminazione dei dati. Ciò garantisce che i dati vengano interpretati correttamente, indipendentemente dall'endpoint della CPU che li utilizza. Nel nostro mondo sempre più connesso in rete, è essenziale supportare adeguatamente tutti i tipi di dispositivi, di grandi dimensioni o di piccole dimensioni, che potrebbero avere bisogno di funzionare con dati binari provenienti da server o altri dispositivi peer sulla rete.

L'interfaccia DataView è progettata specificatamente per leggere e scrivere dati da e verso i file e la rete. DataView opera su dati con un'endpoint specificata. L'endpoint, grande o piccolo, deve essere specificato a ogni accesso di ogni valore, assicurando di ottenere risultati coerenti e corretti durante la lettura o la scrittura di dati binari, indipendentemente dalla endianness della CPU su cui è in esecuzione il browser.

Solitamente, quando l'applicazione legge dati binari da un server, sarà necessario analizzarli una volta per convertirli nelle strutture di dati utilizzate internamente dall'applicazione. È necessario utilizzare DataView durante questa fase. Non è consigliabile utilizzare le viste array con tipo multi-byte (Int16Array, Uint16Array ecc.) direttamente con i dati recuperati tramite XMLHttpRequest, FileReader o qualsiasi altra API di input/output, poiché le viste degli array digitati utilizzano l'endpoint nativo della CPU. Ne parleremo più avanti.

Diamo un'occhiata a un paio di semplici esempi. Il formato file BMP di Windows era il formato standard per l'archiviazione delle immagini sugli inizi di Windows. La documentazione al link di cui sopra indica chiaramente che tutti i valori interi del file sono memorizzati in formato small-endian. Di seguito è riportato uno snippet di codice che analizza l'inizio dell'intestazione BMP utilizzando la libreria DataStream.js che accompagna questo articolo:

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();
  // ...
}

Ecco un altro esempio, questo tratto dalla demo di rendering High Dynamic Range nel progetto di esempio WebGL. Questa demo scarica dati non elaborati in virgola mobile, che rappresentano texture ad alto intervallo dinamico, e devono essere caricati su WebGL. Ecco lo snippet di codice che interpreta correttamente i valori in virgola mobile su tutte le architetture della CPU. Supponiamo che la variabile "arrayBuffer" sia un ArrayBuffer appena scaricato dal server tramite 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 regola generale è: quando ricevi dati binari dal server web, esegui un passaggio oltre con un DataView. Leggere i singoli valori numerici e archiviarli in un'altra struttura di dati, che può essere un oggetto JavaScript (per piccole quantità di dati strutturati) o una visualizzazione array digitata (per blocchi di dati di grandi dimensioni). Ciò garantisce che il codice funzioni correttamente su tutti i tipi di CPU. Inoltre, utilizza DataView per scrivere dati in un file o sulla rete e assicurati di specificare in modo appropriato l'argomento littleEndian per i vari metodi set per produrre il formato file che stai creando o utilizzando.

Ricorda che tutti i dati che passano sulla rete implicitamente hanno un formato e un'endpoint (almeno per i valori multi-byte). Assicurati di definire e documentare chiaramente il formato di tutti i dati che l'applicazione invia attraverso la rete.

API del browser che utilizzano array typed

Ti illustrerò una breve panoramica delle diverse API browser che attualmente utilizzano gli array digitati. Il ritaglio attuale include WebGL, Canvas, API Web Audio, XMLHttpRequests, WebSocket, Web worker, API Media Source e API File. Dall'elenco delle API puoi vedere che gli array digitati sono particolarmente adatti per lavori multimediali sensibili alle prestazioni e per il trasferimento di dati in modo efficiente.

WebGL

Il primo utilizzo degli array typed è stato in WebGL, che consente di trasferire dati del buffer e dati di immagine. Per impostare i contenuti di un oggetto buffer WebGL, utilizza la chiamata gl.bufferData() con un array typed.

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

Gli array typed vengono utilizzati anche per trasferire i dati delle texture. Ecco un esempio base di passaggio del contenuto della texture utilizzando un array typed.

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
);

Sono necessari anche array typed per leggere i pixel dal contesto WebGL.

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

Canvas 2D

Di recente, l'oggetto Canvas ImageData è stato realizzato per funzionare con la specifica Array digitati. Ora puoi ottenere una rappresentazione di Array digitati dei pixel su un elemento canvas. Ciò è utile perché ora puoi anche creare e modificare array di pixel canvas senza dover maneggiare l'elemento canvas.

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

XMLHttpRequest2

XMLHttpRequest ha generato un impulso di array typed e ora puoi ricevere una risposta di tipo Array typed invece di dover analizzare una stringa JavaScript in un array typed. Si tratta di una funzionalità molto utile per passare i dati recuperati direttamente alle API multimediali e per analizzare i file binari recuperati dalla rete.

Non devi fare altro che impostare il responseType dell'oggetto XMLHttpRequest su "arraybuffer".

xhr.responseType = 'arraybuffer';

È necessario prestare attenzione ai problemi di endpoint quando si scaricano i dati dalla rete. Consulta la sezione sull'affidabilità qui sopra.

API per i file

Il FileReader può leggere i contenuti dei file come un ArrayBuffer. Puoi quindi collegare le viste array tipiche e DataView al buffer per manipolarne i contenuti.

reader.readAsArrayBuffer(file);

Dovete tenere a mente anche la resistenza. Per maggiori dettagli, consulta la sezione relativa alla compatibilità.

Oggetti trasferibili

Gli oggetti trasferibili in postMessage rendono il passaggio di dati binari ad altre finestre e Web worker molto più rapidamente. Quando invii un oggetto a un worker come Trasferibile, l'oggetto diventa inaccessibile nel thread di invio e il worker ricevente ottiene la proprietà dell'oggetto. Ciò consente un'implementazione altamente ottimizzata in cui i dati inviati non vengono copiati, ma solo la proprietà dell'array typed viene trasferita al destinatario.

Per utilizzare oggetti trasferibili con i web worker, devi usare il metodo webkitPostMessage sul worker. Il metodo webkitPostMessage funziona come postMessage, ma richiede due argomenti anziché uno solo. Il secondo argomento aggiunto è un array di oggetti che vuoi trasferire al worker.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Per recuperare gli oggetti dal lavoratore, quest'ultimo può ritrasmetterli al thread principale nello stesso modo.

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

Zero copie, evviva!

API Media Source

Di recente, gli elementi multimediali hanno anche acquisito un buon livello di Array tipicamente sotto forma di API Media Source. Puoi passare direttamente un array typed contenente dati video a un elemento video utilizzando webkitSourceAppend. In questo modo, l'elemento video aggiunge i dati del video dopo il video esistente. SourceAppend è ideale per gli interstitial, le playlist, lo streaming e altri utilizzi per riprodurre più video utilizzando un singolo elemento video.

video.webkitSourceAppend(uint8Array);

WebSocket binari

Puoi anche utilizzare gli array digitati con WebSocket per evitare di dover stringere tutti i dati. Ideale per scrivere protocolli efficienti e ridurre al minimo il traffico di rete.

socket.binaryType = 'arraybuffer';

Fiu! Con questo si conclude la revisione dell'API. Passiamo alle librerie di terze parti per la gestione degli array digitati.

Librerie di terze parti

jDataView

jDataView implementa uno shim DataView per tutti i browser. DataView era una funzionalità solo di WebKit, ma ora è supportato dalla maggior parte degli altri browser. Il team di sviluppatori di Mozilla sta per implementare una patch per abilitare DataView anche su Firefox.

Eric Bidelman del team Developer Relations di Chrome ha scritto un esempio di lettore di tag ID3 MP3 di piccole dimensioni che utilizza jDataView. Ecco un esempio di utilizzo tratto dal post del 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.
}

codifica stringa

Al momento lavorare con le stringhe in array typed è un po' complicato, ma c'è la libreria di codificazione delle stringhe che aiuta a farlo. Stringencoding implementa la proposta di specifica di codifica delle stringhe dell'array typed, quindi è anche un buon modo per farsi un'idea di ciò che verrà.

Ecco un esempio di utilizzo di base della codifica stringa:

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

BitView.js

Ho scritto una piccola libreria di manipolazione per gli array digitati chiamata BitView.js. Come dice il nome, funziona in modo molto simile a DataView, ad eccezione del fatto che funziona con i bit. Con BitView puoi ottenere e impostare il valore di un bit con un determinato offset di bit in un ArrayBuffer. BitView offre anche metodi per memorizzare e caricare int a 6 e 12 bit con offset di bit arbitrari.

Gli interi a 12 bit sono ideali per lavorare con le coordinate dello schermo, in quanto i display tendono ad avere meno di 4096 pixel nella dimensione più lunga. Utilizzando int a 12 bit anziché int a 32 bit, si ottiene una riduzione delle dimensioni del 62%. Come esempio più estremo, stavo lavorando con shapefile che utilizzano valori mobili a 64 bit per le coordinate, ma non avevo bisogno della precisione perché il modello veniva mostrato solo alle dimensioni dello schermo. Il passaggio alle coordinate di base a 12 bit con delta a 6 bit per codificare le modifiche rispetto alla coordinata precedente ha ridotto la dimensione del file al decimo. Per vedere la demo, fai clic qui.

Ecco un esempio di utilizzo di 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

Una delle cose più entusiasmanti degli array digitati è il modo in cui semplificano la gestione dei file binari in JavaScript. Anziché analizzare una stringa carattere per carattere e convertire manualmente i caratteri in numeri binari e simili, ora puoi ottenere un ArrayBuffer con XMLHttpRequest ed elaborarlo direttamente utilizzando un DataView. In questo modo è più facile, ad esempio, caricare un file MP3 e leggere i tag dei metadati da utilizzare nel lettore audio. Oppure carica un shapefile e trasformalo in un modello WebGL. Oppure leggi i tag EXIF su un file JPEG e mostrali nell'applicazione per le presentazioni.

Il problema degli ArrayBuffer XHR è un po' complicato leggere i dati di tipo struct dal buffer. DataView è ideale per leggere alcuni numeri alla volta in modo endian-safe, le visualizzazioni array tipolate sono ideali per leggere array di numeri nativi endian allineati alla dimensione dell'elemento. Quello che ci è sembrato mancante è un modo di leggere in array e struct di dati in modo conveniente. Inserisci DataStream.js.

DataStream.js è una libreria di array typed che legge e scrive valori scalari, stringhe, array e struct di dati da ArrayBuffers in modo simile a un file.

Esempio di lettura in un array di valori in virgola mobile da 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 diventa davvero utile per la lettura di dati più complessi. Supponi di avere un metodo che legge gli indicatori 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);
}

In alternativa, utilizza il metodo DataStream.readStruct per leggere le struct di dati. Il metodo readStruct accetta un array di definizione dello struct contenente i tipi dei membri dello struct. Dispone di funzioni di callback per la gestione di tipi complessi e gestisce anche gli array di dati e gli struct nidificati:

// 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
]);

Come puoi vedere, la definizione dello struct è un array piatto di coppie [name, type]. Gli struct nidificati vengono eseguiti utilizzando un array per il tipo. Gli array vengono definiti utilizzando un array di tre elementi, in cui il secondo elemento è il tipo di elemento array e il terzo elemento è la lunghezza dell'array (come numero, come riferimento a un campo letto in precedenza o come funzione di callback). Il primo elemento della definizione dell'array è inutilizzato.

I valori possibili per questo tipo sono i seguenti:

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.

Puoi vedere un esempio reale della lettura nei metadati JPEG qui. La demo utilizza DataStream.js per leggere la struttura a livello di tag del file JPEG (insieme ad alcune analisi EXIF) e jpg.js per la decodifica e la visualizzazione dell'immagine JPEG in JavaScript.

Cronologia degli array digitati

Gli array typed hanno avuto origine nella fase iniziale di implementazione di WebGL, quando abbiamo scoperto che il passaggio degli array JavaScript al driver di grafica causava problemi di prestazioni. Con gli array JavaScript, l'associazione WebGL doveva allocare un array nativo e riempirlo camminando sull'array JavaScript e trasmettendo ogni oggetto JavaScript dell'array al tipo nativo richiesto.

Per correggere il collo di bottiglia nella conversione dei dati, Vladimir Vukicevic di Mozilla ha scritto CanvasFloatArray: un array float in stile C con interfaccia JavaScript. Ora puoi modificare CanvasFloatArray in JavaScript e passarlo direttamente a WebGL senza dover eseguire altre operazioni nell'associazione. In ulteriori iterazioni, CanvasFloatArray è stato rinominato in WebGLFloatArray, che è stato ulteriormente rinominato in Float32Array e suddiviso in un ArrayBuffer di supporto e il tipo Float32Array-view per accedere al buffer. Sono stati aggiunti anche tipi per altre dimensioni di numeri interi e in virgola mobile e per le varianti con segno/senza firma.

Note sul layout

Fin dall'inizio, la progettazione degli array typed è stata guidata dalla necessità di trasferire in modo efficiente dati binari alle librerie native. Per questo motivo, le viste array tipiche operano su dati allineati nell'endianità nativa della CPU host. Queste decisioni consentono a JavaScript di raggiungere le massime prestazioni durante operazioni come l'invio dei dati di vertice alla scheda grafica.

DataView è progettato specificamente per l'I/O di file e rete, in cui i dati hanno sempre un'endianità specificata e potrebbero non essere allineati per ottenere le massime prestazioni.

La suddivisione del progetto tra assemblaggio di dati in memoria (utilizzando le viste array digitate) e I/O (utilizzando DataView) era consapevole. I motori JavaScript moderni ottimizzano in modo significativo le visualizzazioni degli array digitati e con essi ottengono prestazioni elevate sulle operazioni numeriche. Gli attuali livelli di prestazioni delle visualizzazioni array tipiche sono stati resi possibili da questa decisione di progettazione.

Riferimenti