canvas APIのデザイン方針

というわけでcanvas APIでちゃんとしたアプリケーションが書けるかというテストに入る。
まだ未パッチのmoshで動かないなど運用上難しいところがあって改善中というフェーズ。多分0.3.0では、今までAO benchでやっていたような"bytevectorにビットマップ書いてPNGにする"とか、下のシンプル描画くらいが標準ライブラリとして入る。はず。
実はこれから作っていくモデラのような、対話的アプリケーションはWindows版のmoshで無いと書けない。

  • X11に関してはGtkをサポートしたい。
  • OS Xに関しては絶望的としか申し上げようがない。少なくとも僕はQuickDraw以降のMacプログラミングを知らないのでほぼゼロから始めないと。。
  • SDLにはWindowを一つしか開けないという絶妙な制限があるので、自前のwindowシステムを上に載せるつもり。
    • たぶんMonaOS上でも同様。cairo自体は(gccさえ使えば)かなり移植性は高いし、FFIは抽象化してあるので静的リンクに変えても直ぐ使えるはず。

APIは幾つかのレベルに分かれている。

シンプル描画API

シンプル描画APIは、displayとかwrite程度の手軽さで画像を作画することが出来る。

(import (rnrs) (ext canvas) (ext canvas simple))
(define cv (make-canvas 400 400))
(set-source color-black)
(paint cv)
(set-source color-white)
(move-to cv 10 10)
(line-to cv 390 10)
(line-to cv 390 390)
(line-to cv 10 390)
(close-path cv)
(stroke cv) 
(canvas->pdf/mm cv "test.pdf")

(ext canvas simple)はpaintとかstrokeのような非常に一般的な名称を手続きとして使用する。R6RSのライブラリはシンボルにprefixを付けたりリネームしたりも出来るので、その辺で対処して欲しい。あと、(ext canvas simple)のexportする手続きは全て破壊的だけど ! は付いていない。これはreadとかwriteに!が付かないのでそれに合わせた。
描画命令は基本的にOpenVGに合わせているが、paint(塗りつぶし)のように便利でよく使うものも追加している。
シンプル描画は可能な限り手続きをジェネリックに作って、少ない努力ですぐ書けるようにデザインしている。そのかわり動作は非効率で低速。
Windows上で使うなら、canvas-display!手続きを使って新しくWindowを開いて結果を表示することもできる(予定 - メインスレッド上じゃないとWindowを作れないので改善中)。
実装的には、canvas->XXXのオペレーションを実行する際に全ての変換やcairo contextの生成、描画命令の発行などを行っている。それまでcanvas objectの実質的な内容はただのリスト。

リスト描画API

リスト描画APIはよりパフォーマンスの高い描画APIで、( (move-to 0 0) (line-to 10 0) )のようなschemeデータで構成されたリストを渡して描画を行う。
ただこのAPIはJITCと連携しないと意味がない部分が大きいので詳細はまだ調整中。

ビットマップAPI

世間のベクタグラフィックス実装には2種類あって :

  • ハードウェアで実装されている場合は、バッファを再利用せずに毎回書く方が楽で速い。描画したあとのイメージを取っておくために転送する方が手間が掛かることさえある。
  • ソフトウェアで実装されている場合は、極力バッファを再利用する方がいい。

後者のケースであっても効率的に動作するためにビットマップAPIは描画APIから分離されている。
ビットマップAPIでは、拡大縮小回転を伴ったビットマップの貼り付けと、半透明/加算合成、畳み込み演算、色変換を提供する。
ビットマップAPIは様々なところで実装されるので難しいことになっている。Windowsの場合 :

  • ビットマップの貼り付け、半透明/加算合成 : cairo
  • 畳み込み演算 : 自前のCコード
  • 色変換 : schemeコード

畳み込み演算はプリミティブとしては1次元のX方向Y方向しか用意せず、ガウスぼかし等はSchemeのコードでカーネルを作ってやる必要がある。
ビットマップAPIで使うビットマップは、canvas以外にbytevectorからも生成できる。しかし、ビットマップを直接保存するAPIは無い(!)ので一旦canvasも作って描画しないといけない。ビットマップを初期値としてcanvasを作ることも出来る。

API/フォントAPI

APIはその名の通り色やグラデーションを作る。グラデーションは今のところ2色のRGBA線形補完しか出来ない。またsRGB専用。SLIBのような高度な色APIは(そもそもバックエンドがサポートしていない都合上)無い。
フォントAPIを含めた文字描画は詳細未定。fontconfigのようなシステムをそのまま移植してしまう手も考えられるが。。デバッグ用に、システムフォントで文字列を描画する描画命令を持つ。

方向性

このAPIで実装する重要なアプリケーションは、skymoshに有ったVisual REPL。
要するにWindowsのコンソールは通常の人間には難しいので、ターミナルエミュレータをこのAPIで実装してそれも標準ライブラリとして提供したい。
逆にDXRubyとかPyGameのような方向性はあまり目指していなくて、今のところ2D描画に特化しているし、アニメーションなどに関する考察も導入予定は無い。
ハードウェアアクセラレーションやAOTC、JITCはかなり意識してデザインしているので、Schemeで作ったデータを視覚化したい!といったときに可能な限りC言語に逃げなくても良いように考えている。
表やグラフの描画は非常に需要があるので別のライブラリとして提供することを検討中。これらはTTYやHTMLへの出力が要求される分野なのでcanvasだけではカバーできない。
描画APIに関しては、ypsilonのようにcairoやOpenGLの直接的なマッピングを提供するという方向性も考えられるが、"50万の点をプロットしたい"みたいなときにFFIで毎回型チェックしつつ呼び出すのは微妙に非効率。

問題点

GC
今のところcanvasを作りっぱなしにすることは出来ない。FFIに参照カウント方式オブジェクトのサポートを入れるのが良いと思っていて、試行錯誤している。
考えられる方法は2つあって :

  • GC_REGISTER_FINALIZERしたメモリ領域をGC_MALLOCで作ってスクリプト側に持たせ、FFI側でunwrapして使う
  • ポインタをGC_GENERAL_REGISTER_DISAPPEARING_LINKしてGC側に値をクリアさせ、時折(またはGCのたびに)チェックして解放する

前者は、多くの(Cライブラリによる)"オブジェクト"はポインタとして参照されることは無く、単にhandleとして利用されることに着目している。
後者はいわゆるweak referenceの手法で、よりジェネリックに適用できる。