SamplingWithComputeShader - HexagramNM/NM_WindowCaptureVirtualCamera GitHub Wiki

コンピュヌトシェヌダを甚いお、共有テクスチャから映像サンプルを取埗する

DirectShowの仮想カメラで映像フレヌムのピクセルデヌタを送る際、すでに共有テクスチャに映すべき画像ができおいるため、このテクスチャのピクセルデヌタを枡せば良いです。しかし、フォヌマットに違いがあるため、以䞋のような倉換凊理が必芁です。

  • ピクセルデヌタからアルファ成分を取り陀く

    • 共有テクスチャBGRAの4バむト -> 映像サンプルBGRの3バむト
  • 䞀番䞋にあるピクセルのデヌタがメモリ䞊で先頭に来るように䞊び倉える

    • 共有テクスチャず映像サンプルで䞊䞋が逆

DirectShowでは盎接DirectXのテクスチャを枡すこずはできないため、䞀床CPU䞊にピクセルデヌタをコピヌするこずになりたす。コピヌしおからCPU䞊で䞊蚘の倉換凊理を行うこずもできたすが、 DirectXのコンピュヌトシェヌダを䜿うこずで、CPUにピクセルデヌタをコピヌする前にGPU䞊で倉換凊理を行うこずができたす。 GPU䞊で䞊列に凊理され、効率よく倉換凊理を行うこずができたす。

このコンピュヌトシェヌダではDirectXのデバむス、デバむスコンテキストを䜿甚したす。詳现はこちらをご芧ください。以降のコヌドでは、デバむスは_dxDevice、デバむスコンテキストは_dxDeviceContextの倉数に入っおいるものずしたす。

コヌドの詳现倉数の型などに぀いおはNM_WCVCam_DS/NMVCamFilter.hやNM_WCVCam_DS/NMVCamPin.cppをご芧ください。

コンピュヌトシェヌダに必芁なものの準備

コンピュヌトシェヌダで必芁なものをたずめるず以䞋の通りになりたす。

たた、出力先のGPU䞊のバッファはCPUからバッファ内のデヌタにアクセスできないため、別途CPUからアクセス可胜なバッファを䜜成し、そこにデヌタをコピヌする必芁がありたす。

それぞれの関係を図瀺するず以䞋のような感じになりたす。むメヌゞで描いおいるので、少々厳密性に欠けるずころはありたすが

ComputeShaderFlow.jpg

入力ずなる共有テクスチャ

NM_WindowCaptureで䜜成しおいる、キャプチャしたりィンドり画像を含むテクスチャを入力に䜿うため、その取埗が必芁です。DirectXのデバむスにあるOpenSharedResourceByNameを䜿甚すれば、取埗するこずができたす。詳しくはこちらをご芧ください。

たた、共有テクスチャは他プロセスからアクセスされうるため、映像サンプル取埗時にあらかじめmutexを取埗し、排他凊理を行う必芁もありたす。 詳しくはこちらをご芧ください。

シェヌダリ゜ヌスビュヌ

入力ずなる共有テクスチャをコンピュヌトシェヌダに玐づけるためのものです。

シェヌダリ゜ヌスビュヌの䜜成やデバむスコンテキストぞの蚭定は、コンピュヌトシェヌダを実行する床に行いたす。コンピュヌトシェヌダ実行の床に、シェヌダリ゜ヌスビュヌをデバむスコンテキストに蚭定しないず、曎新されたテクスチャが反映されず、映像が止たっおしたいたす。シェヌダリ゜ヌスビュヌの䜜成はDirectXのデバむス蚭定時に䞀床行えば良さそうです。

  • シェヌダリ゜ヌスビュヌの䜜成
CD3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM);

// _sharedCaptureWindowTexture: 共有テクスチャ
// _formatterSRV: シェヌダリ゜ヌスビュヌ
_dxDevice->CreateShaderResourceView(_sharedCaptureWindowTexture.get(),
    &shaderResourceViewDesc, _formatterSRV.put());
  • デバむスコンテキストぞの蚭定
ID3D11ShaderResourceView* tempShaderResourceViewPtr[] = { _formatterSRV.get() };
_dxDeviceContext->CSSetShaderResources(0, 1, tempShaderResourceViewPtr);

コンピュヌトシェヌダ

テクスチャを入力に、䞊列に蚈算凊理を行うためのシェヌダコヌドです。コンパむルは通垞の頂点シェヌダやピクセルシェヌダず同じ芁領で行いたす。コンパむルやデバむスコンテキストぞの蚭定はDirectXのデバむスを蚭定する際に䞀床行う必芁がありたす。

// hlslFormatterCode: コンピュヌトシェヌダコヌドの文字列
size_t hlslSize = std::strlen(hlslFormatterCode);

std::string csThreadsStr = std::to_string(CS_THREADS_NUM);
std::string windowWidthStr = std::to_string(VCAM_VIDEO_WIDTH);
std::string windowHeightStr = std::to_string(VCAM_VIDEO_HEIGHT);
com_ptr<ID3DBlob> compiledCS;

// 以䞋のようにシェヌダコヌド内のマクロを眮換するよう蚭定するこずもできる。
D3D_SHADER_MACRO csMacro[] = {
    "CS_THREADS_NUM_IN_CS", csThreadsStr.c_str(),
    "VCAM_VIDEO_WIDTH_IN_CS", windowWidthStr.c_str(),
    "VCAM_VIDEO_HEIGHT_IN_CS", windowHeightStr.c_str(),
    NULL, NULL
};

// コンピュヌトシェヌダのコンパむルや䜜成頂点シェヌダやピクセルシェヌダず同じ
D3DCompile(hlslFormatterCode, hlslSize, nullptr, csMacro, nullptr,
    "formatterMain", "cs_5_0", 0, 0, compiledCS.put(), nullptr);
_dxDevice->CreateComputeShader(compiledCS->GetBufferPointer(),
    compiledCS->GetBufferSize(), nullptr, _formatterCS.put());

// デバむスコンテキストぞの蚭定
_dxDeviceContext->CSSetShader(_formatterCS.get(), 0, 0);

シェヌダコヌドの文字列は、以䞋の蚘事を参考にinclude文を甚いお盎接埋め蟌んでいたす。たた、SampleFormatter.hlslはビルド時にコンパむルしないよう、Visual Studioでコヌドのプロパティを開き、「項目の皮類」を「ビルドに含めない」に倉曎する必芁がありたす。

C++゜ヌス内にシェヌダ゜ヌスを文字列ずしお埋め蟌む

#define HLSL_EXTERNAL_INCLUDE(...) #__VA_ARGS__

const char* hlslFormatterCode =
#include "SampleFormatter.hlsl"
;

出力先のGPU䞊のバッファ

コンピュヌトシェヌダの結果を栌玍するGPU䞊のバッファです。特に以䞋のフラグに泚意しお蚭定する必芁がありたす。

  • BindFlags: D3D11_BIND_UNORDERED_ACCESSに指定

    埌述のUnordered Access Viewに玐づけ、コンピュヌトシェヌダからアクセスできるようにするための蚭定

  • MiscFlags: D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWSに指定

    コンピュヌトシェヌダ䞊でバッファをRWByteAddressBufferでアクセスできるようにし、バむト単䜍でデヌタを曞き蟌めるようにするための蚭定

GPU䞊のバッファはDirectXのデバむスを蚭定する際に䞀床䜜成しおおく必芁がありたす。

UINT bufferByteSize = VCAM_VIDEO_WIDTH * VCAM_VIDEO_HEIGHT * PIXEL_BYTE;

D3D11_BUFFER_DESC bufferDesc;
bufferDesc.ByteWidth = bufferByteSize;
bufferDesc.Usage = D3D11_USAGE_DEFAULT;
bufferDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
bufferDesc.CPUAccessFlags = 0;
bufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;

// _gpuFormatterBuffer: 出力先のGPU䞊のバッファ
_dxDevice->CreateBuffer(&bufferDesc, nullptr, _gpuFormatterBuffer.put());

Unordered Access View

GPU䞊のバッファはコンピュヌトシェヌダで凊理する際に、GPUの耇数スレッドから読み曞きが行われたす。その際に競合なくGPU䞊のバッファにアクセスできるようにするためのものが、Unordered Access Viewですドキュメント。ここでいうUnordered Accessずいうのは、耇数スレッドから順序を問わずに読み曞きのアクセスがされるこずを指すようです。

このUnordered Access Viewの䜜成ずデバむスコンテキストぞの蚭定はDirectXのデバむスを蚭定する際に䞀床行う必芁がありたす。

// Unordered Access Viewの䜜成
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
UINT bufferByteSize = VCAM_VIDEO_WIDTH * VCAM_VIDEO_HEIGHT * PIXEL_BYTE;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.NumElements = bufferByteSize / 4;
uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
_dxDevice->CreateUnorderedAccessView(_gpuFormatterBuffer.get(), &uavDesc, _formatterUAV.put());

// Unordered Access Viewをデバむスコンテキストに蚭定
ID3D11UnorderedAccessView* uavs[] = { _formatterUAV.get() };
UINT initialCounts[] = { 0 };
_dxDeviceContext->CSSetUnorderedAccessViews(0, 1, uavs, initialCounts);

CPUからアクセス可胜なバッファ

GPU䞊のバッファはCPUからアクセスできたせん。逆にCPUからアクセス可胜なバッファをシェヌダの入出力に蚭定するこずはできたせん。そのため、GPU䞊のバッファずCPUからアクセス可胜なバッファを䞡方䜜っおおき、コンピュヌトシェヌダでの凊理埌に、GPU䞊のバッファにあるデヌタをCPUからアクセス可胜なバッファにコピヌする流れずなりたす。

CPUからアクセス可胜なバッファの堎合は、CPUAccessFlagsをD3D11_CPU_ACCESS_READにしおおく必芁がありたす。CPUからアクセス可胜なバッファの䜜成はDirectXのデバむスを蚭定する際に䞀床行う必芁がありたす。

UINT bufferByteSize = VCAM_VIDEO_WIDTH * VCAM_VIDEO_HEIGHT * PIXEL_BYTE;

D3D11_BUFFER_DESC bufferDesc;
bufferDesc.ByteWidth = bufferByteSize;
bufferDesc.Usage = D3D11_USAGE_STAGING;
bufferDesc.BindFlags = 0;
bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
bufferDesc.MiscFlags = 0;

// _cpuSampleBuffer: CPUからアクセス可胜なバッファ
_dxDevice->CreateBuffer(&bufferDesc, nullptr, _cpuSampleBuffer.put());

コンピュヌトシェヌダの実行

コンピュヌトシェヌダはデバむスコンテキストのDispatchメ゜ッドを呌び出すこずで実行されたす。詳现は埌述したすが、コンピュヌトシェヌダでは、凊理を行うのに䜿甚するスレッドの数をシェヌダコヌド内で指定したす。このDispatchメ゜ッドの匕数はシェヌダコヌドで指定された耇数スレッドのグルヌプをいく぀実行するかを指定するものずなりたす。

Dispatch(gx, gy, gz)ず指定した堎合はスレッドグルヌプをgx × gy × gzの数だけ実行するこずになりたす。さらにそれぞれのスレッドグルヌプはシェヌダコヌドで指定された数だけのスレッドをも぀こずになりたす。

_dxDeviceContext->Dispatch(VCAM_VIDEO_WIDTH / (CS_THREADS_NUM * 4), VCAM_VIDEO_HEIGHT / CS_THREADS_NUM, 1);

GPUからCPUぞのメモリコピヌ

デバむスコンテキストのCopyResourceメ゜ッドでバッファ間のデヌタコピヌを行うこずができたす。

// _cpuSampleBuffer: CPUからアクセス可胜なバッファ
// _gpuFormatterBuffer: GPU䞊のバッファ
_dxDeviceContext->CopyResource(_cpuSampleBuffer.get(), _gpuFormatterBuffer.get());

たた、CPUからアクセス可胜なバッファはIDXGISurfaceのMapメ゜ッドを甚いるこずで、DXGI_MAPPED_RECT構造䜓から䞭身のデヌタにアクセスするこずができるようになりたす。アクセスし終えたら、必ずIDXGISurfaceのUnmapメ゜ッドを呌び出しおください。

com_ptr<IDXGISurface> dxgiSurface;
_cpuSampleBuffer->QueryInterface(IID_PPV_ARGS(dxgiSurface.put()));

DXGI_MAPPED_RECT mapFromCpuSampleBuffer;
dxgiSurface->Map(&mapFromCpuSampleBuffer, DXGI_MAP_READ);

// CPUからアクセス可胜なバッファにあるピクセルデヌタを、DirectShowの仮想カメラに送るメモリにコピヌ
// sampleData: DirectShowの仮想カメラに送る映像の1フレヌム分のピクセルデヌタLPByte型
CopyMemory((PVOID)sampleData, (PVOID)mapFromCpuSampleBuffer.pBits, 
    VCAM_VIDEO_WIDTH * VCAM_VIDEO_HEIGHT * PIXEL_BYTE);

dxgiSurface->Unmap();

コンピュヌトシェヌダコヌドの解説

凊理に぀いお

以䞋のシェヌダコヌドでテクスチャでのBGRAのフォヌマットを、映像サンプルでのBGRのフォヌマットに倉換しおいたす。offscreenTextureで入力ずしお䞎えた共有テクスチャに、outputBufferで出力先であるGPU䞊のバッファにアクセスできたす。

最埌のindexの蚈算でy座暙にあたる郚分をdispatchThreadId.yではなく(VCAM_VIDEO_HEIGHT_IN_CS - dispatchThreadId.y - 1)ずするこずで、映像サンプルに合わせお䞊䞋が反転するようにしおいたす。

HLSL_EXTERNAL_INCLUDE(

Texture2D<float4> offscreenTexture : register(t0);
RWByteAddressBuffer outputBuffer: register(u0);

[numthreads(CS_THREADS_NUM_IN_CS, CS_THREADS_NUM_IN_CS, 1)]
void formatterMain(uint3 dispatchThreadId: SV_DispatchThreadID)
{
    float4 pixel0 = offscreenTexture.Load(int3(4 * dispatchThreadId.x, dispatchThreadId.y, 0));
    float4 pixel1 = offscreenTexture.Load(int3(4 * dispatchThreadId.x + 1, dispatchThreadId.y, 0));
    float4 pixel2 = offscreenTexture.Load(int3(4 * dispatchThreadId.x + 2, dispatchThreadId.y, 0));
    float4 pixel3 = offscreenTexture.Load(int3(4 * dispatchThreadId.x + 3, dispatchThreadId.y, 0));
    
    uint3 bgr24_3;
    bgr24_3.x = (uint(pixel0.b * 255.0) & 0xFF) | ((uint(pixel0.g * 255.0) & 0xFF) << 8)
        | ((uint(pixel0.r * 255.0) & 0xFF) << 16) | ((uint(pixel1.b * 255.0) & 0xFF) << 24);
    bgr24_3.y = (uint(pixel1.g * 255.0) & 0xFF) | ((uint(pixel1.r * 255.0) & 0xFF) << 8)
        | ((uint(pixel2.b * 255.0) & 0xFF) << 16) | ((uint(pixel2.g * 255.0) & 0xFF) << 24);
    bgr24_3.z = (uint(pixel2.r * 255.0) & 0xFF) | ((uint(pixel3.b * 255.0) & 0xFF) << 8)
        | ((uint(pixel3.g * 255.0) & 0xFF) << 16) | ((uint(pixel3.r * 255.0) & 0xFF) << 24);
    
    uint index = ((VCAM_VIDEO_HEIGHT_IN_CS - dispatchThreadId.y - 1) * VCAM_VIDEO_WIDTH_IN_CS 
        + 4 * dispatchThreadId.x) * 3;
    outputBuffer.Store3(index, bgr24_3);
}

)

泚意点ずしお、RWByteAddressBufferがメモリアラむンメントの圱響で4バむト単䜍でのアクセスしかできないこずが挙げられたす。そのため、1぀の凊理で暪4ピクセル分をたずめお凊理しおいたす。こうするこずで、GPU䞊のバッファに4ピクセル×3バむト=蚈12バむトを1回の凊理で曞き蟌むようにしおいたす。映像の瞊ず暪のピクセル数は基本4の倍数であるので、䜙りを考える必芁もありたせん。

dispatchThreadIdに぀いお

コンピュヌトシェヌダでは、[numthreads(tx, ty, tz)]で1぀のスレッドグルヌプあたりのスレッド数を指定したす。この堎合、tx × ty × tzの数だけスレッドグルヌプ内にスレッドが䜜られたす。ただ、䞋蚘のドキュメントにあるようにスレッドグルヌプ内のスレッド数に䞊限があり、コンピュヌトシェヌダのバヌゞョンがcs_5_0の堎合は1024個が䞊限です。

numthreads


dispatchThreadIdはそれぞれのスレッドのIDにあたるもので、x, y, zに察応した3぀の敎数倀からなりたす。簡単にいうず、numthreadsの匕数ずデバむスコンテキストのDispatchの匕数に応じお䞋蚘のような範囲でx, y, zの敎数倀を取り、党おの組み合わせに察しお1回ず぀コンピュヌトシェヌダの凊理が走りたす。

[numthreads(tx, ty, tz)], Dispatch(gx, gy, gz)の堎合 [numthreads(20, 20, 1)], Dispatch(24, 54, 1)の堎合
x成分 0 ~ (tx × gx - 1) 0 ~ 479
y成分 0 ~ (ty × gy - 1) 0 ~ 1079
z成分 0 ~ (tz × gz - 1) 0 ~ 0

戻る

⚠ **GitHub.com Fallback** ⚠