Matrizes tipadas: dados binários no navegador

Ilmari Heikkinen

Introdução

Matrizes tipadas são uma adição relativamente recente aos navegadores, surgindo da necessidade de ter uma forma eficiente de lidar com dados binários no WebGL. Uma matriz tipada é um bloco de memória com uma visualização tipada, assim como as matrizes funcionam em C. Como uma matriz tipada usa memória bruta, o mecanismo JavaScript pode passar a memória diretamente para bibliotecas nativas sem ter que converter os dados em uma representação nativa com cuidado. Como resultado, as matrizes tipadas têm um desempenho muito melhor do que as matrizes JavaScript para transmitir dados para WebGL e outras APIs que lidam com dados binários.

As visualizações de matriz tipada agem como matrizes de tipo único para um segmento de um ArrayBuffer. Há visualizações para todos os tipos numéricos usuais, com nomes autodescritivos como Float32Array, Float64Array, Int32Array e Uint8Array. Há também uma visualização especial que substituiu o tipo de matriz de pixels no ImageData do Canvas: Uint8ClampedArray.

O DataView é o segundo tipo de visualização e foi feito para processar dados heterogêneos. Em vez de ter uma API semelhante a uma matriz, o objeto DataView fornece uma API get/set para ler e gravar tipos de dados arbitrários em deslocamentos arbitrários de bytes. O DataView funciona muito bem para ler e gravar cabeçalhos de arquivos e outros dados semelhantes a structs.

Noções básicas de uso de matrizes tipadas

Visualizações de matriz tipada

Para usar matrizes tipadas, crie um ArrayBuffer e uma visualização. A maneira mais fácil é criar uma visualização de matriz tipada do tamanho e tipo desejados.

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

Há vários tipos diferentes de visualizações de matriz tipada. Elas compartilham a mesma API, então, uma vez que você sabe como usar uma, você praticamente sabe como usar todas elas. No próximo exemplo, vou criar uma de cada visualização de matriz tipada atual.

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

O último é um pouco especial, ele restringe valores de entrada entre 0 e 255. Isso é especialmente útil para algoritmos de processamento de imagens da tela, já que agora você não precisa restringir manualmente sua matemática de processamento de imagens para evitar ultrapassar o intervalo de 8 bits.

Por exemplo, veja como aplicar um fator gama a uma imagem armazenada em um Uint8Array. Não muito bonito:

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

Com o Uint8ClampedArray, você pode pular a restrição manual:

pixels[i] *= gamma;

A outra maneira de criar visualizações de matriz tipada é criar primeiro um ArrayBuffer e, em seguida, criar visualizações que apontam para ele. As APIs que fornecem dados externos geralmente lidam com ArrayBuffers, portanto, é assim que você obtém uma visualização de matriz tipada para eles.

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

Você também pode ter várias visualizações para o mesmo ArrayBuffer.

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

Para copiar uma matriz tipada para outra matriz tipada, a maneira mais rápida é usar o método de conjunto de matriz tipada. Para um uso semelhante ao memcpy, crie Uint8Arrays para os buffers das visualizações e use set para copiar os dados.

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

Para usar ArrayBuffers que contêm dados de tipos heterogêneos, a maneira mais fácil é utilizar uma DataView no buffer. Suponha que tenhamos um formato de arquivo que tenha um cabeçalho com um int não assinado de 8 bits seguido por dois ints de 16 bits, seguido por uma matriz de payload de pontos flutuantes de 32 bits. A leitura desse texto com visualizações de matriz tipada é viável, mas pode ser um pouco trabalhosa. Com uma DataView, podemos ler o cabeçalho e usar uma visualização de matriz tipada para a matriz flutuante.

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

No exemplo acima, todos os valores que leio são big-endian. Se os valores no buffer forem few-endian, você poderá passar o parâmetro opcional littleEndian para o getter:

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

Observe que as visualizações de matriz tipada estão sempre na ordem de bytes nativos. Isso é para que sejam rápidos. Use uma DataView para ler e gravar dados quando a endianness for um problema.

O DataView também tem métodos para gravar valores em buffers. Esses setters são nomeados da mesma maneira que os getters, "set" seguido pelo tipo de dados.

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

Uma discussão sobre endianness

Endianness, ou ordem de bytes, é a ordem em que números de vários bytes são armazenados na memória do computador. O termo big-endian descreve uma arquitetura de CPU que armazena o byte mais significativo primeiro; little-endian, o byte menos significativo primeiro. A disposição de extremidade usada em determinada arquitetura de CPU é totalmente arbitrária. Há boas razões para escolher qualquer uma delas. Na verdade, algumas CPUs podem ser configuradas para oferecer suporte a dados big-endian e few-endian.

Por que você precisa se preocupar com a endianness? O motivo é simples. Ao ler ou gravar dados do disco ou da rede, a endianness dos dados precisa ser especificada. Isso garante que os dados sejam interpretados corretamente, independentemente da extremidade da CPU que está trabalhando com eles. Em nosso mundo cada vez mais conectado em rede, é essencial oferecer suporte adequado a todos os tipos de dispositivos, big-endian, small-endian, que podem precisar trabalhar com dados binários provenientes de servidores ou outros pares na rede.

A interface do DataView foi projetada especificamente para ler e gravar dados de e para arquivos e a rede. O DataView opera em dados com uma endianness especificada. A extremidade, grande ou pequena, deve ser especificada com todos os acessos de cada valor, assegurando que você obtenha resultados consistentes e corretos na leitura ou gravação de dados binários, independentemente da endianness da CPU em que o navegador está sendo executado.

Normalmente, quando o aplicativo lê dados binários de um servidor, é preciso fazer a verificação por eles uma vez para convertê-los nas estruturas de dados que o aplicativo usa internamente. O DataView deve ser usado durante esta fase. Não é uma boa ideia usar as visualizações de matriz de vários bytes (Int16Array, Uint16Array etc.) diretamente com os dados buscados via XMLHttpRequest, FileReader ou qualquer outra API de entrada/saída, porque as visualizações de matriz tipada usam a extremidade nativa da CPU. Falaremos sobre isso mais adiante.

Vamos analisar alguns exemplos simples. O formato de arquivo Windows BMP era o formato padrão de armazenamento de imagens nos primeiros dias do Windows. A documentação no link acima indica claramente que todos os valores inteiros no arquivo são armazenados no formato little-endian. Veja um snippet de código que analisa o início do cabeçalho BMP usando a biblioteca DataStream.js que acompanha este artigo:

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

Aqui está outro exemplo, este da demonstração de renderização de High Dynamic Range no projeto de exemplos de WebGL. Esta demonstração faz o download de dados brutos de ponto flutuante small-endian, representando texturas de High Dynamic Range, e precisa fazer o upload deles para o WebGL. Este é o snippet de código que interpreta corretamente os valores de ponto flutuante em todas as arquiteturas de CPU. Suponha que a variável “arrayBuffer” seja um ArrayBuffer que acaba de ser baixado do servidor 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);

A regra prática é: ao receber dados binários do servidor da Web, faça uma passagem com um DataView. Leia os valores numéricos individuais e armazene-os em outra estrutura de dados, seja um objeto JavaScript (para pequenas quantidades de dados estruturados) ou uma visualização de matriz tipada (para grandes blocos de dados). Isso garante que seu código funcione corretamente em todos os tipos de CPU. Use também o DataView para gravar dados em um arquivo ou a rede e certifique-se de especificar adequadamente o argumento littleEndian para os vários métodos set a fim de produzir o formato de arquivo que você está criando ou usando.

Lembre-se, todos os dados que passam pela rede têm, implicitamente, um formato e um endianness (pelo menos para quaisquer valores de múltiplos bytes). Defina e documente claramente o formato de todos os dados que seu aplicativo envia pela rede.

APIs de navegador que usam matrizes tipadas

Vou apresentar uma breve visão geral das diferentes APIs de navegador que usam matrizes tipadas. O recorte atual inclui WebGL, Canvas, API de áudio da Web, XMLHttpRequests, WebSockets, Web Workers, API Media Source e APIs File. Na lista de APIs, você pode perceber que as matrizes tipadas são adequadas para trabalhos multimídia que exigem maior desempenho e para transmissão de dados de maneira eficiente.

WebGL

O primeiro uso de Matrizes tipadas foi no WebGL, onde ele é usado para transmitir dados de buffer e de imagem. Para definir o conteúdo de um objeto de buffer do WebGL, use a chamada gl.bufferData() com uma matriz tipada.

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

Matrizes tipadas também são usadas para transmitir dados de textura. Veja um exemplo básico de transmissão de conteúdo de textura usando uma matriz tipada.

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

Você também precisa de matrizes tipadas para ler pixels do contexto WebGL.

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

Tela 2D

Recentemente, o objeto ImageData Canvas foi criado para funcionar com a especificação de matrizes tipadas. Agora você pode acessar uma representação de matrizes tipadas dos pixels em um elemento de tela. Isso é útil, já que agora você também pode criar e editar matrizes de pixels de tela sem mexer no elemento canvas.

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

XMLHttpRequest2

XMLHttpRequest recebeu uma otimização de matriz tipada, e agora você pode receber uma resposta de matriz tipada em vez de analisar uma string JavaScript em uma matriz tipada. Isso é realmente útil para passar dados obtidos diretamente para APIs multimídia e para analisar arquivos binários buscados na rede.

Basta definir o responseType do objeto XMLHttpRequest como "arraybuffer".

xhr.responseType = 'arraybuffer';

Lembre-se de que você deve estar ciente dos problemas de endianness ao fazer o download de dados da rede! Consulte a seção sobre endianness acima.

APIs File

O FileReader pode ler o conteúdo do arquivo como um ArrayBuffer. Em seguida, você pode anexar visualizações de matriz tipada e DataViews ao buffer para manipular seu conteúdo.

reader.readAsArrayBuffer(file);

Você também deve considerar a endianness aqui. Confira a seção de endianness para mais detalhes.

Objetos transferíveis

Os objetos transferíveis em postMessage tornam a transmissão de dados binários para outras janelas e Web Workers muito mais rápida. Quando você envia um objeto para um worker como transferível, o objeto fica inacessível na linha de execução de envio e o worker de recebimento recebe a propriedade do objeto. Isso permite uma implementação altamente otimizada, na qual os dados enviados não são copiados, apenas a propriedade da matriz tipada é transferida para o destinatário.

Para usar objetos transferíveis com Web Workers, você precisa usar o método webkitPostMessage no worker. O método webkitPostMessage funciona como postMessage, mas usa dois argumentos em vez de apenas um. O segundo argumento adicionado é uma matriz de objetos que você quer transferir para o worker.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Para recuperar os objetos do worker, ele pode transmiti-los de volta para a linha de execução principal da mesma maneira.

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

Nenhuma cópia, uau!

API Media Source

Recentemente, os elementos de mídia também receberam algum benefício de matriz tipada na forma da API Media Source. Você pode passar diretamente uma matriz tipada que contém dados de vídeo para um elemento de vídeo usando webkitSourceAppend. Isso faz com que o elemento de vídeo anexe os dados do vídeo após o vídeo existente. O SourceAttach é ótimo para fazer intersticiais, listas de reprodução, streaming e outros usos nos quais é possível reproduzir vários vídeos usando um único elemento de vídeo.

video.webkitSourceAppend(uint8Array);

WebSockets binários

Você também pode usar matrizes tipadas com WebSockets para evitar ter que criar strings em todos os seus dados. Ótimo para escrever protocolos eficientes e minimizar o tráfego de rede.

socket.binaryType = 'arraybuffer';

Ufa! Isso encerra a revisão da API. Vamos analisar bibliotecas de terceiros para lidar com matrizes tipadas.

Bibliotecas de terceiros

jDataView

jDataView implementa um paliativo DataView para todos os navegadores. O DataView costumava ser um recurso exclusivo do WebKit, mas agora é compatível com a maioria dos outros navegadores. A equipe de desenvolvedores do Mozilla está criando um patch para ativar o DataView no Firefox também.

Eric Bidelman, da equipe de Relações com desenvolvedores do Chrome, escreveu um exemplo pequeno de leitor de tags MP3 ID3 que usa jDataView. Aqui está um exemplo de uso da postagem do 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ção de strings

Trabalhar com strings em matrizes tipadas pode ser um pouco trabalhoso no momento, mas existe a biblioteca de codificação de strings que pode ajudar nisso. A stringencoding implementa a especificação de codificação de string de matriz tipada proposta, o que também é uma boa maneira de entender o que está por vir.

Veja um exemplo de uso básico de stringencoding:

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

BitView.js

Escrevi uma pequena biblioteca de manipulação de bits para matrizes tipadas chamada BitView.js. Como o nome indica, ele funciona como o DataView, mas funciona com bits. Com o BitView, você pode obter e definir o valor de um bit em um determinado deslocamento de bit em um ArrayBuffer. O BitView também possui métodos para armazenar e carregar ints de 6 e 12 bits em deslocamentos arbitrários de bits.

Os inteiros de 12 bits são bons para trabalhar com coordenadas de tela, já que as telas tendem a ter menos de 4096 pixels ao longo da dimensão mais longa. Ao usar ints de 12 bits em vez de ints de 32 bits, você obtém uma redução de tamanho de 62%. Para um exemplo mais extremo, eu estava trabalhando com Shapefiles que usam pontos flutuantes de 64 bits para as coordenadas, mas não preciso da precisão porque o modelo só seria mostrado no tamanho da tela. Alternar para coordenadas base de 12 bits com deltas de 6 bits para codificar alterações em relação à coordenada anterior reduziu o tamanho do arquivo para um décimo. Clique aqui para acessar uma demonstração desse recurso.

Confira um exemplo de como usar o 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

Uma das coisas mais interessantes sobre matrizes tipadas é como elas facilitam o gerenciamento de arquivos binários em JavaScript. Em vez de analisar cada caractere e convertê-los manualmente em números binários, agora você pode conseguir um ArrayBuffer com XMLHttpRequest e processá-lo diretamente usando um DataView. Isso facilita, por exemplo, carregar um arquivo MP3 e ler as tags de metadados para uso no player de áudio. Ou carregue em um shapefile e transforme em um modelo WebGL. Ou leia as tags EXIF de um JPEG e mostre-as em seu aplicativo de apresentação de slides.

O problema com XHRs ArrayBuffer é que a leitura de dados semelhantes a structs do buffer é um pouco trabalhosa. O DataView é bom para ler alguns números por vez com segurança endian. As visualizações de matriz tipada são boas para ler matrizes de números nativos alinhados ao tamanho de elementos. Sentimos que faltava uma maneira de ler matrizes e structs de dados de maneira conveniente e segura. Insira o DataStream.js.

DataStream.js é uma biblioteca de matrizes tipadas que lê e grava escalares, strings, matrizes e estruturas de dados de ArrayBuffers de maneira semelhante a um arquivo.

Exemplo de leitura em uma matriz de pontos flutuantes de um 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);

Onde o DataStream.js se torna realmente útil é na leitura de dados mais complexos. Suponha que você tenha um método que lê marcadores 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);
}

Ou use o método DataStream.readStruct para ler estruturas de dados. O método readStruct recebe uma matriz de definição de struct que contém os tipos dos membros da struct. Ela também tem funções de callback para lidar com tipos complexos, além de processar matrizes de dados e structs aninhados:

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

A definição do struct é uma matriz simples de pares de [nome, tipo]. Estruturas aninhadas são realizadas com uma matriz para o tipo. Matrizes são definidas usando uma matriz de três elementos, em que o segundo elemento é o tipo de elemento da matriz e o terceiro elemento é o comprimento da matriz (seja como um número, como uma referência ao campo lido anteriormente ou como uma função de callback). O primeiro elemento da definição da matriz não é usado.

Os valores possíveis para o tipo são os seguintes:

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.

Confira um exemplo ao vivo de leitura em metadados JPEG aqui. A demonstração usa o DataStream.js para ler a estrutura no nível da tag do arquivo JPEG (com algumas análises EXIF) e jpg.js para decodificar e exibir a imagem JPEG em JavaScript.

Histórico de matrizes tipadas

Matrizes tipadas começaram no estágio inicial de implementação do WebGL, quando descobrimos que passar matrizes JavaScript para o driver gráfico estava causando problemas de desempenho. Com as matrizes JavaScript, a vinculação WebGL precisava alocar uma matriz nativa e preenchê-la caminhando sobre a matriz JavaScript e transmitir todos os objetos JavaScript na matriz para o tipo nativo necessário.

Para corrigir o gargalo de conversão de dados, Vladimir Vukicevic do Mozilla criou o CanvasFloatArray: uma matriz flutuante no estilo C com uma interface JavaScript. Agora, é possível editar o CanvasFloatArray em JavaScript e o transmitir diretamente ao WebGL, sem que seja necessário fazer nenhum trabalho extra na vinculação. Em outras iterações, CanvasFloatArray foi renomeado como WebGLFloatArray, que foi renomeado como Float32Array e dividido em um ArrayBuffer de apoio e pela visualização tipo Float32Array para acessar o buffer. Os tipos também foram adicionados para outros tamanhos inteiros e de ponto flutuante e para variantes assinadas/não assinadas.

Considerações sobre o design

Desde o início, o design das matrizes tipadas foi impulsionado pela necessidade de transmitir dados binários de maneira eficiente para bibliotecas nativas. Por esse motivo, as visualizações de matriz tipada operam sobre dados alinhados na endianness nativo da CPU do host. Essas decisões possibilitam que o JavaScript alcance o desempenho máximo durante operações como o envio de dados de vértice à placa de vídeo.

O DataView foi projetado especificamente para E/S de arquivo e rede, em que os dados sempre têm uma endianness especificada e podem não estar alinhados para desempenho máximo.

A divisão de projeto entre a montagem de dados na memória (usando as visualizações de matriz tipadas) e a E/S (usando o DataView) foi consciente. Os mecanismos JavaScript modernos otimizam intensamente as visualizações de matrizes tipadas e alcançam alto desempenho em operações numéricas com eles. Os níveis atuais de desempenho das visualizações de matriz tipada foram possíveis graças a essa decisão de projeto.

Referências