こんにちは。福岡オフィスエンジニアの @petamoriken です。趣味でFloat16Arrayのponyfill1を公開しECMAScriptに入れてもらうように活動していたところ、喜ばしいことに2023年5月のTC39会議にてStage 3となりました。折角なのでその経緯を書いていこうと思います。
ECMAScript excitement 😉@TC39 advanced these proposals this week 🎉
— Rob Palmer (@robpalmer2) 2023年5月18日
4️⃣ Atomics.waitAsync
4️⃣ RegExp v flag
4️⃣ Well-Formed Unicode Strings
3️⃣ Decorator Metadata
3️⃣ Float16Array
2️⃣ Base64 for Uint8Array
2️⃣ Promise.withResolvers
2️⃣ TimeZone Canonicalizn
1️⃣ Intl.ZonedDateTimeFormat
半精度浮動小数点数とは
コンピュータで実数を扱うときに主に使われるのが浮動小数点数です。通常IEEE 754-2019で定義される基本形式の倍精度浮動小数点(binary64)と単精度浮動小数点数(binary32)が使われます。例えば一般的なCの処理系だとdouble
がbinary64でfloat
がbinary32ですね。JavaScriptのnumber
はbinary64です。
さて基本形式ではないですが半精度浮動小数点数(binary16)が定義されています。仮数部が10ビットと少なく十進数換算でおよそ3桁程度しか扱えませんが、少ないメモリ量でGPUに転送し高速に演算することができます。またモダンなCPUでは変換命令が搭載されています。
Float16Arrayの提案とponyfill
遡ること2014年11月。TypedArrayがThe Khronos GroupのWebGLの仕様からES2015のドラフトに移す作業がなされているさなか、ES DiscussでユーザーからFloat16Array
の提案がなされました2。
WebGLではもともとテクスチャや頂点データ、レンダーバッファにbinary16が扱えます。これはグラフィックス文脈でbinary16がよく使われており、2004年時点ですでにOpenGL拡張で扱えるようになっていた3背景があります。
一方でECMAScriptではbinary16が扱えず、WebGLにUint16Array
として与えなければなりません。つまりnumber
からbinary16のバイト列への変換を自前で用意する必要がありました。
しばらく議論が止まってしまっていましたが、たまたま自分がWebGLとES2015を勉強中にこのトピックを見つけ、Proxy
を使ってFloat16Array
のponyfillを作ることを思いつきました。Proxy
はオブジェクトの操作をトラップすることができる機能です。例えばプロパティアクセスの[[Get]]
をトラップしてみると以下のようになります。
Proxy
を使ってUint16Array
の[[Get]]
と[[Set]]
をトラップし、その都度変換することでFloat16Array
のponyfillを作ることができます。ただいざ実装してみるといくつか難しいところがありました。
- ECMAScriptは
number
に対してビット演算を使用した場合32ビット整数に丸める仕様となっているため、変換には工夫する必要があった4。 - TypedArrayのメソッドはJavaScriptからアクセスできない内部スロットを多用する仕様となっており、これは
Proxy
ではトラップできないためほとんどのメソッドを再実装する必要があった。 - 当時JavaScriptCore (Safari)でTypedArrayの整数インデックスに対してのディスクリプタが
writable: false
になっていたり、ChakraCore (Microsoft Edge)でWeakMap
のキーにProxy
が使えなかったりするバグがありワークアラウンドを実装する必要があった。
これらをなんとか解決しponyfillを公開したところ反響があり、Leo Balterさんに2017年5月のTC39会議に取り上げてもらいStage 1となりました。 qiita.com
6年の沈黙、そして
Stage 1になったのは良かったものの残念ながらネイティブに実装する必要性が認められずなかなか進みませんでした。
転機が訪れたのは2022年11月。W3Cのthe Color on the Web Community Groupよりハイダイナミックレンジ(広輝度)、広色域の色をCanvasの2DコンテキストとImageData
で扱うため、8ビット符号なし整数として格納している仕様を拡張してbinary16として格納出来るようにする提案がなされました。
Display-P3色空間のサポート
今までWeb上で扱える色空間はsRGBに限られていました。Appleによって新たに定義されたDisplay-P3色空間を扱えるように推し進められ、今では全てのモダンブラウザでCSS Color Module Level 4のcolor
函数を使うことが出来るようになっています5。
仕様の中でこのcolor
函数を使う場合に最低でも10ビットの精度を要求しており、16ビット以上の浮動小数点数を使って格納することを推奨しています6。また取りうる値も[0, 1]
でクランプされず、領域外の色も扱うことができます。
浮動小数点数で色を扱う提案
CSSに倣ってWebGLやCanvasの2DコンテキストにもDisplay-P3色空間を扱う仕様が盛り込まれています7。しかし浮動小数点数で色情報を格納する仕様がまだないためそれぞれで提案されています。
ここではCanvasの2Dコンテキストの方を紹介します。なお将来仕様が変更される可能性があることに注意してください。 github.com
Canvasの2Dコンテキストを作る際のオプションにcolorType
が追加されます。取りうる値としては"unorm8"
と"float16"
です。
さてCanvasの2DコンテキストではImageData
を使って各ピクセルの色に直接アクセスすることが出来ます。colorType: "unorm8"
の場合はUint8ClampedArray
を使いますが、colorType: "float16"
の場合はどうすればいいでしょう。残念ながら提案ではFloat16Array
がないためやむを得ずFloat32Array
を用いるとされています。
そこでStage 1のまま停滞しているFloat16Array
を紹介し、これを進めていかないかと提案してみました。
github.com
好意的に受け止めてもらい、WHATWGなどで取り上げてくれました。その結果Kevin GibbonsさんにTC39の提案の方を進めてもらうこととなり、2023年3月にStage 2に、5月にStage 3になりました🎉
終わりに
このFloat16ArrayはWebGPUやWeb Neural Network APIでも好意的に受け入れられているようです8。ECMAScriptに入ったら色々なWebの仕様で使われることになるのかなと思います。
さてピクシブ株式会社ではECMAScriptやWebの仕様を追いかけるのが大好きなフロントエンドエンジニアを募集しています。フロントエンド互助会の取り組みによって部署を横断して情報共有しており、とても楽しい環境だと思います!
- polyfillに対してグローバルやプロトタイプをモンキーパッチせずにモジュールとして提供するものをponyfillと言います。↩
- このES Discussはすでに運用を停止しています。現在はユーザーから新しい仕様を提案する際にはES Discourseが用いられています。↩
- GL_ARB_half_float_pixelを参照。↩
- 今はES2020 BigIntを使うことで32ビットよりも大きい値に対してビット演算できます。↩
- もちろんハードウェアが対応していなければ表示することが出来ません。判定にはcolor-gamutメディアクエリを使います。↩
- https://www.w3.org/TR/css-color-4/#serializing-color-function-valueを参照。↩
- Add color space support for CanvasRenderingContext2D, ImageData, and ImageBitmapやAdd drawingBufferColorSpace/unpackColorSpace attributes to WebGLRenderingContextBaseを参照。↩
- ありがたいことにWebGPU Conformance Test Suiteで自分のponyfillが採用されています。↩