Matrizes tipadas: dados binários no navegador

Ilmari Heikkinen

Introdução

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

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

DataView é o segundo tipo de visualização e se destina a lidar com 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 de bytes arbitrários. O DataView funciona muito bem para ler e gravar cabeçalhos de arquivos e outros dados do tipo struct.

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

Visualizações de matrizes tipadas

Para usar matrizes tipadas, você precisa criar um ArrayBuffer e uma visualização para ele. A maneira mais fácil é criar uma visualização de matriz digitada 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 matrizes digitadas. Todas elas compartilham a mesma API, assim, quando você sabe como usar uma, você sabe muito bem como usar todas elas. Vou criar uma das visualizações de matrizes digitadas que já existem no próximo exemplo.

// 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 limita os valores de entrada entre 0 e 255. Isso é especialmente útil para algoritmos de processamento de imagens do Canvas, já que agora você não precisa limitar manualmente a matemática de processamento de imagens para evitar o transbordamento do intervalo de 8 bits.

Por exemplo, veja como aplicar um fator de 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 fixação manual:

pixels[i] *= gamma;

A outra maneira de criar visualizações de matrizes com tipos é criar primeiro um ArrayBuffer e depois visualizações que apontam para ele. As APIs que recebem dados externos normalmente lidam com ArrayBuffers, e é assim que você consegue uma visualização de matriz tipada delas.

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

Também é possível 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 digitada para outra, a maneira mais rápida é usar o método de conjunto de matrizes digitadas. Para um uso semelhante ao memcpy, crie Uint8Arrays para os buffers das visualizações e use o conjunto 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 com tipos heterogêneos, a maneira mais fácil é usar uma DataView para o buffer. Suponha que temos um formato de arquivo com um cabeçalho com um int não assinado de 8 bits seguido por dois ints de 16 bits, seguidos por uma matriz de payload de pontos flutuantes de 32 bits. É possível ler isso com visualizações de matrizes digitadas, mas é um pouco difícil. Com uma DataView, podemos ler o cabeçalho e usar uma visualização de matriz digitada para a matriz de ponto 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 li são big-endian. Se os valores no buffer forem small-endian, você poderá passar o parâmetro littleEndian opcional ao getter:

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

As visualizações de matrizes digitadas estão sempre na ordem de bytes nativa. Isso é para torná-los rápidos. Use uma DataView para ler e gravar dados em que o endianness será um problema.

A 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 endianidade

A paridade endian, ou ordem de bytes, é a ordem em que os 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 armazena o byte menos significativo primeiro. Qual nível de endianness é usado em uma determinada arquitetura de CPU é completamente arbitrário; há boas razões para escolher qualquer uma delas. Na verdade, algumas CPUs podem ser configuradas para oferecer suporte a dados big-endian e little-endian.

Por que você precisa se preocupar com o endianidade? O motivo é simples. Ao ler ou gravar dados do disco ou da rede, é necessário especificar o endian dos dados. Isso garante que os dados sejam interpretados corretamente, independentemente do endian da CPU que está trabalhando com eles. Em um mundo cada vez mais conectado, é essencial oferecer suporte adequado a todos os tipos de dispositivos, com endianidade grande ou pequena, que podem precisar trabalhar com dados binários provenientes de servidores ou outros pares na rede.

A interface DataView foi projetada especificamente para ler e gravar dados de e para arquivos e a rede. O DataView opera com dados com um endian especificado. O endianness, big ou little, precisa ser especificado em cada acesso de cada valor, garantindo resultados consistentes e corretos ao ler ou gravar dados binários, independentemente do endianness da CPU em que o navegador está sendo executado.

Normalmente, quando o aplicativo lê dados binários de um servidor, é necessário fazer a verificação uma vez para convertê-los nas estruturas de dados que o aplicativo usa internamente. O DataView deve ser usado durante essa fase. Não é recomendável usar as visualizações de matrizes digitadas de vários bytes (Int16Array, Uint16Array etc.) diretamente com dados buscados por XMLHttpRequest, FileReader ou qualquer outra API de entrada/saída, porque as visualizações de matrizes digitadas usam o endianidade nativa da CPU. Falaremos mais sobre isso mais tarde.

Vamos conferir alguns exemplos simples. O formato de arquivo Windows BMP era o padrão para armazenar imagens nos primeiros dias do Windows. A documentação vinculada acima indica claramente que todos os valores inteiros no arquivo são armazenados no formato little-endian. Este é 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();
  // ...
}

Confira outro exemplo, este da demonstração de renderização de alta faixa dinâmica no projeto de amostras do WebGL. Esta demonstração faz o download de dados brutos de ponto flutuante few-endian que representam 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 acabou de ser transferido do servidor por 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 geral é: ao receber dados binários do servidor da Web, faça uma única passagem com uma 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 o código funcione corretamente em todos os tipos de CPU. Use também o DataView para gravar dados em um arquivo ou na rede e 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 uma endianness (pelo menos, para quaisquer valores de múltiplos bytes). Defina e documente claramente o formato de todos os dados enviados pelo aplicativo pela rede.

APIs do navegador que usam matrizes tipadas

Apresentaremos uma breve visão geral das diferentes APIs de navegador que usam matrizes tipadas. O corte atual inclui WebGL, Canvas, API de áudio da Web, XMLHttpRequests, WebSockets, Web Workers, API Media Source e APIs de arquivo. Na lista de APIs, você pode notar que as matrizes tipadas são adequadas para trabalhos multimídia sensíveis ao desempenho e transmissão de dados de maneira eficiente.

WebGL

O primeiro uso de matrizes tipadas foi no WebGL, onde elas são usadas para transmitir dados de buffer e de imagem. Para definir o conteúdo de um objeto de buffer WebGL, use a chamada gl.bufferData() com uma matriz de tipos.

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

As matrizes tipadas também são usadas para transmitir dados de textura. Confira 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 com tipos para ler pixels do contexto do 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 Canvas ImageData foi criado para funcionar com a especificação de matrizes tipadas. Agora você pode receber uma representação de matrizes tipadas dos pixels em um elemento de tela. Isso é útil porque agora você também pode criar e editar matrizes de pixels da tela sem precisar mexer no elemento da tela.

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

XMLHttpRequest2

O XMLHttpRequest recebeu um aumento de Typed Array, e agora você pode receber uma resposta de Typed Array em vez de ter que analisar uma string JavaScript em um Typed Array. Isso é muito útil para transmitir dados buscados diretamente para APIs multimídia e para analisar arquivos binários buscados da rede.

Tudo que você precisa fazer é definir o responseType do objeto XMLHttpRequest como "arraybuffer".

xhr.responseType = 'arraybuffer';

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

APIs de arquivos

O FileReader pode ler o conteúdo do arquivo como um ArrayBuffer. Em seguida, é possível anexar visualizações de matrizes e DataViews digitados ao buffer para manipular o conteúdo.

reader.readAsArrayBuffer(file);

Você também precisa considerar o endianness. Confira a seção sobre endianidade 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 se torna 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, em que os dados enviados não são copiados, apenas a propriedade da matriz tipada é transferida para o receptor.

Para usar objetos transferíveis com Web Workers, você precisa usar o método webkitPostMessage no worker. O método webkitPostMessage funciona da mesma forma que postMessage, mas recebe 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 passá-los de volta para a linha de execução principal da mesma maneira.

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

Zero cópias, ufa!

API Media Source

Recentemente, os elementos de mídia também receberam algumas vantagens de matrizes tipadas na forma da API Media Source. É possível transmitir diretamente um Typed Array 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 atual. O SourceAppend é ótimo para fazer intersticiais, playlists, streaming e outros usos em que você queira 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 a conversão de todos os dados em string. Ótimo para escrever protocolos eficientes e minimizar o tráfego de rede.

socket.binaryType = 'arraybuffer';

Ufa! Isso conclui a análise da API. Vamos passar para as bibliotecas de terceiros para trabalhar com matrizes tipadas.

Bibliotecas de terceiros

jDataView

jDataView implementa um paliativo DataView para todos os navegadores. A DataView costumava ser um recurso exclusivo do WebKit, mas agora tem suporte da maioria dos outros navegadores. A equipe de desenvolvedores da Mozilla está no processo de lançar um patch para ativar o DataView no Firefox também.

Eric Bidelman, da equipe de relações com desenvolvedores do Chrome, escreveu um pequeno exemplo de leitor de tags MP3 ID3 que usa o jDataView. Confira 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.
}

stringencoding

Trabalhar com strings em matrizes tipadas é um pouco complicado no momento, mas a biblioteca stringencoding pode ajudar. A codificação de string implementa a especificação de codificação de string de matrizes tipadas proposta. Por isso, ela também é uma boa maneira de ter uma ideia do que está por vir.

Aqui está um exemplo de uso básico de stringencoding:

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

BitView.js

Eu escrevi uma pequena biblioteca de manipulação de bits para matrizes tipadas chamada BitView.js. Como o nome diz, ela funciona de forma muito semelhante à DataView, exceto que funciona com bits. Com o BitView, é possível receber e definir o valor de um bit em um deslocamento de bit específico em um ArrayBuffer. O BitView também tem métodos para armazenar e carregar ints de 6 e 12 bits em deslocamentos de bits arbitrários.

Ints 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 32 bits, você tem uma redução de tamanho de 62%. Para um exemplo mais extremo, trabalhei com Shapefiles que usam pontos flutuantes de 64 bits para as coordenadas, mas não precisei da precisão porque o modelo só seria mostrado no tamanho da tela. Mudar para coordenadas base de 12 bits com deltas de 6 bits para codificar mudanças da coordenada anterior reduziu o tamanho do arquivo a um décimo. Confira a demonstração aqui.

Confira um exemplo de uso do 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 trabalho com arquivos binários em JavaScript. Em vez de analisar uma string caractere por caractere e converter manualmente os caracteres em números binários e assim por diante, agora você pode receber um ArrayBuffer com XMLHttpRequest e processá-lo diretamente usando um DataView. Isso facilita, por exemplo, o carregamento de um arquivo MP3 e a leitura das tags de metadados para uso no seu reprodutor de áudio. Ou carregue um shapefile e o transforme em um modelo WebGL. Ou leia as tags EXIF de um JPEG e mostre-as no app de apresentação de slides.

O problema com XHRs de ArrayBuffer é que ler dados semelhantes a estruturas do buffer é um pouco difícil. A DataView é boa para ler alguns números por vez de maneira segura para endian, e as visualizações de matrizes digitadas são boas para ler matrizes de números endian nativos alinhados ao tamanho do elemento. Sentimos que faltava uma maneira de ler matrizes e estruturas de dados de maneira conveniente e segura para endian. Insira o DataStream.js.

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

Exemplo de leitura em uma matriz de números 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);

O DataStream.js é realmente útil para ler dados mais complexos. Suponha que você tenha um método que leia 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 estrutura que contém os tipos dos membros da estrutura. Ele tem funções de callback para processar tipos complexos e também processa matrizes de dados e estruturas aninhadas:

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

Como você pode ver, a definição de struct é uma matriz plana de pares [nome, tipo]. As estruturas aninhadas são feitas com uma matriz para o tipo. As 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 é o comprimento da matriz (como um número, como uma referência a um 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 de metadados JPEG aqui. A demonstração usa o DataStream.js para ler a estrutura de nível de tag do arquivo JPEG (junto com algumas análises EXIF) e o jpg.js para decodificar e mostrar a imagem JPEG em JavaScript.

Histórico de matrizes tipadas

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

Para corrigir o gargalo da conversão de dados, Vladimir Vukicevic, da Mozilla, escreveu CanvasFloatArray: uma matriz de números flutuantes no estilo C com uma interface JavaScript. Agora você pode editar o CanvasFloatArray em JavaScript e transmiti-lo diretamente para o WebGL sem precisar fazer nenhum trabalho extra na vinculação. Em outras iterações, o CanvasFloatArray foi renomeado como WebGLFloatArray, que foi renomeado como Float32Array e dividido em um ArrayBuffer de apoio e o Float32Array-view tipado para acessar o buffer. Os tipos também foram adicionados para outros tamanhos de números inteiros e de ponto flutuante e variantes assinadas/não assinadas.

Considerações sobre o design

Desde o início, o design dos arrays tipados foi motivado pela necessidade de transmitir dados binários de maneira eficiente para bibliotecas nativas. Por esse motivo, as visualizações de matrizes com tipo operam com dados alinhados na endianidade nativa da CPU do host. Essas decisões permitem que o JavaScript alcance o desempenho máximo durante operações como o envio de dados de vértice para a placa de vídeo.

O DataView foi projetado especificamente para E/S de arquivos e rede, em que os dados sempre têm um endian especificado e podem não estar alinhados para o desempenho máximo.

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

Referências