2009/05/17
Direct3D11 影の設定 ShadowMap
久しぶりに影を扱ったのでメモです。
Direct3D 10 以降、ShaderModel 4.0 以降はハードウエアシャドウマップが当たり前に
使えるようになっているので比較的簡単になりました。
Direct3D8 ~ Direct3D9、ShaderModel 1.0 ~ 3.0 までは NVIDIA GeForce
独自の拡張機能でした。ハードウエアシャドウマップは
・DepthBuffer を直接参照するため他にバッファを用意することがないこと
・通常のサンプリングと違い、比較した結果の Bool 値 1.0 or 0.0 を返すこと
・比較結果をバイリニア補間するため、追加コストなしにハードウエアでフィルタリング可能なこと
等の利点があります。
ShaderModel 2.0 以降なら同等の機能をシェーダーで記述することができます。
旧 RADEON 向けシェーダーでは必須となりますが、追加バッファが必要でバイリニア
用サンプリングのコストがゼロにならないので完全に同じとはいえませんでした。
Direct3D 10 では標準機能の一部になったので、ShaderModel 4.0 に対応した
RADEON HD 2900XT 以降はハードウエアシャドウマップが使えます。
下位の ShaderModel でも利用可能で、RADEON HD 2900XT では
ShaderModel 3.0 を使っても GeForce 向けシェーダーがそのまま動作しました。
最初試したときは感動。
ShaderModel 4.0 以降は値を比較しながらサンプリングできる汎用機能として用意されて
いるので、GeForce 用、RADEON 用とシェーダーやプログラムを分ける必要がなくなりました。
バッファの作成
使用している CD3D11_TEXTURE2D_DESC や CD3D11_DEPTH_STENCIL_VIEW_DESC
等は DirectX SDK March 2009 以降使えるようになった新機能です。
各種構造体を初期化してくれるヘルパーで、D3D11.h で定義されています。
ヘッダを見て発見しました。
サンプラーの作成
比較結果のフィルタリングなので、D3D11_FILTER_COMPARISON_~ と
COMPARISON 付きのフィルタを指定します。
CD3D11_SAMPLER_DESC に渡している D3D11_DEFAULT は、デフォルト値で構造体を
初期化することを意味しています。
クリアと RenderTarget の設定
ShadowBuffer にレンダリングする場合の前処理です。
OMSetRenderTargets() では DepthBuffer のみ指定します。
RenderTarget が NULL の場合書き込みが行われません。
リソースの設定
作成した ShadowBuffer を用いて影描画する場合の前設定です。
シェーダーとのバインドを簡略化するために、ShadowBuffer 用のレジスタ番号を
決めうちにしています。
シェーダー
ShadowBuffer を生成する場合は特別なことが何もありません。
カラーを書かないので不要に見えますが、テクスチャの Alpah 抜きに対応する場合は
テクスチャも読み込まなければならなくなります。
今回は特別なことを何もしてないので大丈夫ですが、影の合成を効率化しようと
凝ったことをし出すとテクスチャの扱いが結構負担になってきます。
パス毎にフィルタリングや Mip 指定、uv の計算等を厳密にあわせておかないと、
ちょっとしたレンダーステートの差がテクスチャの隙間を生み出してしまうからです。
例えば DepthBuffer の事前生成など。
描画用シェーダー
VertexShader では描画用の Matrix と影用の Matrix、2つの座標系へ変換します。
実際の PixelShader は非常に長いので光源演算を大幅に削りました。
影のサンプリングは SampleCmpLevelZero() を使っています。
Depth を読み込み、指定した z 値と比較した結果をフィルタリングして返すので、
対応する光源に対して影の中かどうかわかります。
影用 uv への変換は効率化するなら Matrix に埋め込んでおくことができます。
埋め込むと影を落とすときと完全に同じ Matrix にならないことが難点。
基本的な描画手順
影、不透明、半透明 といった順番になります。
DepthBuffer の事前生成を行う場合や、複数の光源の影を落とす場合は描画パスが増加していきます。
alpha 抜きを使う場合は半透明と同じように、さらに別グループに分類した方が良いかもしれません。
関連エントリ
・DirectX SDK March 2009
・Direct3D 10 DXGI_FORMAT の機能対応一覧
・D3D10/DX10 D3D10_FILTER の新機能
・D3D10/DX10 RADEON HD2900XT
Direct3D 10 以降、ShaderModel 4.0 以降はハードウエアシャドウマップが当たり前に
使えるようになっているので比較的簡単になりました。
Direct3D8 ~ Direct3D9、ShaderModel 1.0 ~ 3.0 までは NVIDIA GeForce
独自の拡張機能でした。ハードウエアシャドウマップは
・DepthBuffer を直接参照するため他にバッファを用意することがないこと
・通常のサンプリングと違い、比較した結果の Bool 値 1.0 or 0.0 を返すこと
・比較結果をバイリニア補間するため、追加コストなしにハードウエアでフィルタリング可能なこと
等の利点があります。
ShaderModel 2.0 以降なら同等の機能をシェーダーで記述することができます。
旧 RADEON 向けシェーダーでは必須となりますが、追加バッファが必要でバイリニア
用サンプリングのコストがゼロにならないので完全に同じとはいえませんでした。
Direct3D 10 では標準機能の一部になったので、ShaderModel 4.0 に対応した
RADEON HD 2900XT 以降はハードウエアシャドウマップが使えます。
下位の ShaderModel でも利用可能で、RADEON HD 2900XT では
ShaderModel 3.0 を使っても GeForce 向けシェーダーがそのまま動作しました。
最初試したときは感動。
ShaderModel 4.0 以降は値を比較しながらサンプリングできる汎用機能として用意されて
いるので、GeForce 用、RADEON 用とシェーダーやプログラムを分ける必要がなくなりました。
バッファの作成
// depth buffer 用リソースの作成 CD3D11_TEXTURE2D_DESC ddesc( DXGI_FORMAT_R24G8_TYPELESS, width, height, 1, // array 1, // mip D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_DEPTH_STENCIL, D3D11_USAGE_DEFAULT, 0, // cpu 1, // sample count 0, // quality 0 // misc ); iDevice->CreateTexture2D( &ddesc, NULL, &iShadowDepth ); // Depth Buffer 用 View の作成 // レンダリング時はこちら CD3D11_DEPTH_STENCIL_VIEW_DESC dsdesc( D3D11_DSV_DIMENSION_TEXTURE2D, DXGI_FORMAT_D24_UNORM_S8_UINT, 0, // mipSlice 0, // firstArraySlice 1, // arraySize 0 // flags ); iDevice->CreateDepthStencilView( iShadowDepth, &dsdesc, &iShadowDepthView ); // Shader Resource 用 View の作成 // サンプリング時はこちら CD3D11_SHADER_RESOURCE_VIEW_DESC sdesc( D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 0, // mostDetailedMip 1, // mipLevels 0, // firstArraySlice 1 // arraySize ); iDevice->CreateShaderResourceView( iShadowDepth, &sdesc, &iShadowView );
使用している CD3D11_TEXTURE2D_DESC や CD3D11_DEPTH_STENCIL_VIEW_DESC
等は DirectX SDK March 2009 以降使えるようになった新機能です。
各種構造体を初期化してくれるヘルパーで、D3D11.h で定義されています。
ヘッダを見て発見しました。
サンプラーの作成
CD3D11_SAMPLER_DESC desc( D3D11_DEFAULT ); desc.AddressU= D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressV= D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressW= D3D11_TEXTURE_ADDRESS_CLAMP; desc.Filter= D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; desc.ComparisonFunc= D3D11_COMPARISON_LESS; iDevice->CreateSamplerState( &desc, &iShadowSampler );
比較結果のフィルタリングなので、D3D11_FILTER_COMPARISON_~ と
COMPARISON 付きのフィルタを指定します。
CD3D11_SAMPLER_DESC に渡している D3D11_DEFAULT は、デフォルト値で構造体を
初期化することを意味しています。
クリアと RenderTarget の設定
void __fastcall ShadowBuffer::Clear( ID3D11DeviceContext* iContext ) { iContext->ClearDepthStencilView( iShadowDepthView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, DVF(1), 0 ); } void __fastcall ShadowBuffer::SetRenderTarget( ID3D11DeviceContext* iContext ) { ID3D11RenderTargetView* irtv= NULL; iContext->OMSetRenderTargets( 1, &irtv, iShadowDepthView ); CD3D11_VIEWPORT view( DVF(0), DVF(0), ShadowWidth, ShadowHeight ); iContext->RSSetViewports( 1, &view ); }
ShadowBuffer にレンダリングする場合の前処理です。
OMSetRenderTargets() では DepthBuffer のみ指定します。
RenderTarget が NULL の場合書き込みが行われません。
リソースの設定
void __fastcall ShadowBuffer::SetResources( ID3D11DeviceContext* iContext ) { iContext->PSSetShaderResources( SHADOW_REGISTER_ID, 1, &iShadowView ); iContext->PSSetSamplers( SHADOW_REGISTER_ID, 1, &iShadowSampler ); }
作成した ShadowBuffer を用いて影描画する場合の前設定です。
シェーダーとのバインドを簡略化するために、ShadowBuffer 用のレジスタ番号を
決めうちにしています。
シェーダー
struct VS_INPUT { float3 vPos : POSITION; float3 vNormal : NORMAL; float2 vTex0 : TEXCOORD; #if CF_WEIGHT float4 vWeight : WEIGHT; uint4 vIndex : INDEX; #endif }; struct VS_OUTPUT { float4 vPos : SV_Position; float2 vTex0 : TEXCOORD; }; VS_OUTPUT vmain( VS_INPUT idata ) { VS_OUTPUT odata; float4x4 world; #if CF_WEIGHT world= World[idata.vIndex.x] * idata.vWeight.x; world+= World[idata.vIndex.y] * idata.vWeight.y; world+= World[idata.vIndex.z] * idata.vWeight.z; world+= World[idata.vIndex.w] * idata.vWeight.w; #else world= World[0]; #endif float4 wpos= mul( float4( idata.vPos.xyz, 1 ), world ); odata.vPos= mul( wpos, PV ); odata.vTex0= idata.vTex0; return odata; } typedef VS_OUTPUT PS_INPUT; Texture2D tex0 : register( t0 ); SamplerState sample0 : register( s0 ); float pmain( PS_INPUT idata ) : SV_Target { #if CF_ALPHA1BIT float alpha= tex0.Sample( sample0, idata.vTex0.xy ).w; if( alpha < 0.8f ){ clip( -1 ); } #endif return 0; }
ShadowBuffer を生成する場合は特別なことが何もありません。
カラーを書かないので不要に見えますが、テクスチャの Alpah 抜きに対応する場合は
テクスチャも読み込まなければならなくなります。
今回は特別なことを何もしてないので大丈夫ですが、影の合成を効率化しようと
凝ったことをし出すとテクスチャの扱いが結構負担になってきます。
パス毎にフィルタリングや Mip 指定、uv の計算等を厳密にあわせておかないと、
ちょっとしたレンダーステートの差がテクスチャの隙間を生み出してしまうからです。
例えば DepthBuffer の事前生成など。
描画用シェーダー
struct VS_INPUT { float3 vPos : POSITION; float3 vNormal : NORMAL; float2 vTex0 : TEXCOORD; #if CF_WEIGHT float4 vWeight : WEIGHT; uint4 vIndex : INDEX; #endif }; struct VS_OUTPUT { float4 vPos : SV_Position; float3 vNormal : NORMAL; float2 vTex0 : TEXCOORD; float4 vShadow : SHADOW; }; VS_OUTPUT vmain( VS_INPUT idata ) { VS_OUTPUT odata; float4x4 world; #if CF_WEIGHT world= World[idata.vIndex.x] * idata.vWeight.x; world+= World[idata.vIndex.y] * idata.vWeight.y; world+= World[idata.vIndex.z] * idata.vWeight.z; world+= World[idata.vIndex.w] * idata.vWeight.w; #else world= World[0]; #endif float4 wpos= mul( float4( idata.vPos.xyz, 1 ), world ); odata.vPos= mul( wpos, PV ); odata.vNormal.xyz= mul( idata.vNormal, (float3x3)world ); odata.vTex0= idata.vTex0; odata.vShadow= mul( wpos, ShadowMatrix ); return odata; } typedef VS_OUTPUT PS_INPUT; Texture2D tex0 : register( t0 ); SamplerState sample0 : register( s0 ); Texture2D shadowTex0 : register( t8 ); SamplerComparisonState shadowSample0 : register( s8 ); float4 pmain( PS_INPUT idata ) : SV_Target { float3 diffuse= Ambient.xyz; float3 specular= float3( 0, 0, 0 ); float4 color= float4( 0, 0, 0, 0 ); float3 sh_uv= idata.vShadow.xyz; sh_uv.xyz/= idata.vShadow.w; sh_uv.x= sh_uv.x * 0.5f + 0.5f; sh_uv.y= -sh_uv.y * 0.5f + 0.5f; sh_uv.z-= 0.000004f; // 適当 float shdepth= shadowTex0.SampleCmpLevelZero( shadowSample0, sh_uv.xy, sh_uv.z ); // float shdepth= shadow_pcf( sh_uv ); lightMask= saturate( shdepth + 0.0f ); float3 normal= normalize( idata.vNormal.xyz ); diffuse.xyz+= saturate( dot( normal, _LightDir ) ) * Diffuse.xyz * lightMask; specular.xyz+= pow( saturate( dot( normal, normalize(idata.vEyeVec + _LightDir ) )), Specular.w ) * Specular.xyz * lightMask; float4 texcol= tex0.Sample( sample0, idata.vTex0.xy ); color.xyz+= texcol.xyz * diffuse.xyz + specular.xyz; color.w= texcol.w * Ambient.w; #if CF_ALPHA1BIT if( color.w < 0.8f ){ clip( -1 ); } #endif return color; }
VertexShader では描画用の Matrix と影用の Matrix、2つの座標系へ変換します。
実際の PixelShader は非常に長いので光源演算を大幅に削りました。
影のサンプリングは SampleCmpLevelZero() を使っています。
Depth を読み込み、指定した z 値と比較した結果をフィルタリングして返すので、
対応する光源に対して影の中かどうかわかります。
影用 uv への変換は効率化するなら Matrix に埋め込んでおくことができます。
埋め込むと影を落とすときと完全に同じ Matrix にならないことが難点。
基本的な描画手順
// Shadow Buffer の作成 ShadowBuffer->Clear( iContext ); ShadowBuffer->SetRenderTarget( iContext ); iContext->OMSetDepthStencilState( iDSS_ZEnable, 0 ); // shadow レンダリング用 matrix の設定など // shadow buffer 生成のシェーダーで描画 // 影をサンプリングしながらレンダリング // 本来の RenderTarget の設定 // レンダリング用 matrix の設定 ShadowBuffer->SetResources(); // 描画用シェーダーで描画
影、不透明、半透明 といった順番になります。
DepthBuffer の事前生成を行う場合や、複数の光源の影を落とす場合は描画パスが増加していきます。
alpha 抜きを使う場合は半透明と同じように、さらに別グループに分類した方が良いかもしれません。
関連エントリ
・DirectX SDK March 2009
・Direct3D 10 DXGI_FORMAT の機能対応一覧
・D3D10/DX10 D3D10_FILTER の新機能
・D3D10/DX10 RADEON HD2900XT
2009/05/13
Direct3D10/11 DXGI1.1 とリモート GPU (アダプタ)
DXGI1.1 のアダプタ列挙を試します。
●EeePC901-X で実行した場合 (Windows7 RC x86)
945 内蔵 GPU が列挙できています。
実際に Direct3D10 / 11 デバイスを作るとエラーなので、ドライバがまだ D3D10 Level9
に対応していないことがわかります。
●デスクトップ PC で実行した場合 (Windows7 RC x64)
2枚差したビデオカードが両方とも列挙されています。
それぞれ 10.1 でデバイスの作成ができます。
●リモートデスクトップ経由で実行した場合
EeePC 901 からデスクトップ PC に接続しています。
予想通り両方のマシン、すべてのアダプタが見えていることがわかります。
EeePC の 945 では DXGI_ADAPTER_DESC1 の Flags に DXGI_ADAPTER_FLAG_REMOTE bit が立っており、このデバイスを区別することが可能です。
プログラム自体はホスト側のデスクトップ PC で走っているので、プログラムから見れば
クライアントである EeePC の方がリモートです。
もし Remote のアダプタが Command Remoting に対応していれば
ネットワークを経由していてもクライアント側の PC でリアルタイムに
レンダリングすることが可能となります。
ちなみに DXGI1.0 で列挙するとホスト側の最初のアダプタ "RADEON HD4850" しか
見えませんでした。
● Feature
各アダプタの詳細を調べると下記の通り。
いわば Direct3D11 における caps です。
以前 DirectX SDK Nov2008 時点で調べた結果はこちら。
ほとんどのオプションは 0 のままですが、よく見ると REFERENCE で
DoublePrecisionFloatShaderOps が 1 になっていました。
REFERENCE を使えばシェーダーの double 演算命令もテストできるようになったということです。
同記事 でも触れていますが ComputeShader 4.0/4.1 については November の時点ですでに対応していますね。
関連エントリ
・Direct3D DXGI とマルチ GPU (アダプタ)
・Windows7 リモートデスクトップと Direct3D
・Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
●EeePC901-X で実行した場合 (Windows7 RC x86)
// EeePC 901-X [Mobile Intel(R) 945 Express Chipset Family (Microsoft Corporation - WDDM 1.0)] vmem:0MB sysmem:64MB shmem:192MB flag=0
945 内蔵 GPU が列挙できています。
実際に Direct3D10 / 11 デバイスを作るとエラーなので、ドライバがまだ D3D10 Level9
に対応していないことがわかります。
●デスクトップ PC で実行した場合 (Windows7 RC x64)
// Desktop PC [ATI Radeon HD 4800 Series ] vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100) [ATI Radeon HD 4670] vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100)
2枚差したビデオカードが両方とも列挙されています。
それぞれ 10.1 でデバイスの作成ができます。
●リモートデスクトップ経由で実行した場合
[Mobile Intel(R) 945 Express Chipset Family (Microsoft Corporation - WDDM 1.0)] vmem:0MB sysmem:64MB shmem:192MB flag=1 REMOTE [ATI Radeon HD 4800 Series ] vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100) [ATI Radeon HD 4670] vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100)
EeePC 901 からデスクトップ PC に接続しています。
予想通り両方のマシン、すべてのアダプタが見えていることがわかります。
EeePC の 945 では DXGI_ADAPTER_DESC1 の Flags に DXGI_ADAPTER_FLAG_REMOTE bit が立っており、このデバイスを区別することが可能です。
プログラム自体はホスト側のデスクトップ PC で走っているので、プログラムから見れば
クライアントである EeePC の方がリモートです。
もし Remote のアダプタが Command Remoting に対応していれば
ネットワークを経由していてもクライアント側の PC でリアルタイムに
レンダリングすることが可能となります。
ちなみに DXGI1.0 で列挙するとホスト側の最初のアダプタ "RADEON HD4850" しか
見えませんでした。
● Feature
各アダプタの詳細を調べると下記の通り。
いわば Direct3D11 における caps です。
[DEFAULT] (Direct3D11) *HARDWARE = 10.1 (a100) feature threading DriverConcurrentCreates=0 feature threading DriverCommandLists=0 feature d3d10_x ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=0 feature doubles DoublePrecisionFloatShaderOps=0 *REFERENCE = 11.0 (b000) feature threading DriverConcurrentCreates=0 feature threading DriverCommandLists=0 feature d3d10_x ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=1 feature doubles DoublePrecisionFloatShaderOps=1 *WARP = 10.1 (a100) feature threading DriverConcurrentCreates=0 feature threading DriverCommandLists=0 feature d3d10_x ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=0 feature doubles DoublePrecisionFloatShaderOps=0 [ATI Radeon HD 4800 Series ] (V:4098 D:37954 S:84021250 R:0) luid:000097b2 00000000 vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100) feature threading DriverConcurrentCreates=0 feature threading DriverCommandLists=0 feature d3d10_x ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=0 feature doubles DoublePrecisionFloatShaderOps=0 [ATI Radeon HD 4670] (V:4098 D:38032 S:625086466 R:0) luid:0000b18e 00000000 vmem:504MB sysmem:0MB shmem:2811MB flag=0 (Direct3D11) *HARDWARE = 10.1 (a100) feature threading DriverConcurrentCreates=0 feature threading DriverCommandLists=0 feature d3d10_x ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=0 feature doubles DoublePrecisionFloatShaderOps=0
以前 DirectX SDK Nov2008 時点で調べた結果はこちら。
ほとんどのオプションは 0 のままですが、よく見ると REFERENCE で
DoublePrecisionFloatShaderOps が 1 になっていました。
REFERENCE を使えばシェーダーの double 演算命令もテストできるようになったということです。
同記事 でも触れていますが ComputeShader 4.0/4.1 については November の時点ですでに対応していますね。
関連エントリ
・Direct3D DXGI とマルチ GPU (アダプタ)
・Windows7 リモートデスクトップと Direct3D
・Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
2009/05/11
Direct3D DXGI とマルチ GPU (アダプタ)
PC を入れ替えて複数のビデオカードを同時に使えるようになりました。
対応マザーとそれなりの電源が必要です。
いろいろ試しています。
マルチ GPU といっても SLI や CrossFire のことではなく、
それぞれ独立した GPU として使います。
例えば GeForce と RADEON の 2枚差しで、それぞれにモニタをつないで
マルチモニタのように使用することができます。
・GeForce GTX260 → モニタ1
・RADEON HD4670 → モニタ2
GeForce は モニタ1 の描画を行い、RADEON は モニタ2 へ出力を行っています。
Windows から使っている分には 1枚のビデオカードに複数モニタをつないだ状態と
何も変わりません。
広いデスクトップとして使用可能で双方にまたがったウィンドウも配置できます。
ここまでは理解できますが Direct3D を使った場合はどうなるでしょうか。
D3D10CreateDevice / D3D11CreateDevice 時に PC につながっている任意のアダプタ
(GPU)を与えることが可能です。
どちらの GPU でも同じように使えるし、どちらのモニタにも描画することができました。
行き来もできるし中間に配置しても動いています。
このあたりをコントロールするのが DXGI で、非常に柔軟な使い方が可能となっています。
・IDXGIAdapter = GPU
・IDXGIOutput = モニタ
現在利用可能なアダプタとモニタは
IDXGIFactory::EnumAdapters()
IDXGIAdapter::EnumOutputs()
で列挙可能です。モニタに接続されていないアダプタも描画に使うことができます。
DXGI1.1 ではこれにさらに Command Remoting が加わります。
リモートデスクトップでアクセスしている場合に、ホストとクライアントどちらの
アダプタでレンダリングするか選択できるわけです。
●サンプルで確認
DirectX SDK 付属の Direct3D 11 のサンプルプログラムを起動するとウィンドウに
アダプタ名 (GPU名) が表示されています。
モニタ間 (アダプタ間) を行き来するとアダプタ名が切り替わるので違いがよくわかります。
プログラム的にはわざわざアダプタを切り替える必要は無いのですが、
DXUT では敢えてこのような仕様になっているようです。(理由は後述)
●プログラムで確認
実際にプログラムを書いて任意のアダプタで動かしてみました。
・RADEON HD4850 → モニタ1
・RADEON HD4670 → モニタ2
IDXGIFactory::EnumAdapters() でアダプタを列挙して、任意のアダプタを使って
デバイスを作成します。
アダプタは IDXGIFactory::EnumAdapters() で列挙したハードウエアアダプタ、
もしくは IDXGIFactory::CreateSoftwareAdapter() で作成したソフトウエアアダプタです。
すでに TYPE を特定できるので、DriverType には D3D_DRIVER_TYPE_UNKNOWN を
与えなければなりません。(最初ここではまりました)
結果
HD4850 ではモニタ1 の方が高速、HD4670 ではモニタ2 の方が高速です。
アダプタから直接出力した方が速く、予想通りの結果といえます。
同時にたとえ直結されていなくても、モニタ2 の描画も HD4850 が行った方が速いこともわかります。
DirectX SDK 付属サンプルの DXUT がウィンドウの位置を監視して、モニタに応じて
デバイスを作り直しているのは少しでも高速に動作するためだと考えられます。
昔はビデオカードを何度も何度も差し直して開発していました。
GeForce と RADEON の挙動を同時に確認できるなんて、大変便利になったものです。
上のテストの最中、途中で GeForce GTX260(192sp) から HD4850 に差し直したのは
本日非常に暑かったからです。
関連エントリ
・Windows7 リモートデスクトップと Direct3D
対応マザーとそれなりの電源が必要です。
いろいろ試しています。
マルチ GPU といっても SLI や CrossFire のことではなく、
それぞれ独立した GPU として使います。
例えば GeForce と RADEON の 2枚差しで、それぞれにモニタをつないで
マルチモニタのように使用することができます。
・GeForce GTX260 → モニタ1
・RADEON HD4670 → モニタ2
GeForce は モニタ1 の描画を行い、RADEON は モニタ2 へ出力を行っています。
Windows から使っている分には 1枚のビデオカードに複数モニタをつないだ状態と
何も変わりません。
広いデスクトップとして使用可能で双方にまたがったウィンドウも配置できます。
ここまでは理解できますが Direct3D を使った場合はどうなるでしょうか。
D3D10CreateDevice / D3D11CreateDevice 時に PC につながっている任意のアダプタ
(GPU)を与えることが可能です。
どちらの GPU でも同じように使えるし、どちらのモニタにも描画することができました。
行き来もできるし中間に配置しても動いています。
このあたりをコントロールするのが DXGI で、非常に柔軟な使い方が可能となっています。
・IDXGIAdapter = GPU
・IDXGIOutput = モニタ
現在利用可能なアダプタとモニタは
IDXGIFactory::EnumAdapters()
IDXGIAdapter::EnumOutputs()
で列挙可能です。モニタに接続されていないアダプタも描画に使うことができます。
DXGI1.1 ではこれにさらに Command Remoting が加わります。
リモートデスクトップでアクセスしている場合に、ホストとクライアントどちらの
アダプタでレンダリングするか選択できるわけです。
●サンプルで確認
DirectX SDK 付属の Direct3D 11 のサンプルプログラムを起動するとウィンドウに
アダプタ名 (GPU名) が表示されています。
モニタ間 (アダプタ間) を行き来するとアダプタ名が切り替わるので違いがよくわかります。
プログラム的にはわざわざアダプタを切り替える必要は無いのですが、
DXUT では敢えてこのような仕様になっているようです。(理由は後述)
●プログラムで確認
実際にプログラムを書いて任意のアダプタで動かしてみました。
・RADEON HD4850 → モニタ1
・RADEON HD4670 → モニタ2
IDXGIFactory::EnumAdapters() でアダプタを列挙して、任意のアダプタを使って
デバイスを作成します。
// 列挙 IDXGIAdapter1* iAdapter= NULL; IDXGIFactory1* iFactory= NULL; CreateDXGIFactory1( __uuidof(IDXGIFactory1), reinterpret_cast( &iFactory ) ); for( unsigned int index= 0 ;; index++ ){ HRESULT ret= iFactory->EnumAdapters1( index, &iAdapter ); if( ret == DXGI_ERROR_NOT_FOUND ){ break; } // ~ アダプタの選択 // iAdapter->Release(); } iFactory->Release(); // 作成 HRESULT hr= D3D11CreateDeviceAndSwapChain( iAdapter, iAdapter ? D3D_DRIVER_TYPE_UNKNOWN : DriverType, NULL, // software D3D11_CREATE_DEVICE_DEBUG,// flags NULL, // featurelevels 0, // featurelevels D3D11_SDK_VERSION, &SwapChainDesc, &iSwapChain, &iDevice, &FeatureLevel, &iContext );
アダプタは IDXGIFactory::EnumAdapters() で列挙したハードウエアアダプタ、
もしくは IDXGIFactory::CreateSoftwareAdapter() で作成したソフトウエアアダプタです。
すでに TYPE を特定できるので、DriverType には D3D_DRIVER_TYPE_UNKNOWN を
与えなければなりません。(最初ここではまりました)
結果
RADEON HD4850 → モニタ1 : 257fps RADEON HD4850 → モニタ2 : 205fps RADEON HD4670 → モニタ1 : 140fps RADEON HD4670 → モニタ2 : 164fps
HD4850 ではモニタ1 の方が高速、HD4670 ではモニタ2 の方が高速です。
アダプタから直接出力した方が速く、予想通りの結果といえます。
同時にたとえ直結されていなくても、モニタ2 の描画も HD4850 が行った方が速いこともわかります。
DirectX SDK 付属サンプルの DXUT がウィンドウの位置を監視して、モニタに応じて
デバイスを作り直しているのは少しでも高速に動作するためだと考えられます。
昔はビデオカードを何度も何度も差し直して開発していました。
GeForce と RADEON の挙動を同時に確認できるなんて、大変便利になったものです。
上のテストの最中、途中で GeForce GTX260(192sp) から HD4850 に差し直したのは
本日非常に暑かったからです。
関連エントリ
・Windows7 リモートデスクトップと Direct3D