2025年6月9日月曜日

テクスチャの無制限配列

これまで、シェーダーによる描画では、テクスチャを1枚ずつ個別に指定していた。

文字列描画の際、スプライト描画の要領で使うテクスチャは1つで、描画は数百文字同時に一括指定などで効率的に描画していた。
ただし、テクスチャ自体が複数枚で、それを一括で描画する仕組みを用意していなかったためテクスチャ毎に描画命令を呼び出していた。

これを改善しようと思う。


テクスチャの無制限配列指定


HLSL変数宣言


Texture2D Tex[] : register(t0) ;
こんな感じでテクスチャを指定して、Tex[n]のように使いたい。
定義自体は可能で、Shader Model 5.1以降で使える模様。


RootSignature宣言


RootSignatureのテクスチャ指定部分は下記のようにする

DescriptorTable( SRV(t0, flags=DESCRIPTORS_VOLATILE, numDescriptors=unbounded)...

flagsにはDATA_STATICか、DATA_STATIC_WHILE_SET_AT_EXECUTEを指定していたけど、無制限の場合はDESCRIPTORS_VOLATILEになるらしい。
numDescriptorsにはunboundedを指定する。

問題はデスクリプタヒープをどう指定するか。
現状初期化時に、CBV、SRV、UAV用と、サンプラ用の大きなヒープで作成しており、描画毎に同じヒープをCommandList->SetDescriptorHeapsに指定している。

テクスチャ部分はTex[0]~描画指定数分利用することになるが、SetDescriptorHeapsに配列順になっているデスクリプタヒープを渡す必要がある。
今までのデスクリプタヒープを静的なものとすると、今度は動的なデスクリプタヒープを用意することになる。


Dynamic Descriptor Heap


静的デスクリプタヒープと同様にな動的デスクリプタヒープを初期化時に用意する。
今まで渡していた静的デスクリプタヒープに加えて、動的デスクリプタヒープをSetDescriptorHeapsに渡そうとしたがエラーになる。同じタイプ(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)のものを渡せないらしく、動的デスクリプタヒープを渡したい場合は静的デスクリプタヒープは渡せなくなる。
静的デスクリプタヒープに加えて、配列分のデータを動的デスクリプタヒープで渡そうとしたけど、サンプラ以外はすべて動的デスクリプタヒープで渡すことになる。
そのため、静的デスクリプタヒープの初期化でFlagsに指定していたD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEをD3D12_DESCRIPTOR_HEAP_FLAG_NONEに変更して、動的デスクリプタヒープのFlagsにD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEを指定する。

実際描画する際、静的デスクリプタヒープと動的デスクリプタヒープのD3D12_CPU_DESCRIPTOR_HANDLEを用意して、Device->CopyDescriptorsSimple関数でコピーする。静的デスクリプタヒープでは空いている部分をアサインして、利用が終わったら返却しているため、使うリソースの順番はばらばらだったけど、動的デスクリプタヒープを使って、順番を指定できるようになったため、配列で使用できることになる。



定数バッファ(CB)や、構造化バッファ(SB)はサイズを増やせば複数描画分をまとめて渡すことが元々出来た。テクスチャも動的デスクリプタヒープを利用することで、使用するテクスチャを順番に並べることができるようになる。

これで複数枚のテクスチャ全部描画する際、複数回描画命令を実行していたのが1回の描画命令でまとめて描画が可能になった。
ある状況下で比べてみるとCPU使用率が約半分になって、GPU使用率はほぼ変わらずだった。


Barrier

以前拡張バリアの対応を行った。

それまで結構バリア処理で悩んでいて複雑になったになってきたので、修正を機に全部リセットして作り直した。
その際、通常のバリア処理はなくして拡張バリアのみにし、その結果Agility SDKに対応している環境でしか動かなくなった。

というのが今までの状況。

そのうち一般公開されるだろうと思いながら待っていたけど一向にされない。
NvidiaのGPU環境では問題なく動く。
AMDのGPU環境では、Agility SDK対応ドライバを選択して入れてあれば動く。
IntelのGPU環境では、ドライバは対応されているようなことが書いてあるが、少なくとも自分の環境ではそのドライバをインストールに失敗し、拡張バリアはサポートされない状態。

個人的には問題ないんだけど、諸事情でいろいろな環境で動かす必要が出てきたのでAgility SDKなしでも動くようにする。


通常のバリア処理



拡張バリアサポートチェック



起動時に拡張バリアのサポートチェックをして、対応してなかった場合はそこで処理をストップしていた。通常バリアから拡張バリアに完全に切り替えたのでこれで問題なかったが、
これを拡張バリアに対応していない場合でも処理を継続し、両方の環境で動くようにする。

ID3D12Device10::CheckFeatureSupport関数でD3D12_FEATURE_D3D12_OPTIONS12をチェック。
D3D12_FEATURE_DATA_D3D12_OPTIONS12.EnhancedBarriersSupportedの値を保持して、処理を切り替えるようにする。


リソース作成



ID3D12Device10::CreatePlacedResource2でリソースを作っていたけど、通常バリアのモードの場合はID3D12Device::CreatePlacedResourceに切り替える。
D3D12_RESOURCE_DESC1とD3D12_BARRIER_LAYOUTが、D3D12_RESOURCE_DESCとD3D12_RESOURCE_STATESになる。

今のライブラリはCBV、バッファ利用、SRV、テクスチャ利用に分かれているが、CBV、バッファの方は、特に問題なく用意できそう。
昔利用していた時は、リソース種類別に初期のステータスや実行コマンド毎にステータス遷移の制御を行っていたが、バッファに関しては一切の制御を止めて、D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSに任せることで単純になった。通常のバリアでもそれは同様で、初期ステータスはD3D12_RESOURCE_STATE_COMMONのまま、状態遷移は何もしないようにした。

問題はレンダーターゲットと深度バッファで、このリソースにはD3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSが指定できないため、自分で制御する必要がある。


コピー時



レンダリングして、結果をテクスチャにコピーするような場合、COPY_SOURCEに遷移してコピー後に、RENDER_TARGETやDEPTH_WRITEに戻しているが、通常バリアでも同様に遷移させる。コピー先に関してはCOPY_DESTで作成するようにしているため遷移不要で、コピー後も拡張バリアではALLOW_SIMULTANEOUS_ACCESSのおかげで何もしなくて済んでいた。
通常バリアではALLOW_SIMULTANEOUS_ACCESSを指定していても多少制御が必要で、コピー後に、ALL_SHADER_RESOURCEに遷移させないとデバッグレイヤにエラーが出力され続ける。


ミップマップ作成時



1.UAV付のテクスチャを用意して、画像をコピー。
2.各ミップマップをCSで縮小コピー。
3.UAV無しのスタティックテクスチャにコピー。
という手順でミップマップを作成している。
ミップマップ作成過程で拡張バリアを使っている箇所で、通常バリアを指定して実行してみるとデバッグレイヤにエラーが表示される。
試行錯誤の結果、通常バリアの場合1から2、2から3の間に、状態遷移を挟む必要があった。
1から2の間には、D3D12_RESOURCE_STATE_COPY_DESTから、D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCEの状態遷移を設定。
2から3の間には、D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCEから、D3D12_RESOURCE_STATE_COPY_SOURCEの状態遷移を設定。
拡張バリアの場合はD3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSの指定で、かなり自動になったけど、通常バリアの場合は若干制御が増える。






2024年8月12日月曜日

2D描画

D3D11On12をやめて、自前で文字描画を作った。
次に必要になるのが線、矩形、円の描画。矩形と円については線バージョンと塗りつぶしバージョンを用意する。


塗りつぶし用シェーダ


どう描画するのがいいか?
思いつく方法は2つで、オーソドックスに頂点バッファとインデックスバッファを用意して、別途構造化バッファで形状や色を指定する方法。
もう1つは、構造化バッファのみで描画する方法。

文字描画は後者の方法で描画していて、前から2D描画の仕組みもこれで行こうと考えていた。
いざこの方法で描画しようとした場合、形別にシェーダ関数を用意してシェーダ内で振り分けるか、呼び出し側で振り分けてシェーダ自体を切り替える必要があると至った。
どちらもシェーダにとって重くなりそうな処理なので前者の方式で描画することにした。

形状を頂点バッファ、インデックスバッファで表現することにより、シェーダを同一にできる。通常の3Dモデル描画と同じだけど、こうすることによりこの後作る円や、もっと複雑な形状もモデルの準備で済む。
複数インスタンスを同時に描画する部分は構造化バッファに位置、スケール、色情報を含めて対応することにした。
通常3Dモデルの場合はWorldMatrixを複数用意して描画するけど、サイズが大きいのとそこまで複雑な指定をしないので、制限を掛けることでサイズを減らしてパフォーマンスを向上させる。
シェーダ側でWorldMatrixを作って、m[0][0]に幅の倍率、m[1][1]に高さの倍率、m[0][3]にX座標(移動量)、m[1][3]にY座標(移動量)を指定してからmul関数で変換する。

あと、深度バッファを有効にして、z座標を調整することで自動的にZオーダが処理されるようにした。手前ほど先に描画するようにして奥をスキップできるようにソートして構造化バッファに書き込むようにする。


矩形(塗りつぶしバージョン)


矩形のモデルを下記の4点で用意
-0.5,0.5(左上)
0.5,0.5(右上)
-0.5,-0.5(左下)
0.5,-0.5(右下)


円(塗りつぶしバージョン)


3Dモデルの場合はグローシェーディングでモデルがローポリでも球に見えるけど、2Dの場合輪郭だけなのでごまかせない。
分割数16、32、64の輪郭+中心の頂点バッファのデータを用意して、描画サイズにより違和感のないクオリティを選択して描画できるようにした。


線用シェーダ


線の描画自体あまりやったことがなかったけど、唯一やってたのが視錐台の描画でD3D12_PRIMITIVE_TOPOLOGY_TYPE_LINEを使っていた。

線の描画ではD3D_PRIMITIVE_TOPOLOGY_LINELISTとD3D_PRIMITIVE_TOPOLOGY_LINESTRIPの両方を対応できるシェーダを用意したいと考えた。
通常の線描画であれば頂点バッファを用意する必要もないので、構造化バッファのみで描画を行う方式を採用した。
D3D_PRIMITIVE_TOPOLOGY_LINELISTで描画する場合は、構造化バッファに始点、終点のセットで書き込みを行う。
D3D_PRIMITIVE_TOPOLOGY_LINESTRIPで描画する場合は、単純に構造化バッファに点情報を書き込む。ただし、他のデータをまた描きたい場合その前に描いた線と繋がってしまう。そこで、線と線の間に、アルファ0、Zオーダ0の2点(最後の線の終点と次の線の先頭と同じ座標)を追加することにした。こうすることで、D3D_PRIMITIVE_TOPOLOGY_LINESTRIPのデータもまとめて1コマンドで描画できるようになる。


線の場合、D3D_PRIMITIVE_TOPOLOGY_LINELISTで2点追加することで描画できる。


矩形


矩形の場合は、線のD3D_PRIMITIVE_TOPOLOGY_LINESTRIPに対して5点(左上、右上、右下、左下、左上)を指定する。



円の場合は、線のD3D_PRIMITIVE_TOPOLOGY_LINESTRIPに対してN点を指定する。
Nは3点以上で、cos、sinで座標を計算する。8点以上で円ぽくなる。


スプライト用シェーダ


矩形のテクスチャ貼り付けバージョンのシェーダを用意する。
データソースにリソースに登録されたテクスチャの他に、文字列描画で出力したテクスチャ、さらに今作ってる2D描画で出力したテクスチャも指定できるようにした。


スプライト描画


張り付ける位置の矩形、テクスチャを参照する矩形を指定する。



半透明の描画



不透明の描画の場合、Zオーダが手前を先に描画することで高速化を図る。
逆に透明の描画の場合は、Zオーダが奥のものから描画することで結果を正しく表現する。
またシェーダも切り替えて深度バッファ参照のみに変更して書き込みを止める。

同じシェーダ同士の描画順は制御しているが、塗りつぶし、線、スプライトの描画順は固定。
スプライト(不透明)、塗りつぶし(不透明)、線(不透明)、スプライト(透明)、塗りつぶし(透明)、線(透明)の順で描画する。



2024年8月11日日曜日

PIX GPUキャプチャ失敗

今までDirectXの描画でうまくいかない部分をPIXで確認して何度もヒントを貰って直せた。

ところが突然PIXのキャプチャに失敗するようになった。
直前に直したであろうソースを戻しても記憶が曖昧でうまくいかない。
それから2,3日キャプチャができない日が続いた。

ネットで調べると、サスペンド状態で実行してキャプチャすると成功するというのを見つけ、試してみるとたしかに成功した。
ただこれは狙ったタイミングではキャプチャできず、一番最初のみうまくいく。

その状態のまま開発を続けたが、どうしてもキャプチャしたい状態が出てきてちゃんと調査することにした。


ほとんどの動作をコメントにして、キャプチャできるかを試したら久しぶりに成功した。
この状態から徐々に処理を開放していき駄目になる部分を特定した。


最終的に原因の特定に成功。


キャプチャ失敗の原因



最終的に失敗していた箇所が中間レンダーターゲットをテクスチャにコピーする処理だった。
このコピー前後にバリアを張っていたが、外してもデバッグレイヤで怒られないので不要なことはしないようにコメントアウトしていた。
実はこれが原因でPIXのキャプチャ失敗するようになったようだ。

バリアを戻すと、PIXでキャプチャが成功する。
実際の動作には何の問題もないがPIXのために必要なのか、それともデバッグレイアのチェックが抜けていて実は必要な処理なのか?

PIXでキャプチャできないのは困るので、バリアのコメントを外して復活させることで解決した。

2024年8月4日日曜日

文字列を描画する仕組みをレンダリングエンジンに組み込む

文字列を描画する仕組みは作ったけど、レンダリングエンジンへの組み込みが中途半端になっていたので、きちんと組み込んでみた。


文字列の属性


最初はFPSなどを描画する為に毎フレーム更新する前提のものを用意していたけど、ラベルのように設定したら更新されないものにも対応したいと思っていた。

SetText(一時的なテキスト設定)


毎フレーム更新される前提で、Update内で毎回設定する。
前回のテキストはクリアされ設定しなければ描画されない。

SetLabel(ラベルテキスト設定)


毎フレームは変更されず、更新のあった場合だけ再描画する。
利用側は毎フレーム書き込んでもいいし、書き込まなくてもよい。
管理側でID別に文字列を管理し、関数が呼び出される度に値を比較し変更があった場合だけ描画する。


出力先の設定


今までレンダーターゲットにのみ書き込んでいた。
SetTextは直接レンダーターゲットに書き込み、毎フレーム必要な文字列を描画する。
SetLabelは中間レンダーターゲットをキャッシュに利用できるようにする。
専用の単一中間レンダーターゲットに対して書き込み、変更がなければそのまま維持される。(ダブルバッファで用意してないのは前回の書き込みを利用するため)
描画完了後、別途テクスチャにコピーする。実際の画面への描画はこのテクスチャを貼り付ける。


出力方法


SetTextの場合は、主にレンダーターゲットを想定しており全体がクリアされたところに描画する。
SetLabelの場合は、状況により全体クリアと部分クリアを切り替える。
 

部分クリア


最初に描画位置を設定して文字列を描画する。
この時の描画範囲を記憶しておく。
次回描画時に前回記憶してある範囲だけをクリアして、新しい文字を書き込む。
大量のラベルを貼り付けても、実際に描画するのは最初と書き換えたときのみ。書き換えた場合もその書き換えた文字列のみの描画だけで済む。
ただし制限として文字列同士が重なり合うような場合は強制的に全体を描画し直す必要がある。

全体クリア


前述の通り初回の書き込み時と、文字列が重なるような状況の場合は強制的に全体クリアを行う。文字列が重なる場合、各ラベルの描画範囲を確認して重なる対象のラベルだけ部分クリアも可能だけど、ライブラリ内では重くなるから行わない。
利用側が重ならないように使うか、重なる状況の場合は強制クリア、もしくはSetTextでの描画を選択してもらう。


ハマりポイント



ClearRenderTargetView


部分クリアはこの関数の引数に渡す複数の矩形で行っている。
動作させてみると落ちる。
設定した矩形の数が61個で呼び出していた模様。
これを試しに1つに変更したら動いた。いままで0でしか動かしたことがなかった。
10、20、30と増やしても動き、50にしたらだめだった。
予想は32個で、31、32、33と試したら33で落ちた。
制限についてネットで調べてみても出てこない。
似たようなので見つけたのはClearUnorderedAccessViewUint関数の指定できる矩形の数が127だったという記事。環境に依存するのか、DirectXの仕様として決まっているのかもわからない。とりあえず数が多い場合は32ずつ処理するように修正した。



2024年4月29日月曜日

Text Services Framework (TSF)のつづき

以前TSFについて実装した。
TSFにまつわる文字入力と、それを表示する仕組みの部分にいくつかのバグが見つかり、ここ何日かで直していた。


バグその1


開発当初から気づいてはいたんだけど、放置していたバグ。
入力文字をテクスチャに展開していき用意していた領域全部を使ってしまった場合、先頭に戻って1行分をクリアして再利用するようにしている。
このとき、表示するデータの中に1行目のテクスチャを利用する文字が含まれていた場合表示されなくなってしまう。

この不具合の修正は、次に消えるであろう数行に表示データが含まれている場合は、最新部分に移してしまうようにすることで対処してみた。

実際に実装はしてみたがそのチェック処理が若干重めになるのと、実際に不具合が起きた際に起きる表示が1フレームだけ表示されない(もしくは別の文字で表示される)だけなので、あえて元のままにした。


バグその2


漢字変換を通さない文字入力(半角アルファベット入力など)をした直後に、漢字変換をしようと、文字入力をすると入力できないというもの。
状況によって、変換候補だけが表示されたり、更に文字入力すると表示されるようになったり、ならなかったりという現象が起こった。

現象はMicrosoftIMEで起きて、Google日本語入力では起きない。
幸い現象は100%起こせて、いつでも検証出来たがなかなか原因がつかめなかった。

何回も試しているうちになんとなく原因はわかった。
通常はITextStoreACPの継承関数がコールバックとしてTextStore側から通知される形だけど、文字変換を通さない入力の場合、ロックを掛けて、カーソル位置や内部文字を変更してアンロックをして、TextStore側は変更内容を知らない状態。勝手にこっち側で内容を変更しただけなので、文字列が増えたことが伝わってない状態で文字列外のカーソル位置に入力しているように見えてるのではないかと推測した。
GetSelectionや、GetTextなどが呼ばれて内容が変わってるのはわかってるはずなんだけど、MicrosoftIMEはわからないらしい。

その線で調べてみた結果、ITextStoreACPSink::OnTextChangeを呼べばいいのではないかと言うことにたどり着いた。
試しに実装してみると正しく入力できるようになった。
他にもこちらだけで内容を変更している箇所がいくつかあったので通知を加え、ついでにカーソル位置が変わったらITextStoreACPSink::OnSelectionChangeも呼ぶようにしてみた。


バグその3


毎フレーム登録して描画するのは非効率なので、ラベルとしてID付きで登録できるようにして、更新がなければStructuredBufferの更新をしないようにした。また一部のラベルで更新があった場合でも他のラベルのバイナリを再作成不要なようにバイナリを保持するようにした。

試しに100個のラベルを登録して毎フレームの登録がなくなりCPU使用率が下がって、GPU使用率だけが増えることを確認しようとしたところプログラムが落ちてしまった。

このデータ管理に自作ライブラリのHashMapを利用していたが、今まで発覚しなかったバグを見つけた。
調べてみるとHashMapの拡張時にデータの移行がうまく行っていなかった。
キャパシティの75%以上になったタイミングでデータ拡張を行い今までのデータを新しく確保した領域に移す。
テンプレートに指定された型がstd::is_trivially_copyable_vがtrueなら要素をまとめてmemcpy、falseなら1要素毎にnewする仕組みに振り分けている。
moveで例外を飛ばさない(std::is_nothrow_move_constructible_v)という暗黙の条件もつけていた。この条件に合致しない型を指定した状態でmoveを行うと何もしないようにしていた。
今回指定した型がちょうどstd::is_nothrow_move_constructible_vでfalseを返しており、データ拡張時元データを引き継いでいない状態になっていた。

構造体にいくつかの型が含まれているが、自作ライブラリのすべてをstd::is_nothrow_move_constructible_vにしているつもりだったけど、そうでないものが含まれていた。
UTF-8用の文字列クラスがあり、バイナリクラス(vectorのunsigned char型)を継承して作っていた。バイナリクラスはstd::is_nothrow_move_constructible_vはtrueで問題ないが、これをテンプレートクラスで継承した型の場合、スーパクラス側の自身のmoveコンストラクタとmove operator=が表面に届いていないようで、別途定義する必要があった。

必要な定義を追加して、move時std::is_nothrow_move_constructible_vでない場合はassertを入れつつ、copyを呼び出すように修正。対応していない型のmoveに気づくようにするのと、最悪このままreleaseビルドした場合でも動くようにした。


バグその4


UTF-8型の文字列クラスにもう1つバグがあって、ICUの機能を組み込んで見た目の文字数を取得できるようにする際、BreakIteratorのキャッシュを用意していた。
一度書記素単位に分けた情報を作ったら、次回はそれを使い回す。
今自分で書いていてもすぐに起こるであろうバグそのままが起きていた。
登録されている文字の変化があった場合、キャッシュのクリアが必要だったがクリアしていなかったためバグその3を直したあとに文字化けするようになった。
内容が変化し得る箇所にクリア処理を追加した。


バグその5


「バグその3」を引き起こした機能の実装でバイナリをキャッシュに問題があった。
データの更新に合わせてキャッシュはクリアしている。
ただ「バグその1」で起こっていた、テクスチャが書き換わった際のフォローが抜けている。

テクスチャの書き換えが起こったら、すべてのキャッシュを削除するようにした。
本来は利用していたものだけキャッシュを削除すればスマートだが、厳密にチェックするとなると重くなる。「バグその1」で妥協したように処理済みデータに対してキャッシュを消してしまっても、表示されないのは1フレームのみなのでこちらも妥協することにした。

これも対応してみたはいいけど、テクスチャ書き換えの度に文字がフラッシュするので、キャッシュ消す必要もない。どうせフラッシュするならそのままにすることにより影響受けるとこだけに抑えられる。結局何もしないことにした。
「バグその1」はフラッシュしないところまで実装したけど、こっちの方は今のところフラッシュしない方法を思いついてない。





2024年3月16日土曜日

シェーダに符号付き8ビットデータを渡す

前回のステンシルバッファでテキストのクリッピングをやろうとしたけど、単純な矩形をステンシルバッファへ書き込む方法がまだわからないので方針を変えることにした。

ステンシルバッファをClearDepthStencilViewの最後の引数に複数渡せる矩形情報でクリッピング範囲を指定できたら良かったんだけど、クリアの値が作成時のクリア値以外で書き込むとパフォーマンスが落ちるらしいので諦めた。


テキストのクリッピング処理


テキストをレンダリングするシェーダに渡しているSRVに16ビットのオフセット情報を増やして、書き込む幅を調整できるように考えた。

今まで渡していた情報はすべてuint16_tで描画する位置のx, yと、フォントマスタのインデックスno、カラー番号cno。
このデータサイズが毎フレーム更新に影響を与えるために極力小さくするチューニングを行って現在の形になっている。データを増やすのはかなり抵抗があるが致し方ない。

int16_tでオフセットofsを追加した。
マイナスの場合は左から、プラスの場合は右からオフセット分描画しないようにする。


普通に2バイトのデータを書き込んでシェーダ側でint16_tとして解釈すればこれについてはなんの問題もなかった。
ただ、1文字内で左右どちらもクリッピングはできないという制限はあるがこれは許容した。

ここまで出来て、縦方向も欲しくなった。エディットボックス内の文字列描画で、枠以上の文字列入力が可能な場合にクリッピング処理が発生する想定で作っていたから、縦方向は仕様次第で不要な状況にできるかなと思っていたけど作ることにした。

符号付きの16ビットの範囲は32767~-32768なので1文字に使う範囲としてはもったいない。これを符号付き8ビットにすると、127~-128なのでちょうど良い感じ。8ビットシフトして、上位8ビットに水平方向のオフセット、下位8ビットに垂直方向のオフセットを渡すようにした。

HLSL側でもシフトが普通に使えるので、水平方向のオフセットは右シフトで普通に取り出し、下位8ビットは「& 0x00FF」で上位8ビットを切り捨てた。
ところがこれだと渡した値が、プラスの場合は問題ないが、マイナスの場合正しく評価されない。

試してうまく行ったのは、一度左に8ビットシフトしてから、右に8ビットシフトする方法。上位8ビットについては右シフトで算術シフトできることはわかっていたので、下位8ビットも、最上位ビットにタッチさせて右シフトすれば行けるかと思ったら行けた。

他にもいろいろやり方はあるだろうけど、条件分岐一切なしにできたのでこれが良さそう。専用の関数(8ビット→16ビット)があれば別だけど。


今回の対応はVertexBufferにしてやれば、オフセット情報は増やす必要なく、書き込む位置とUVの調整で済むし、1文字内で両端のクリッピングにも対応できるけど、どうなんだろ?
フォントのマスタが不要になる一方、1文字4点分のデータ書き込みが必要になるから微妙か。