テクスチャの作成およびテクスチャ マネージャ

ハードウェア(GPU)テクスチャの作成と管理の統合メカニズムは、API で公開されます。テクスチャは、内部または外部のどちらでロードされるかに関係なく再利用するためにキャッシュされ、すべてのメモリ管理は内部で処理されます。

テクスチャは、ディスク上のイメージから作成することも、CPU メモリ内のピクセルのブロックを使用して作成することもできます。テクスチャのタイプとサポートされるフォーマットの数は、旧式のどのインタフェースよりも大幅に多くなります。また、OpenGL のみをサポートする旧式のインタフェースとは異なり、インタフェースは描画 API に依存しません。

テクスチャは MTexture クラスで表され、管理は MTextureManager クラスで処理されます。

テクスチャを取得するための 3 つの基本的なインタフェースがあります。

  1. ディスク上のファイルからテクスチャを取得する。この方法では、現在使用可能なイメージ コード タイプがサポートされます。任意のカスタム プラグイン コーデックが IMF プラグイン インタフェースによって追加されている場合は、それらもサポートされます。ロードされたイメージの説明は、MTextureDescription クラスで表されます。MTextureDescription 自体はリソースではなく、リソースの説明です。
  2. CPU メモリ内にあるピクセルのブロックからテクスチャを取得する。この場合、ピクセルを説明するために MTextureDescription の入力が必要です。プラグイン作成者が、データの適切な説明を指定します。データの Null ブロックを参照するテクスチャを割り当てることができます。
  3. Maya のシェーディング ノード プラグまたはノードの出力に基づいてテクスチャを取得する。

    MTextureManager::acquireTexture() および MTexture::update() メソッド(入力引数としてファイル テクスチャ ノードを受け取る)は、ファイル テクスチャ ノードからファイル名を内部で決定するので、明示的にファイル テクスチャ名を指定する場合は、これらを使用する方が良いでしょう。acquireTexture() メソッドの使用方法を示すサンプル コードについては、Developer Kit サンプル hwApiTextureTest を参照してください。update() メソッドの使用方法を示すサンプル コードについては、サンプル viewOverriedTrackTexture を参照してください。

テクスチャの独自性は、テクスチャの名前で指定されます。ディスク上のイメージの場合は、ファイル パスを使用して一意の名前が指定されます。それ以外の場合は、プラグイン コードで一意となる内容を決定します。取得の際、同じ名前を持つテクスチャが既に存在する場合は、その名前が返されます。

空の文字列が一意の名前として渡された場合、テクスチャは「名前なし」と見なされます。 名前の付いたテクスチャと名前のないテクスチャとの主な違いは、名前のないテクスチャは内部テクスチャ キャッシュシステムによってキャッシュされないために、それぞれのメソッド呼び出しのための新しいテクスチャ インスタンスを作成して取得することです。

MTexture とマネージャが CPU 側のピクセル マップを処理することが目的ではないため、追加のイメージ コーデックをこのインタフェース経由で直接追加することはできません。代わりに、MImage などの既存のインタフェースを、これらのインタフェースと組み合わせて使用することができます。実際、CPU メモリ内のピクセルのブロックを作成するインタフェースでは、GPU テクスチャを取得するための CPU データを受け入れるインタフェースを使用することができます。

テクスチャの取得の関係を、次の図に示します。

図 28

最も直接的なシナリオは、ディスクから GPU メモリにイメージを直接ロードすることです。このシナリオでは、CPU 側のメモリは長い間保持されません。他のシナリオの場合は、CPU メモリにピクセルのブロックを割り当ててから、そのデータを GPU に転送するという 2 段階の処理が必要です。ここでは、3 つのオプションが示されています。つまり、シェーダ ノード上のプラグインからの読み込み、MImage クラスによる読み込み、およびカスタム コードによる読み込みです。

テクスチャの GPU ハンドルにアクセスすることはできますが、読み取り専用です。データそのものが削除されたり、サイズが変更されない限り、この GPU ハンドルを通じてテクスチャで直接更新を実行することができます。DirectX 11 では、これは実際にテクスチャ ハンドルであり、OpenGL では、テクスチャ ID (整数)です。テクスチャを更新するためのフォーマライズされたインタフェースに関する詳細は、「2.4.1 カラー テクスチャを更新する」および「2.4.2 深度テクスチャ」を参照してください。

テクスチャの一般的な使用事例として、シェーダへのバインドがあります。たとえば、MTexture インスタンスを MShaderInstance 上のテクスチャ パラメータにバインドすることができます。テクスチャがシェーダ インスタンスにバインドされた場合、この情報はレンダー項目の分類フェーズ中に使用されます。たとえば、半透明のピクセル(0~1)があることを示すようにテクスチャを設定すると、レンダー項目を透明として分類することができます。他にも、テクスチャのさまざまなアルファ チャネル プロパティをチェックするための照会および設定方法があります。

カラー テクスチャを更新する

テクスチャ インスタンスを継続的に再取得してその内容を置換することは、時間がかかりすぎるため、お勧めしません。 これは特に名前のないテクスチャに当てはまります。繰り返される取得によって、新しいテクスチャの割り当てが生成されるためです。テクスチャを再利用することが予めわかっている場合は、テクスチャ マネージャからテクスチャを一度取得して、次に MTexture::update() インタフェースを使用して更新することをお勧めします。

この計算処理による著しいパフォーマンスの低下を避けるために、mip-maps の再生成は必要な場合にのみ実行する必要があります。既定では、mip-maps がテクスチャにすでに含まれている場合は、mip-maps の再計算は自動的に行われます。

この更新メカニズムは現在、配列以外の 2D テクスチャの更新に限定されます。

未処理データ更新インタフェース

最も素早く使用できるインタフェースは、未処理データへの参照を受け取ることです。 このデータは、CPU メモリ内に新しく作成されるか、既存の MTexture または MRenderTarget から抽出されたデータから作成されます。

行ピッチとテクスチャ行の長さは必ずしも同じ長さではない場合があるので、新しく作成されたかどうかにかかわらず、適切な行ピッチを使用することが重要です。 行パディングは主に DirectX API を使用して作成されたテクスチャに作用します。 たとえば、データが既存のテクスチャから抽出された場合は、MTexture::rawData() メソッドによって抽出された行ピッチ情報を使用する必要があります。 新しい未処理データでは、行ピッチ情報は bytes-per-pixel メソッド(MTexture::bytesPerPixel())とテクスチャの幅に基づいて、さらに計算されます。

データが MRenderTarget::rawData() によってターゲットから抽出された場合、ターゲットはテクスチャでない場合があるので、返された行ピッチの値を使用することが最適な方法です。 ターゲット(MRenderTarget)は、MRenderTargetManager::acquireRenderTargetFromScreen() メソッドを使用したオン スクリーン バッファ(たとえば、DirectX11 の SwapChain)からのデータで生成することができます。 このケースにおいても、適切な行ピッチの値を使用する必要があります。

新しいデータを書き込むときの基本的なコストは、CPU データを GPU テクスチャにアップロードすることです。これは、GPU からデータを読み戻すことのできない「ブロッキング」操作ではありません。たとえば、OpenGL 開発者にとっては、アップロードは glTexSubImage*() を呼び出すことと同等のものとして考えることができます。

メモリは GPU メモリから CPU にマッピングし直される必要があるため、既存のテクスチャから読み戻すことはブロックされます。

MImage 更新インタフェース

既存の MImage のデータを使用して既存の MTexture を更新することもできます。テクスチャのフォーマットが MImageのものと一致することを確認するのは、呼び出し側の責任になります。 これは現在は 4 チャネル、チャネル当たり 8 バイトの RGBA カラーです。 bytesPerPixel() メソッドは、一致のチェックに使用することができます。

MImage インタフェースを使用するには、GPU メモリを CPU へマッピングし、更新後に GPU へ再マッピングして戻すことが必要になります。

GPU ハンドルを介した "Backdoor" の更新

GPU リソース ハンドル(MTexture::resourceHandle())が有効な場合、これらのハンドルを使用して更新を実行することもできます。 たとえば、GPU から GPU リソース メモリの転送は、API では公開されませんが、未処理リソース ハンドルを使用して実行できます。

このハンドルを介した操作によってフォーマットまたはデータ サイズの変更が発生しないようにしてください。リソースはレンダラが所有しており、これらのアトリビュートが変更されることは想定していないからです。

テクスチャの更新例

次の図は、さまざまな更新の可能性におけるインタラクションを示しています。

図 29: 図の左にある MImage は、MTexture に新しいコンテキストをアップロードするために使用することができます。このデータの一部は、CPU にマッピングして戻され、新しいデータがコピーされます。 MTexture の上に示したデータの流れは、可能な未処理データの読み戻し、可能な未処理のデータを使用したテクスチャの更新を示しています。 図の右上では、未処理データはオフスクリーン レンダー ターゲットから読み込むことも可能であることを示しています。右下では、テクスチャを更新するために、GPU ハンドルにアクセスできることを示しています。

次に示すサンプル コードでは、テクスチャから未処理のピクセル データを読み込み、ピクセルを反転させ、データをテクスチャに書き戻す例を示しています。テクスチャはここでは 24 ビットの固定点 RGBA フォーマットであると仮定されます。

// Get the texture description from the texture 
MHWRender::MTextureDescription desc;
texture->textureDescription(desc);
unsigned int bpp = texture->bytesPerPixel();

// This code happens to only work with fixed-bit 8888 RGBA
//
if (bpp == 4 && 
	(desc.fFormat == MHWRender::kR8G8B8A8_UNORM ||
	 desc.fFormat == MHWRender::kB8G8R8A8))
{
	int rowPitch = 0;
	int slicePitch = 0;
	bool generateMipMaps = true;

	// Extract out the raw data from the texture. Also gets the row and slice pitch
	// The assumption is that this is 2D texture so slicePitch would be 1.
	// A GPU->CPU transfer of data will occur.
	unsigned char* pixelData = (unsigned char *)texture->rawData(rowPitch, slicePitch);
	unsigned char* val = NULL;
	if (pixelData && rowPitch > 0 && slicePitch > 0)
	{
		// Do some example operation: invert the pixel values (255-value)
		for (unsigned int i=0; i<desc.fHeight; i++)
		{
			val = pixelData + (i*rowPitch);
			for (unsigned int j=0; j<desc.fWidth*4; j++)
			{
				*val = 255 - *val;
				val++;
			}
		}
		// Update the texture. A CPU to GPU transfer of data will occur
		texture->update(pixelData, generateMipMaps, rowPitch);
	}
	delete [] pixelData;
}

サンプル プラグイン hwAPITextureTest は、ディスクから読み込んだテクスチャを更新するために上記の反転コードを使用し、結果を画面に転送し、そしてオン スクリーン ターゲットから未処理データを抽出して、テクスチャに戻します。最後に、テクスチャはディスクに書き込まれます。

サンプル プラグイン viewImageBlitOverride は、レンダー オーバーライド(MRenderOverride)の各リフレッシュのためのカラー テクスチャの更新を示しています。

深度テクスチャ

深度テクスチャの作成と使用を許可するために、いくつかのインタフェースが存在します。 深度テクスチャが使用された場合のフォーマットや方法を示すものがないため、これらのインタフェースを使用することは必須ではありません。

Maya には、カメラに対して相対的な深度値を、メモリ内またはディスク上のいずれかに格納するための内部フォーマットがあります。これは、Maya IFF ファイル フォーマットの一部として公開され、レンダラとして書き込むか、MImage などの API クラスを使用してディスクから読み込むことができます。

次の図は、単一の .iff ファイルからの色と深度を示しています。

図 30

便宜上、「深度テクスチャ」を作成するために、テクスチャ マネージャは深度イメージの読み込みをサポートしています。 このようなテクスチャの内部フォーマットは、赤チャネル(R32F)に格納された単一チャネルの 32 ビット浮動小数点です。 MTextureManager::acquireDepthTexture() メソッドは深度データを、MImage、または未処理ピクセルのブロックのいずれかから読み込むことを許可します。

便宜のため、データは作成時に[0...1]の範囲に正規化されることがあります。 正規化を実行するには、正規化ディスクリプタのインスタンスが必要です(MDepthNormalizationDescription)。 既定のコンストラクタは、既定のパース カメラに関連付けられた値にメンバー データを設定します。この正規化プロセスで前提となっていることは、Maya のソフトウェア レンダラが生成する深度バッファに使用されている方式に、あるいはこれと同等に MImage への格納用に使用されている深度フォーマットに、その入力データが従っていることです。

正規化には時間がかかるので、まずテクスチャを正規化なしで作成し、レンダリング時に適切なシェーダ コードを追加して値を正規化することができます。

MImage によりデータが提供されている場合は、MDepthNormalizationDescription とともに使用するための値を抽出するには、メソッド MImage::getDepthMapRange() を使用できます。

プラグイン viewImageBlitOverride は、正規化された深度テクスチャを塗り潰すための未処理データを生成するコード例と、ディスク上の .iff ファイルから既存の深度バッファを読み込むためのコード例を表示します。

深度テクスチャを作成するための手順の例を示すコードの一部を、プラグインから下記に抜き出しました。

MImage から深度データのみをロードする場合、「ダミー」である MImage は、出力「ターゲット」サイズを指定して作成される必要があります。深度データは、MImage から抽出され、テクスチャ マネージャからの深度テクスチャを取得するために使用されます。 次の例では、CPU 側の正規化が行われます。

// Pre-create a MImage of the output target size.
//
MImage image;
image.create(targetWidth, targetHeight, 4, MImage::kByte);


// Load depth image from disk to create the depth texture. The file on disk is assumed to
// have depth data embedded.
//
MString depthImageFileName(“testFile.iff”);
if (MStatus::kSuccess == image.readDepthMap( depthImageFileName ))
{
	image.getDepthMapSize( targetWidth, targetHeight );

	// Set up normalization structure.
	// Take into the account the clipping values from the image.
	// 
	MHWRender::MDepthNormalizationDescription normalizationDesc;
	float minValue, maxValue;
	image.getDepthMapRange( minValue, maxValue );
	normalizationDesc.fNearClipDistance = minValue;
	normalizationDesc.fFarClipDistance = maxValue;

	// Acquire a texture using the depth pixels and normalization structure
	mDepthTexture.texture = textureManager->acquireDepthTexture("", 
		image, false, &normalizationDesc );
}

未処理データをロードするためには、浮動小数点データのブロックを割り当て、適切な値を入力します。 このケースでは、各タイルが異なるカメラ深度を持つ位置にチェッカー パターンを作成します。

コードには、正規化された値を作成するかどうかのオプションが表示されています(変数useCameraDistanceValues を使用)。コードにはまた、未処理データを取得し、インタフェースを使用して MImage に再パッケージ化するオプションも表示されています(変数 createDepthWithMImage を使用)。

// Load depth using programmatically created data
//
float *textureData = new float[targetWidth*targetHeight];
if (textureData)
{
	// Use 'createDepthWithMImage' to switch between using the MImage
	// and raw data interfaces.
	//
	// Use 'useCameraDistanceValues' to switch between using -1/distance-to-camera
	// values versus using normalized depth coordinates [0...1].
	// The flag is set to create normalized values by default in order to
	// match the requirements of the shader used to render the texture.
	//
	bool createDepthWithMImage = false;
	bool useCameraDistanceValues = false;

	// Create some dummy 'checkered' depth data. 
	//
	float depthValue = useCameraDistanceValues ? -1.0f / 100.0f : 1.0f;
	float depthValue2 = useCameraDistanceValues ? -1.0f / 500.0f : 0.98f;
	for (unsigned int y = 0; y < targetHeight; y++)
	{
		float* pPixel = textureData + (y * targetWidth);
		for (unsigned int x = 0; x < targetWidth; x++)
		{
			bool checker = (((x >> 5) & 1) ^ ((y >> 5) & 1)) != 0;
			*pPixel++ = checker ? depthValue  : depthValue2;
		}
	}

	// Create a default normalization structure
	MHWRender::MDepthNormalizationDescription normalizationDesc;

	// Take the raw data and repackage it into an MImage and use that interface
	// This is presented as an example only and is not necessary since the
	// raw data interface is available.
	if (createDepthWithMImage)
	{
		image.setDepthMap( textureData, targetWidth, targetHeight );
		mDepthTexture.texture = textureManager->acquireDepthTexture("", 
			image, false, useCameraDistanceValues ? &normalizationDesc : NULL );
	}

	// Take the raw data and repackage it into an MImage and use that interface
	//
	else
	{
		mDepthTexture.texture = textureManager->acquireDepthTexture("", 
			textureData,  targetWidth, targetHeight, false,
			useCameraDistanceValues ? &normalizationDesc : NULL );
	}

	// Data not required anymore so can delete it.
	delete [] textureData;
}

図 31: 深度テクスチャを作成するための可能なデータ パスを示しています。 データは、特定のレンダラ、または手作業で作成することができます。MTextureManager へのインタフェースは、未処理データを使用して、あるいは MImage 引数を使用して入力することを許可します。

深度テクスチャを転送する

深度テクスチャの内容を出力ターゲットに転送する場合の最も簡単な方法は、シェーダ(MShaderInstance)を使用することです。

mayaBlitColorDepth というサンプル シェーダが、エフェクト ファイル フォーマットで提供されています。このシェーダは、シェーダ マネージャを使用して取得できます。シェーダには現在、正規化された値を含む R32 入力テクスチャから深度出力への転送が表示されています。

カラー テクスチャと同様に、座標ターゲット システムは上から下に表示されます。サンプル シェーダは、必要に応じて V で垂直反転を実行します。 プラグイン開発者は様々な規則を選択して各自のテクスチャの作成を選択でき、一貫性のために V 反転を実行できます。

このシェーダは、viewImageBlitOverride プラグインの例で使用されています。