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点分のデータ書き込みが必要になるから微妙か。

2024年3月7日木曜日

Stencil Buffer

今回はステンシルバッファについて


フォーマット



深度バッファのみのときは、DXGI_FORMAT_D32_FLOATを使っていた。
ステンシルバッファを使う場合は、DXGI_FORMAT_D24_UNORM_S8_UINTを使う。
Dの部分が深度バッファの精度で、ステンシルバッファに8ビット使ってしまうため、24に落ちる。
精度を落としたくなければDXGI_FORMAT_D32_FLOAT_S8X24_UINTというものもあるらしいけど、24ビット丸々無駄にするらしいし、4バイトの範囲に収まらないのでなんだか遅そう。


D3D12_RESOURCE_DESC1.Format


深度バッファのみのときは、DXGI_FORMAT_R32_TYPELESS
ステンシルバッファを使う場合は、DXGI_FORMAT_R24G8_TYPELESS


D3D12_CLEAR_VALUE.Format


深度バッファのみのときは、DXGI_FORMAT_D32_FLOAT
ステンシルバッファを使う場合は、DXGI_FORMAT_D24_UNORM_S8_UINT


CreatePlacedResource2.CastFormat


深度バッファのみのときは、
CastFormat[0]=DXGI_FORMAT_D32_FLOAT
CastFormat[1]=DXGI_FORMAT_R32_FLOAT
ステンシルバッファを使う場合は指定なし。

これがいまいち分からず、指定するとエラーになりCreateできない。
深度バッファの方は、指定する場合は両方指定しないとランタイムエラーになる。
指定なしにしたら動くので、下手に指定する必要がないかも。


D3D12_DEPTH_STENCIL_VIEW_DESC.Format


深度バッファのみのときは、DXGI_FORMAT_D32_FLOAT
ステンシルバッファを使う場合は、DXGI_FORMAT_D24_UNORM_S8_UINT

D3D12_GRAPHICS_PIPELINE_STATE_DESC.DSVFormat


深度バッファのみのときは、DXGI_FORMAT_D32_FLOAT
ステンシルバッファを使う場合は、DXGI_FORMAT_D24_UNORM_S8_UINT


D3D12_SHADER_RESOURCE_VIEW_DESC.Format


深度バッファのみのときは、DXGI_FORMAT_R32_FLOAT
ステンシルバッファを使う場合は、DXGI_FORMAT_R24_UNORM_X8_TYPELESS

ここを見るとPlaneSlice0にDXGI_FORMAT_R24_UNORM_X8_TYPELESS、1にDXGI_FORMAT_X24_TYPELESS_G8_UINTを指定する様に書かれている。
SRVでPlaneSliceを複数指定する方法がわからないけど、2つ指定するとそれぞれ深度バッファと、ステンシルバッファが参照できるのだろう。

今のところステンシルありのバッファをテクスチャ利用する予定はないので、必要が出てきたら調べることにする。


PSO


ステンシルバッファを使う場合は、DepthStencilState.StencilEnableをTRUEにする。

ステンシルバッファ書き込み


DepthStencilState.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK ;
DepthStencilState.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK ;
DepthStencilState.FrontFace = {
    D3D12_STENCIL_OP_KEEP,
    D3D12_STENCIL_OP_KEEP,
    D3D12_STENCIL_OP_REPLACE,
    D3D12_COMPARISON_FUNC_ALWAYS
} ;
DepthStencilState.BackFace = DepthStencilState.FrontFace ;

上記は描画した場所をOMSetStencilRefで設定した値で塗りつぶす場合の設定(D3D12_STENCIL_OP_REPLACE)
D3D12_STENCIL_OPは3つ設定できて、最初がステンシルテストに失敗した場合、次がステンシルテストは成功したけど、Zバッファのテストが失敗した場合、最後が両方のテストが成功した場合。
ステンシルバッファの書き込みだけで、描画は行わない場合BlendState.RenderTarget[i].RenderTargetWriteMaskも0にするといいらしいけど、そもそもレンダーターゲットを指定しなければいいんじゃないのか?
バッファ書き込みはやってないので分からず。


ステンシルテスト


DepthStencilState.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK ;
DepthStencilState.StencilWriteMask = 0 ;
DepthStencilState.FrontFace = {
    D3D12_STENCIL_OP_KEEP,
    D3D12_STENCIL_OP_KEEP,
    D3D12_STENCIL_OP_KEEP,
    D3D12_COMPARISON_FUNC_EQUAL
} ;
DepthStencilState.BackFace = DepthStencilState.FrontFace ;

上記は、OMSetStencilRefで設定した値と同じ箇所だけ描画する場合の設定。


ClearDepthStencilViewでD3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCILを指定して、深度バッファとステンシルバッファを初期化したあと、ClearDepthStencilViewの最後の引数に矩形を指定して、ステンシルバッファだけを10で初期化。OMSetStencilRefにも10を設定して描画してみると、こんな感じになった。

Hが欠けてる

指定通りの動作になっているけど、デバッグレイヤには下記の警告が出続ける。

D3D12 WARNING: ID3D12CommandList::ClearDepthStencilView: The clear values do not match those passed to resource creation. The clear operation is typically slower as a result; but will still clear to the desired value. [ EXECUTION WARNING #821: CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE]

ステンシルバッファの書き換えにClearDepthStencilViewを利用したのがだめみたい。
矩形で指定範囲を書き換えたい時、わざわざシェーダを通さずClearDepthStencilViewで手軽にクリップ領域を指定できるかと思ったけど、この使い方はだめみたい。