DX11.2の目玉機能。Tiled Resouce

Window8.1から、DirectX11.2が使用可能になるようです。
11.2の目玉機能である、Tiled Resourceを見てみたいと思います。

Direct3D Tiled Resources sample (Windows 8.1)

まず初めに

この記事を書いている時点(2013/07)では、Windows8.1の正式版はリリースされておらず、SDKのドキュメントのほとんどは、準備中の旨の記述がなされています。下記の記事は、それらをもとに書いたもので、将来に変更される可能性が高いものを扱っています。

タイルリソースが使用可能か確認する

まずは、使用するデバイスでタイルリソースが使用可能かの確認をします。
確認には、ID3D11Device::CheckFeatureSupportの第一引数にD3D11_FEATURE_D3D11_OPTIONS1を渡して確認します。

D3D11_FEATURE_DATA_D3D11_OPTIONS1 featureData;
device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS1, &featureData, sizeof(featureData))

typedef enum _D3D11_TILED_RESOURCES_TIER { 
  D3D11_TILED_RESOURCES_NOT_SUPPORTED  = 0,
  D3D11_TILED_RESOURCES_TIER_1         = 1,
  D3D11_TILED_RESOURCES_TIER_2         = 2
} D3D11_TILED_RESOURCES_TIER;

返される値はNOT_SUPPORTEDとTIER_1,TIER_2があります。
Tier1とTier2に関して、明確な説明がされているドキュメントが見当たりません。
おそらくですが、Tier1のデバイスでは、投機的サンプリング(タイルがアサインされていない領域をサンプリングした際に、その旨を返す)が出来ないと思われます。Tier2のデバイスでは、投機的サンプリングが可能なのではないかと思われます。

ちなみにGTX680(Driver 326.19)でCheckFeatureSupportでDX11のOption機能を確認すると下記の通りの値が返されました。
TiledResouceは使用可能な様です。

TiledResourcesTier	D3D11_TILED_RESOURCES_TIER_1
MinMaxFiltering	0
ClearViewAlsoSupportsDepthOnlyFormats	1
MapOnDefaultBuffers	1

タイルテクスチャの作成

タイルテクスチャは、基本的にテクスチャの実体となるメモリを保持しません。
作成後にタイルをマッピングすることにより、初めて実際にアクセス可能なテクスチャとなります。この種のオブジェクトをタイルリソースと呼びます。
タイルテクスチャの作成は通常のテクスチャと同様に行いますが、D3D11_RESOURCE_MISC_TILEDをMiscFlagsに指定します。
下記の例では、1Kx1KのCubeMapテクスチャを定義しています。

D3D11_TEXTURE2D_DESC diffuseTextureDesc;
ZeroMemory(&diffuseTextureDesc, sizeof(diffuseTextureDesc));
diffuseTextureDesc.Width = 1024;
diffuseTextureDesc.Height = 1024;
diffuseTextureDesc.ArraySize = 6;
diffuseTextureDesc.Format = DXGI_FORMAT_BC1_UNORM;
diffuseTextureDesc.SampleDesc.Count = 1;
diffuseTextureDesc.Usage = D3D11_USAGE_DEFAULT;
diffuseTextureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
diffuseTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE | D3D11_RESOURCE_MISC_TILED;
device->CreateTexture2D(&diffuseTextureDesc, nullptr, &m_diffuseTexture);

タイル情報の取得

タイルリソースを作成したら、そのリソースが利用するタイルの情報を、ID3D11Device2::GetResourceTiling メソッドを用いて取得します。

void ID3D11Device2::GetResourceTiling(
  [in]       ID3D11Resource *pTiledResource,
  [out]      UINT *pNumTilesForEntireResource,
  [out]      D3D11_PACKED_MIP_DESC *pPackedMipDesc,
  [out]      D3D11_TILE_SHAPE *pStandardTileShapeForNonPackedMips,
  [in, out]  UINT *pNumSubresourceTilings,
  [in]       UINT FirstSubresourceTilingToGet,
  [out]      D3D11_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips
);

typedef struct D3D11_PACKED_MIP_DESC {
  UINT8 NumStandardMips;
  UINT8 NumPackedMips;
  UINT  NumTilesForPackedMips;
  UINT  StartTileIndexInOverallResource;
} D3D11_PACKED_MIP_DESC;

typedef struct D3D11_TILE_SHAPE {
  UINT WidthInTexels;
  UINT HeightInTexels;
  UINT DepthInTexels;
} D3D11_TILE_SHAPE;

typedef struct D3D11_SUBRESOURCE_TILING {
  UINT   WidthInTiles;
  UINT16 HeightInTiles;
  UINT16 DepthInTiles;
  UINT   StartTileIndexInOverallResource;
} D3D11_SUBRESOURCE_TILING;

取得できる情報は多いですが、分かりやすいものから見ていきます。

pNumTilesForEntireResource

このリソースで使用するタイルの総数です。

pStandardTileShapeForNonPackedMips

通常のタイル(パックされていないタイル。後述)の1タイルあたりに格納されるテクセルのサイズです。
構造体で、Width,Height,Depthを取得します。

pNumSubresourceTilings,
FirstSubresourceTilingToGet,
pSubresourceTilingsForNonPackedMips

これら3つの引数で、SubResouce(各サーフェース)ごとに、どの位置のタイルを利用するかを取得します。
D3D11_SUBRESOURCE_TILING構造体には、各SubResouceが使用するタイルの開始インデックスである、StartTileIndexInOverallResourceが格納されています。
加えて、Width,Height,Depthに使用するタイルの枚数が格納されています。
タイルの並び順に関しては言及されていませんが、おそらく、width-heigt-depth順だと思われます。

pPackedMipDesc

パックされているMipMapに関する情報を取得します。
MipMapのサイズが小さくなるにつれて、MipMap1枚あたりに1タイルをアサインすると、メモリ使用効率が著しく悪くなります。これを避けるため、複数のMipMapで1枚のタイルを共有するように配置している様です。これをパックと呼びます。
D3D11_PACKED_MIP_DESC構造体には、PackされたMipMapの枚数と、パックされたMipMapが使用するタイルの枚数と、パックされたMipMapが使用するタイルの開始インデックスが取得できます。
つまり、パックされたMipMapが使用するタイルは、タイルインデックスの後端に、まとめて配置されるようです。

PackedMipについて現時点で不明な点

Microsoftのサンプルコードでは、PackedMipの取り扱いに不明な点があります。まず、PackedMipのデータ転送に、通常のテクスチャと同様にUpdateSubResouceを用いています。
もう一点は、D3D11_PACKED_MIP_DESC構造体の、NumPackedMipsの値の取り扱いです。
サンプルコード内で、この値を、「PackされたMipMapのレベル数」として扱っている部分があります。
現状では、SDKのドキュメントとサンプルの実装に齟齬が見られ、注意が必要と思われます。

(訂正:NumPackedMipsの値は、リソース全体における数ではなく、ArraySliceごとの値であったようです。)
また、PackされているSubResouceに対応するD3D11_SUBRESOURCE_TILING構造体の、
StartTileIndexInOverallResourceメンバにはD3D11_PACKED_TILEが格納されているようです。
これもSDKのドキュメントに明記されている部分が見当たりません。

タイルの作成

次は、タイルテクスチャではなく、単なるタイルリソースのバッファの作成方法です。
作成の方法は、通常のバッファオブジェクトと同様に行いますが、MiscFlagsにタイルバッファであることを示す、D3D11_RESOURCE_MISC_TILEDのフラグを指定します。
これも、タイルリソースに該当し、メモリの実体をもたないバッファになります。

D3D11_BUFFER_DESC tempBufferDesc;
ZeroMemory(&tempBufferDesc, sizeof(tempBufferDesc));
tempBufferDesc.ByteWidth = SampleSettings::TileSizeInBytes;
tempBufferDesc.MiscFlags = D3D11_RESOURCE_MISC_TILED;
tempBufferDesc.Usage = D3D11_USAGE_DEFAULT;
ID3D11Buffer tempBuffer;
device->CreateBuffer(&tempBufferDesc, nullptr, &tempBuffer);

タイルプールの作成

次は、タイルの実体を格納する、タイルプールを作成する方法です。
こちらも、通常のバッファオブジェクトと同様に行います。行いますが、MiscFlagsにタイルプールであることを示す、D3D11_RESOURCE_MISC_TILE_POOLを指定します。
こちらは、バッファとして、メモリの実体を保持します。

D3D11_BUFFER_DESC tilePoolDesc;
ZeroMemory(&tilePoolDesc, sizeof(tilePoolDesc));
tilePoolDesc.ByteWidth = SampleSettings::TileSizeInBytes * SampleSettings::TileResidency::PoolSizeInTiles; // 37MB
tilePoolDesc.Usage = D3D11_USAGE_DEFAULT;
tilePoolDesc.MiscFlags = D3D11_RESOURCE_MISC_TILE_POOL;
device->CreateBuffer(&tilePoolDesc, nullptr, &m_tilePool);

タイルをマッピングする

先述のタイルリソースは、メモリの実体をもたないので、そのままでは使用することができません。
使用するためには、実際にタイルプールのタイルを、リソースに対してマッピングする必要があります。
マッピングには、DeviceContext2::UpdateTileMappingsを用います。
先頭の4つの引数で、タイルリソースにマップする位置を指定します。位置の指定は配列を指定できるので、1度の呼び出しで、複数の領域を指定することができます。
続く5個の引数で、マップされるするタイルプールの領域を指定します。こちらも配列で指定できるので、複数の領域を指定することができます。
また、単純なマッピングのほかに、単一のタイルを複数回マップしたり、NULLタイルをアサインすることもできます。

HRESULT ID3D11DeviceContext2::UpdateTileMappings(
  [in]  ID3D11Resource *pTiledResource,
  [in]  UINT NumTiledResourceRegions,
  [in]  const D3D11_TILED_RESOURCE_COORDINATE *pTiledResourceRegionStartCoordinates,
  [in]  const D3D11_TILE_REGION_SIZE *pTiledResourceRegionSizes,
  [in]  ID3D11Buffer *pTilePool,
  UINT NumRanges,
  const UINT *pRangeFlags,
  const UINT *pTilePoolStartOffsets,
  const UINT *pRangeTileCounts,
  [in]  UINT Flags
);

typedef struct D3D11_TILED_RESOURCE_COORDINATE {
  UINT X;
  UINT Y;
  UINT Z;
  UINT Subresource;
} D3D11_TILED_RESOURCE_COORDINATE;

typedef struct D3D11_TILE_REGION_SIZE {
  UINT   NumTiles;
  BOOL   bUseBox;
  UINT   Width;
  UINT16 Height;
  UINT16 Depth;
} D3D11_TILE_REGION_SIZE;

typedef enum D3D11_TILE_RANGE_FLAG { 
  D3D11_TILE_RANGE_NULL               = 0x1,
  D3D11_TILE_RANGE_SKIP               = 0x2,
  D3D11_TILE_RANGE_REUSE_SINGLE_TILE  = 0x4
} D3D11_TILE_RANGE_FLAG;

タイルの内容を更新する(1)

タイルプールに格納されるタイルの内容を更新するには、ID3D11DeviceContext2::UpdateTilesメソッドを使用します。
ここで、注意が必要です。タイルの実体はあくまで、タイルプールが保持しているのですが、タイルの内容の更新には、タイルリソースを指定します。
つまり、更新する範囲のタイルを、UpdateTileMappingsでタイルリソースにマッピングした後に、UpdateTilesで内容を更新するようになっています。
また、最後の引数にD3D11_TILE_MAPPING_NO_OVERWRITEを指定することが出来ます。
これはおそらく、Mapメソッドの際に指定できるNO_OVERWRITEと同じ意味を持っていると思われます。
つまり、現在GPUが使用中のリソースであっても、書き換える範囲はGPUが実際にアクセスしないことをアプリケーション側で保証し、リソース使用中のアクセスを許可させるものだと思われます。

void UpdateTiles(
  [in]  ID3D11Resource *pDestTiledResource,
  [in]  const D3D11_TILED_RESOURCE_COORDINATE *pDestTileRegionStartCoordinate,
  [in]  const D3D11_TILE_REGION_SIZE *pDestTileRegionSize,
  [in]  const void *pSourceTileData,
  [in]  UINT Flags
);

typedef struct D3D11_TILED_RESOURCE_COORDINATE {
  UINT X;
  UINT Y;
  UINT Z;
  UINT Subresource;
} D3D11_TILED_RESOURCE_COORDINATE;

typedef struct D3D11_TILE_REGION_SIZE {
  UINT   NumTiles;
  BOOL   bUseBox;
  UINT   Width;
  UINT16 Height;
  UINT16 Depth;
} D3D11_TILE_REGION_SIZE;

typedef enum D3D11_TILE_MAPPING_FLAG { 
  D3D11_TILE_MAPPING_NO_OVERWRITE  = 0x1
} D3D11_TILE_MAPPING_FLAG;

タイルの内容を更新する(2)

もう一つタイルリソースを更新する方法として、CopyTilesがあります。
これは、転送元のバッファが、ID3D11Bufferになります。
GPUで生成された情報をタイルとして使用する場合は、有用かもしれません。

void CopyTiles(
  [in]  ID3D11Resource *pTiledResource,
  [in]  const D3D11_TILED_RESOURCE_COORDINATE *pTileRegionStartCoord,
  [in]  const D3D11_TILE_REGION_SIZE *pTileRegionSize,
  [in]  ID3D11Buffer *pBuffer,
  [in]  UINT64 BufferStartOffsetInBytes,
  [in]  UINT Flags
);

タイルリソースの同期

タイルリソースに対するアクセスの同期をとるためのバリア関数が用意されています。
メソッド名はID3D11DeviceContext2::TiledResourceBarrierです。
このメソッドは、現在、サンプルコードの実装と、SDKのドキュメントで引数の表記が異なっています。

[From SDK docs.]
void TiledResourceBarrier(
  [in]  void *pTiledResourceAccessBeforeBarrier,
  [in]  BOOL bAccessBeforeBarrierIsView,
  [in]  void *pTiledResourceAccessAfterBarrier,
  [in]  BOOL bAccessAfterBarrierIsView
);

[From SDK]
virtual void STDMETHODCALLTYPE TiledResourceBarrier( 
            /* [annotation] */ 
            _In_opt_  ID3D11DeviceChild *pTiledResourceOrViewAccessBeforeBarrier,
            /* [annotation] */ 
            _In_opt_  ID3D11DeviceChild *pTiledResourceOrViewAccessAfterBarrier) = 0;

このメソッドは、どちらの場合も、タイルリソースがアクセス可能かどうかを保証するためのものです。
このメソッドが存在するということは、UpdateTileMappingsでマッピングを書き換えても、実際のマッピングの変更は非同期的に行われるということだと思われます。

ここまで

今回は、とりあえずSDK側で使用するメソッドを見てみました。
次回は、実際の使用シナリオに沿った内容をサンプルソースから読んでみたいと思います。
加えてタイルテクスチャを使用するHLSLコード側も見てみたいと思います。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中