Direct3D 计算着色器 运行 比 Unity 计算着色器慢 15 倍
Direct3D Compute Shader running 15x slower than Unity Compute Shader
我正在 Unity 中开发一个 VR 应用程序,它使用本地插件进行视频解码,我想对解码后的视频帧进行一些处理。
我的第一步是使用从 Unity 应用程序中的 C# 脚本调度的 Unity 计算着色器。这行得通,我看到了预期的结果,但是我在从渲染线程上的本机插件 运行 中拉出一个参数时遇到了同步问题,该参数需要馈送到主线程上的计算着色器 运行线程。
我认为这可以通过将 Unity 计算着色器转换为 D3D11 计算着色器并在解码器弹出解码器后立即在本机插件中处理解码帧来解决。这也给了我预期的结果,但以巨大的性能成本为代价。应用程序丢帧,当使用 RenderDoc 分析单个帧时,我发现插件中的计算调度调用大约需要 32 毫秒,而使用 Unity 计算着色器时则为 3 毫秒。
我找不到任何关于为什么两者之间存在如此差异的信息。我已尝试将 D3D11 着色器简化为简单地写出零,但探查器仍显示大约 32 毫秒,这让我认为这与我在插件中设置着色器有关。
我已经包含了一些代码来展示我的插件计算着色器的设置和执行。
本机 C++ 插件中的计算着色器:
void process()
{
ID3D11DeviceContext* ctx = NULL;
device->GetImmediateContext(&ctx);
ctx->UpdateSubresource(_pCB, 0, nullptr, &_bufferStruct, 0, 0);
if (!_resourcesSet) {
// Set read texture
ID3D11ShaderResourceView * inY = nullptr;
ID3D11ShaderResourceView * inU = nullptr;
ID3D11ShaderResourceView * inV = nullptr;
_inputTexture->getSRVs(&inY, &inU, &inV);
// Set write texture
ID3D11UnorderedAccessView * outY;
ID3D11UnorderedAccessView * outU;
ID3D11UnorderedAccessView * outV;
_outputTexture->getUAVs(&outY, &outU, &outV);
ctx->CSSetConstantBuffers(0, 1, &_pCB);
ctx->CSSetShaderResources(0, 1, &inY);
ctx->CSSetShaderResources(1, 1, &inU);
ctx->CSSetShaderResources(2, 1, &inV);
ctx->CSSetUnorderedAccessViews(0, 1, &outY, nullptr);
ctx->CSSetUnorderedAccessViews(1, 1, &outU, nullptr);
ctx->CSSetUnorderedAccessViews(2, 1, &outV, nullptr);
ctx->CSSetShader(_computeShader, NULL, 0);
_resourcesSet = true;
}
ctx->Dispatch(outputWidth / 8, outputHeight / 8, 1);
ctx->Release();
}
简化的计算着色器本身:
SamplerState TextureSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};
Texture2D<float> inY : register(t0);
Texture2D<float> inU : register(t1);
Texture2D<float> inV : register(t2);
RWTexture2D<float> outY : register(u0);
RWTexture2D<float> outU : register(u1);
RWTexture2D<float> outV : register(u2);
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
float3 col = float3(0.0, 0.0, 0.0);
outY[id.xy] = col.r;
outU[id.xy / 2] = col.g;
outV[id.xy / 2] = col.b;
}
有什么明显的我遗漏的吗,或者 unity 是否非常擅长优化?
我通过在不同的地方进行一些更改设法解决了这个问题。
首先,我更改了着色器以将输出写入单个纹理对象:
RWTexture2D<float4> unpackedRGBA : register(u0);
然后我设法创建了一个可以在着色器中写入并传递给 Unity 的纹理,这意味着我不需要制作纹理副本,我认为这是加快该过程的真正关键:
D3D11_TEXTURE2D_DESC texDesc;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.Usage = D3D11_USAGE_DEFAULT;
texDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags = 0;
texDesc.MiscFlags = 0;
这里的重要部分是绑定标志的组合,这意味着纹理可以通过绑定 UAV 指针写入着色器,但也可以使用 SRV 指针传递给 Unity。
然后我在 Unity 中使用 SRV 指针创建了一个纹理:
IntPtr nativeTexturePtr = new IntPtr();
nativeGetOutputTexture(ref nativeTexturePtr);
output = Texture2D.CreateExternalTexture(videoWidth, videoHeight, TextureFormat.RGBA32, false, false, nativeTexturePtr);
这导致渲染时间与我使用 Unity 计算着色器的初始实现相当,但我一直看到黑色。最终修复是在分派 D3D11 计算着色器后取消绑定输出纹理,这意味着它可以在需要渲染到场景中时在 Unity 中自由绑定。
ctx->CSSetUnorderedAccessViews(0, 1, &gEmptyUav, nullptr);
我正在 Unity 中开发一个 VR 应用程序,它使用本地插件进行视频解码,我想对解码后的视频帧进行一些处理。
我的第一步是使用从 Unity 应用程序中的 C# 脚本调度的 Unity 计算着色器。这行得通,我看到了预期的结果,但是我在从渲染线程上的本机插件 运行 中拉出一个参数时遇到了同步问题,该参数需要馈送到主线程上的计算着色器 运行线程。
我认为这可以通过将 Unity 计算着色器转换为 D3D11 计算着色器并在解码器弹出解码器后立即在本机插件中处理解码帧来解决。这也给了我预期的结果,但以巨大的性能成本为代价。应用程序丢帧,当使用 RenderDoc 分析单个帧时,我发现插件中的计算调度调用大约需要 32 毫秒,而使用 Unity 计算着色器时则为 3 毫秒。
我找不到任何关于为什么两者之间存在如此差异的信息。我已尝试将 D3D11 着色器简化为简单地写出零,但探查器仍显示大约 32 毫秒,这让我认为这与我在插件中设置着色器有关。
我已经包含了一些代码来展示我的插件计算着色器的设置和执行。
本机 C++ 插件中的计算着色器:
void process()
{
ID3D11DeviceContext* ctx = NULL;
device->GetImmediateContext(&ctx);
ctx->UpdateSubresource(_pCB, 0, nullptr, &_bufferStruct, 0, 0);
if (!_resourcesSet) {
// Set read texture
ID3D11ShaderResourceView * inY = nullptr;
ID3D11ShaderResourceView * inU = nullptr;
ID3D11ShaderResourceView * inV = nullptr;
_inputTexture->getSRVs(&inY, &inU, &inV);
// Set write texture
ID3D11UnorderedAccessView * outY;
ID3D11UnorderedAccessView * outU;
ID3D11UnorderedAccessView * outV;
_outputTexture->getUAVs(&outY, &outU, &outV);
ctx->CSSetConstantBuffers(0, 1, &_pCB);
ctx->CSSetShaderResources(0, 1, &inY);
ctx->CSSetShaderResources(1, 1, &inU);
ctx->CSSetShaderResources(2, 1, &inV);
ctx->CSSetUnorderedAccessViews(0, 1, &outY, nullptr);
ctx->CSSetUnorderedAccessViews(1, 1, &outU, nullptr);
ctx->CSSetUnorderedAccessViews(2, 1, &outV, nullptr);
ctx->CSSetShader(_computeShader, NULL, 0);
_resourcesSet = true;
}
ctx->Dispatch(outputWidth / 8, outputHeight / 8, 1);
ctx->Release();
}
简化的计算着色器本身:
SamplerState TextureSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};
Texture2D<float> inY : register(t0);
Texture2D<float> inU : register(t1);
Texture2D<float> inV : register(t2);
RWTexture2D<float> outY : register(u0);
RWTexture2D<float> outU : register(u1);
RWTexture2D<float> outV : register(u2);
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
float3 col = float3(0.0, 0.0, 0.0);
outY[id.xy] = col.r;
outU[id.xy / 2] = col.g;
outV[id.xy / 2] = col.b;
}
有什么明显的我遗漏的吗,或者 unity 是否非常擅长优化?
我通过在不同的地方进行一些更改设法解决了这个问题。
首先,我更改了着色器以将输出写入单个纹理对象:
RWTexture2D<float4> unpackedRGBA : register(u0);
然后我设法创建了一个可以在着色器中写入并传递给 Unity 的纹理,这意味着我不需要制作纹理副本,我认为这是加快该过程的真正关键:
D3D11_TEXTURE2D_DESC texDesc;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.Usage = D3D11_USAGE_DEFAULT;
texDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags = 0;
texDesc.MiscFlags = 0;
这里的重要部分是绑定标志的组合,这意味着纹理可以通过绑定 UAV 指针写入着色器,但也可以使用 SRV 指针传递给 Unity。
然后我在 Unity 中使用 SRV 指针创建了一个纹理:
IntPtr nativeTexturePtr = new IntPtr();
nativeGetOutputTexture(ref nativeTexturePtr);
output = Texture2D.CreateExternalTexture(videoWidth, videoHeight, TextureFormat.RGBA32, false, false, nativeTexturePtr);
这导致渲染时间与我使用 Unity 计算着色器的初始实现相当,但我一直看到黑色。最终修复是在分派 D3D11 计算着色器后取消绑定输出纹理,这意味着它可以在需要渲染到场景中时在 Unity 中自由绑定。
ctx->CSSetUnorderedAccessViews(0, 1, &gEmptyUav, nullptr);