Tablice z określonymi danymi – dane binarne w przeglądarce.

Ilmari Heikkinen

Wprowadzenie

Tablice typu wpisane są stosunkowo nowym dodatkiem do przeglądarek, powstałym z powodu konieczności wydajnego przetwarzania danych binarnych w WebGL. Tablica typów to płyta pamięci z widokiem wpisanego tekstu, podobnie jak tablice działają w C. Tablica typu tablica jest oparta na nieprzetworzonej pamięci, dlatego mechanizm JavaScript może przekazywać pamięć bezpośrednio do bibliotek natywnych, bez konieczności żmudnej konwertowania danych na format natywny. W efekcie tablice o określonym typie działają dużo lepiej niż tablice JavaScriptu w przypadku przekazywania danych do WebGL i innych interfejsów API do obsługi danych binarnych.

Widoki tablic typów działają jak tablice jednotypowe w przypadku segmentu ArrayBuffer. Istnieją widoki dla wszystkich typowych typów liczbowych z samoopisowymi nazwami, takimi jak Float32Slate, Float64Array, Int32Array i Uint8Array. Dostępny jest też specjalny widok, który zastąpił typ tablicy pikseli w elemencie ImageData: Uint8ClampedArray w Canvas.

DataView to drugi typ widoku danych, który służy do obsługi danych heterogenicznych. Zamiast interfejsu typu tablica obiekt DataView udostępnia interfejs get/set do odczytu i zapisu dowolnych typów danych w dowolnych przesunięciach bajtów. DataView świetnie się sprawdza do odczytywania i zapisywania nagłówków plików i innych danych podobnych do tych w strukturze.

Podstawy korzystania z tablic z typem

Widoki tablic z typem

Aby używać tablic z zapisem, musisz utworzyć obiekt tablicaBuffer i widok tego obiektu. Najłatwiej jest utworzyć tablicę typu o pożądanym rozmiarze i typie.

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

Istnieje kilka różnych typów widoków tablicy. Wszystkie korzystają z tego samego interfejsu API, więc gdy już nauczysz się go używać, będziesz wiedzieć, jak korzystać z nich wszystkich. W następnym przykładzie utworzymy po jednym z każdego z obecnie istniejących widoków tablic typu.

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

Ostatni z nich jest nieco szczególny, ponieważ ogranicza wartości wejściowe do zakresu 0–255. Jest to szczególnie przydatne w przypadku algorytmów przetwarzania obrazów Canvas, ponieważ obecnie nie trzeba ręcznie ograniczać zakresu matematycznego, aby uniknąć przekroczenia 8-bitowego zakresu.

Oto przykład zastosowania współczynnika gamma do obrazu przechowywanego w tablicy Uint8Array. Niezbyt ładnie:

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

Dzięki Uint8ClampedArray możesz pominąć ręczne ograniczanie:

pixels[i] *= gamma;

Innym sposobem tworzenia widoków tablicowych o określonym typie jest utworzenie najpierw obiektu SlateBuffer, a następnie wskazujących je widoków. Interfejsy API, które pobierają dane zewnętrzne, znajdują się zwykle w tablicach SlateBuffers, więc w ten sposób otrzymujesz typ widoku tablicy.

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

Możesz też mieć kilka widoków tego samego ArrayBuffer.

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

Aby skopiować tablicę typów do innej tablicy typów, najszybszym sposobem jest użycie metody tablicy typów. Dla użycia przypominającego memcpy możesz utworzyć tablice Uint8Slate w buforach widoków i użyć polecenia set do skopiowania danych.

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

Aby używać tablic ArrayBuffer zawierających dane o różnych typach, najłatwiej jest użyć do bufora widoku danych. Załóżmy, że mamy format pliku, który ma nagłówek z 8-bitową liczbą całkowitą bez znaku, po której następują 2 16-bitowe liczby całkowite i tablica danych o typie 32-bitowe liczby zmiennoprzecinkowe. Odczytywanie tego z typowanymi widokami tablic jest możliwe, ale trochę kłopotliwe. Dzięki widokowi danych możemy odczytać nagłówek i użyć typu tablicy dla tablicy typu 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);
}

W powyższym przykładzie wszystkie odczytywane przeze mnie wartości to big-endian. Jeśli wartości w buforze są w postaci little-endian, możesz przekazać parametr littleEndian do metody getter:

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

Pamiętaj, że widoki tablic typowanych są zawsze w rodzimej kolejności bajtów. Ma to na celu przyspieszenie ich działania. Należy użyć obiektu DataView do odczytu i zapisu danych tam, gdzie końcowość może stanowić problem.

Obiekt DataView zawiera również metody zapisu wartości w buforach. Te metody ustawiania mają nazwy takie same jak metody pobierania, czyli „set” z dodatkiem typu danych.

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

Dyskusja na temat endianności

Endianność, czyli kolejność bajtów, to kolejność, w jakiej liczby wielobajtowe są przechowywane w pamięci komputera. Termin big-endian określa architekturę procesora, która przechowuje najpierw najbardziej znaczący bajt, a little-endian – najpierw najmniej znaczący bajt. Który format endianności jest używany w danej architekturze procesora, jest całkowicie dowolny; istnieją dobre powody, aby wybrać jedną z tych opcji. Niektóre procesory można skonfigurować tak, aby obsługiwały dane w układzie dużych i małych bajtów.

Dlaczego trzeba zwracać uwagę na endianność? Powód jest prosty. Podczas odczytu lub zapisu danych z dysku lub sieci należy określić endian danych. Dzięki temu dane są interpretowane prawidłowo, niezależnie od endianności procesora, z którym współpracują. W naszym coraz bardziej sieciowym świecie ważne jest, aby prawidłowo obsługiwać wszystkie rodzaje urządzeń (z wielkim lub małym endianem), które mogą potrzebować danych binarnych pochodzących z serwerów lub innych urządzeń w sieci.

Interfejs DataView został zaprojektowany specjalnie do odczytu i zapisu danych do i z plików oraz sieci. DataView działa na danych o określonej wersji. Wielkość i mała endianność muszą być określone przy każdym dostępie do każdej wartości, aby zapewnić spójne i prawidłowe wyniki podczas odczytu lub zapisu danych binarnych, niezależnie od wielkości i małej endianności procesora, na którym działa przeglądarka.

Gdy aplikacja odczytuje dane binarne z serwera, musi je zwykle przeskanować, aby przekonwertować je na struktury danych, których używa wewnętrznie. Na tym etapie należy używać DataView. Nie zalecamy używania widoków tablic typowych o wielokrotnej wielkości bajtów (Int16Array, Uint16Array itp.) bezpośrednio z danymi pobieranymi za pomocą XMLHttpRequest, FileReader lub dowolnego innego interfejsu API wejścia/wyjścia, ponieważ widoki tablic typowych używają natywnej kolejności bajtów procesora. Więcej informacji na ten temat znajdziesz później.

Przyjrzyjmy się kilku prostym przykładom. W początkach systemu Windows format pliku Windows BMP był standardowym formatem przechowywania obrazów. W powiązanej wyżej dokumentacji wyraźnie zaznaczono, że wszystkie wartości całkowite w pliku są przechowywane w formacie little-endian. Oto fragment kodu, który analizuje początek nagłówka BMP za pomocą biblioteki DataStream.js, która jest dołączona do tego artykułu:

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

A oto kolejny przykład z prezentacji renderowania High Dynamic Range w projekcie Przykłady w WebGL. Ta prezentacja pobiera dane w postaci nieprzetworzonych danych zmiennoprzecinkowych w postaci little-endian, które reprezentują tekstury o wysokim zakresie dynamicznym, i musi przesłać je do WebGL. Oto fragment kodu, który prawidłowo interpretuje wartości zmiennoprzecinkowe na wszystkich architekturach procesorów. Załóżmy, że zmienna „arrayBuffer” to tablica ArrayBuffer, która została właśnie pobrana z serwera za pomocą 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);

Zasada jest taka, że po otrzymaniu danych binarnych z serwera internetowego należy je przejrzeć za pomocą DataView. Odczytuj poszczególne wartości liczbowe i zapisuj je w innej strukturze danych: obiekcie JavaScript (w przypadku niewielkich ilości uporządkowanych danych) lub widoku tablicy w formacie typowym (w przypadku dużych bloków danych). Dzięki temu Twój kod będzie działać prawidłowo na wszystkich procesorach. Używaj też DataView do zapisywania danych w pliku lub sieci i pamiętaj, by odpowiednio określić argument littleEndian dla różnych metod set. Pozwoli to uzyskać format pliku, który tworzysz lub używasz.

Pamiętaj, że wszystkie dane, które pośrednio przepływają przez sieć, mają format i końcowość (przynajmniej w przypadku wartości wielobajtowych). Pamiętaj, aby jasno określić i udokumentować format wszystkich danych, które aplikacja wysyła przez sieć.

Interfejsy API przeglądarki korzystające z tablic typu

Omówię pokrótce różne interfejsy API w przeglądarkach, które obecnie korzystają z tablic typów. Obecne ograniczenie dotyczy interfejsów WebGL, Canvas, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, Media Source API i File API. Na liście interfejsów API widać, że tablice typu są nadają się do wydajnej pracy multimedialnej oraz do efektywnego przekazywania danych.

WebGL

Tablice typowane zostały po raz pierwszy zastosowane w WebGL, gdzie można używać ich do przesyłania danych buforowych i graficznych. Aby ustawić zawartość obiektu bufora WebGL, użyj wywołania gl.bufferData() z tablicą typowaną.

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

Typowe tablice są też używane do przekazywania danych tekstury. Oto podstawowy przykład przekazywania zawartości tekstury za pomocą tablicy typowanej.

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

Aby odczytać piksele z kontekstu WebGL, musisz też użyć tablic typowanych.

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

Płótno 2D

Niedawno obiekt Canvas ImageData został dostosowany do specyfikacji tablic typowych. Teraz możesz uzyskać tablice typowych pikseli w elemencie canvas. Jest to przydatne, ponieważ teraz możesz tworzyć i edytować tablice pikseli odbitki na płótnie bez konieczności manipulowania elementem odbitki na płótnie.

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

XMLHttpRequest2

Interfejs XMLHttpRequest został ulepszony o typ tablicy i teraz możesz otrzymywać odpowiedź typu Typed Array zamiast konieczności parsowania ciągu JavaScript na typ Typed Array. Jest to bardzo przydatne rozwiązanie do przekazywania pozyskanych danych bezpośrednio do interfejsów multimedialnych API oraz do analizowania plików binarnych pozyskanych z sieci.

Wystarczy, że ustawisz responseType obiektu XMLHttpRequest na „arraybuffer”.

xhr.responseType = 'arraybuffer';

Pamiętaj, że podczas pobierania danych z sieci musisz mieć świadomość problemów związanych z endianem. Zobacz sekcję o orientacji danych powyżej.

Interfejsy API plików

Interfejs FileReader może odczytać zawartość pliku jako tablicę ArrayBuffer. Następnie możesz dołączyć do bufora widoki tablic i widoków danych, aby manipulować ich zawartością.

reader.readAsArrayBuffer(file);

Tutaj też należy pamiętać o orientacji bajtów. Szczegółowe informacje znajdziesz w sekcji dotyczącej endianności.

Przenośne obiekty

Przenośne obiekty w postMessage znacznie przyspieszają przekazywanie danych binarnych do innych okien i elementów Web Worker. Gdy wysyłasz obiekt do pracownika jako obiekt możliwy do przeniesienia, staje się on niedostępny w wątku wysyłania, a pracownik, który go odbiera, staje się jego właścicielem. Pozwala to na wysoce zoptymalizowaną implementację, w której wysyłane dane nie są kopiowane, a jedynie własność tablicy typowej jest przenoszona do odbiorcy.

Aby używać obiektów przenośnych z procesami web worker, musisz użyć metody webkitPostMessage w procesie. Metoda webkitPostMessage działa tak samo jak postMessage, ale zamiast jednego argumentu przyjmuje 2 argumenty. Dodany drugi argument to tablica obiektów, które chcesz przekazać do workera.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Aby odzyskać obiekty z instancji roboczej, instancja robocza może w ten sam sposób przekazać je z powrotem do wątku głównego.

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

Zero kopii, brawo!

Interfejs Media Source API

Niedawno m.in. interfejs API Media Source API zapewnił elementom multimedialnym pewne przydatne funkcje związane z tablicą typu. Za pomocą metody webkitSourceAppend możesz bezpośrednio przekazywać tablicę Typed Array zawierającą dane wideo do elementu wideo. W ten sposób element wideo doda dane wideo po istniejącym filmie. Element SourceAppend świetnie nadaje się do tworzenia reklam pełnoekranowych, playlist, strumieniowania i innych zastosowań, w przypadku których chcesz odtworzyć kilka filmów za pomocą jednego elementu wideo.

video.webkitSourceAppend(uint8Array);

Binarne WebSockets

Aby uniknąć konieczności konwertowania wszystkich danych na ciągi znaków, możesz też używać tablic typowanych z WebSockets. Jest to świetne rozwiązanie do tworzenia wydajnych protokołów i minimalizowania ruchu w sieci.

socket.binaryType = 'arraybuffer';

Uff! To już koniec sprawdzania interfejsu API. Przyjrzyjmy się teraz bibliotekom innych firm, które obsługują tablice typów.

Biblioteki innych firm

jDataView

jDataView implementuje element DataView shim dla wszystkich przeglądarek. Funkcja DataView była kiedyś dostępna tylko w WebKit, ale teraz obsługuje ją większość innych przeglądarek. Zespół programistów Mozilla pracuje nad poprawką, która umożliwi korzystanie z DataView także w przeglądarce Firefox.

Eric Bidelman z zespołu ds. relacji z deweloperami Chrome opracował małe oprogramowanie do odczytu tagów MP3 ID3, które korzysta z jDataView. Oto przykład użycia z posta na blogu:

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

Praca z ciągłymi w typowych tablicach jest obecnie nieco uciążliwa, ale można skorzystać z biblioteki do kodowania i dekodowania napisów. Stringencoding implementuje proponowaną specyfikację kodowania ciągów znaków w tablicy typów, więc jest też dobrym sposobem na zapoznanie się z przyszłością.

Oto przykład podstawowego użycia kodowania ciągu znaków:

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

BitView.js

Napisałem małą bibliotekę do manipulowania bitami w typowanych tablicach o nazwie BitView.js. Jak sama nazwa wskazuje, działa ona podobnie jak widok danych, ale z bitami. Za pomocą interfejsu BitView możesz pobierać i ustawiać wartość bitu w danym przesunięciu bitowym w tablicy ArrayBuffer. BitView zawiera też metody przechowywania i wczytywania 6- i 12-bitowych liczb całkowitych w dowolnych przesunięciach bitowych.

12-bitowe liczby całkowite są przydatne do pracy z koordynatami ekranu, ponieważ wyświetlacze mają zwykle mniej niż 4096 pikseli na dłuższym boku. Korzystanie z 12-bitowych liczb całkowitych zamiast 32-bitowych powoduje zmniejszenie rozmiaru o 62%. W bardziej skrajnym przykładzie pracowałem z plikami Shape, które używają 64-bitowych liczb zmiennoprzecinkowych do współrzędnych, ale nie potrzebowałem takiej dokładności, ponieważ model miał być wyświetlany tylko w rozmiarach ekranu. Przejście na 12-bitowe współrzędne podstawowe z 6-bitowymi różnicami w celu zakodowania zmian w stosunku do poprzednich współrzędnych spowodowało zmniejszenie rozmiaru pliku o 10%. Demo tej funkcji znajdziesz tutaj.

Oto przykład użycia biblioteki 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

Jedną z najbardziej ekscytujących rzeczy związanych z tablicami typowanymi jest to, że ułatwiają one obsługę plików binarnych w JavaScript. Zamiast analizować ciąg znak po znaku i ręcznie konwertować znaki na liczby dwójkowe, możesz teraz uzyskać ArrayBuffer za pomocą XMLHttpRequest i bezpośrednio przetworzyć go za pomocą DataView. Ułatwia to np. wczytanie pliku MP3 i odczytywanie tagów metadanych, które zostaną użyte w odtwarzaczu audio. Możesz też załadować plik shape i przekształcić go w model WebGL. Możesz też odczytać tagi EXIF z pliku JPEG i wyświetlić je w aplikacji do pokazu slajdów.

Problem z ArrayBuffer XHR polega na tym, że odczytywanie danych typu struct z bufora jest nieco uciążliwe. DataView dobrze sprawdza się przy odczytywaniu kilku liczb naraz w sposób zapewniający bezpieczeństwo w internecie. Widok tablic z typem jest dobry do odczytywania tablic z natywnymi liczbami końcowymi wyrównanymi do rozmiaru elementu. Brakowało nam sposobu na odczytywanie tablic i struktur danych w wygodny sposób, który nie zależy od endianności. Wpisz DataStream.js.

DataStream.js to biblioteka tablic typowych, która odczytuje i zapisuje skalary, ciągi znaków, tablice i struktury danych z ArrayBuffers w sposób podobny do pliku.

Przykład odczytu tablicy liczb zmiennoprzecinkowych z 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 jest szczególnie przydatny przy odczytywaniu bardziej złożonych danych. Załóżmy, że używasz metody odczytującej znaczniki 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);
}

Możesz też użyć metody DataStream.readStruct, aby odczytać struktury danych. Metoda readStruct pobiera tablicę definicji typu struct, która zawiera typy elementów struct. Zawiera funkcje wywołania zwrotnego do obsługi złożonych typów oraz obsługuje tablice danych i zagnieżdżone struktury:

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

Jak widać, definicja struktury to płaska tablica par [name, type]. Struktury zagnieżdżone są tworzone przez tablicę typu. Tablice są definiowane za pomocą tablicy trójelementowej, gdzie drugi element to typ elementu tablicy, a trzeci element to długość tablicy (jako liczba, jako odwołanie do poprzednio odczytanego pola lub jako funkcja wywołania zwrotnego). Pierwszy element definicji tablicy jest nieużywany.

Możliwe wartości tego typu:

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.

Przykład odczytu metadanych JPEG znajdziesz tutaj. Wersja demonstracyjna wykorzystuje DataStream.js do odczytu struktury na poziomie tagu pliku JPEG (wraz z niektórymi analizami EXIF) oraz plik jpg.js do dekodowania i wyświetlania obrazu JPEG w JavaScript.

Historia tablicy typów

Tablice typów pojawiły się na wczesnym etapie wdrażania WebGL, gdy odkryliśmy, że przekazywanie tablic JavaScript do sterownika grafiki powoduje problemy z wydajnością. W przypadku tablic JavaScript powiązanie WebGL musi przydzielić tablicę natywną i wypełnić ją, przechodząc po tablicy JavaScript i przekazując każdy obiekt JavaScript z tablicy do wymaganego typu natywnego.

Aby rozwiązać wąskie gardło konwersji danych, Vladimir Vukicevic stworzył w Mozillii tablicę CanvasFloatSlate: tablicę zmiennoprzecinkową w stylu C z interfejsem JavaScript. Teraz można edytować obiekt CanvasFloatArray w JavaScripcie i przekazywać go bezpośrednio do WebGL bez konieczności wykonywania dodatkowych działań związanych z wiązaniem. W kolejnych iteracjach nazwa CanvasFloatArray została zmieniona na WebGLFloatArray, a następnie na Float32Array i podzielona na ArrayBuffer i typowany widok Float32Array, aby uzyskać dostęp do bufora. Dodaliśmy też typy dla innych rozmiarów liczb całkowitych i zmiennoprzecinkowych oraz dla wersji z wartościami dodatnimi i ujemnymi.

Uwagi dotyczące projektu

Od samego początku projektowanie tablic typowanych był podyktowany potrzebą wydajnego przekazywania danych binarnych do bibliotek natywnych. Z tego powodu widoki tablicy o typach korzystają z wyrównanych danych w natywnej wersji procesora hosta. Dzięki tym decyzjom JavaScript może osiągnąć maksymalną wydajność podczas operacji takich jak wysyłanie danych wierzchołków do karty graficznej.

Biblioteka DataView została zaprojektowana specjalnie do obsługi wejść/wyjść plików i sieci, gdzie dane mają zawsze określoną kolejność bajtów i mogą nie być wyrównane pod kątem maksymalnej wydajności.

Podział projektu na składanie danych w pamięci (z wykorzystaniem widoków tablic typowanych) i wejście/wyjście (z wykorzystaniem widoku danych) był świadomy. Nowoczesne silniki JavaScriptu znacznie optymalizują widoki tablic typowych i osiągają wysoką wydajność w przypadku operacji numerycznych z ich użyciem. Ta decyzja projektowa umożliwiła osiągnięcie obecnego poziomu wydajności widoków tablic typu.

Pliki referencyjne