我刚刚开始学习 DirectX 编程,使用 F# 和 SharpDX 作为 .NET 包装器。作为一个测试用例,我渲染了 Mandelbrot 集。计算是使用 2 个计算着色器完成的。
第一个着色器计算每个像素的深度(函数“CalcMandel”),结果存储在 RWStructuredBuffer 中。这种计算需要大量的单乘或双乘,但它在我的 GPU (AMD 7790) 上速度非常快。“CalcMandel”具有属性
[numthreads(16, 16, 1)]
并通过
context.Dispatch (imageWidth / 16, imageHeight / 16, 1)
这里没有问题——“核心”Mandelbrot 集的 1000 x 800 像素图像以超过 1000 fps 的速度运行(在 GPU 上使用单精度)。
第二个着色器几乎什么都不做:它计算先前计算的最小值、最大值和平均值(函数“CalcMinMax”)。“CalcMinMax”具有属性
[numthreads(1, 1, 1)]
并通过调用
context.Dispatch (1,1,1)
对于给定的图像大小,单个 GPU 线程必须遍历缓冲区超过 800.000 个整数来计算最小值、最大值和平均值。我使用单线程,因为我不知道如何以并行方式实现此计算。
问题:“CalcMinMax”非常慢:帧速率从 1000 多帧下降到 5 帧!
我的问题:这里有什么问题?我是否使用了错误的设置/参数(numthreads)?如何加快 min-max 计算?
我的想法:我的第一个假设是访问 RWBuffer 可能很慢——事实并非如此。当我用常量替换缓冲区访问时,帧速率没有增加。
我的 GPU 有 appr。900 个着色器核心并使用数千个线程来计算 Mandelbrot 集,而“CalcMinMax”仅使用一个线程。然而,我仍然不明白为什么事情变得如此缓慢。
我将不胜感激任何建议!
=================================================
// HLSL CONTENT (Mandelbrot 集计算省略):
cbuffer cbCSMandel : register( b0 )
{
double a0, b0, da, db;
double ja0, jb0;
int max_iterations;
bool julia; int cycle;
int width; int height;
double colorFactor;
int algoIndex;
int step;
};
struct statistics
{
int minDepth;
int axDepth;
float avgDepth;
int loops;
};
RWStructuredBuffer<float4> colorOutputTable : register (u0);
StructuredBuffer<float4> output2 : register (t0);
RWStructuredBuffer<int> counterTable : register (u1);
RWStructuredBuffer<float4> colorTable : register (u2);
RWStructuredBuffer<statistics>statsTable : register (u3);
// Mandelbrot calculations….
// Results are written to counterTable and colorOutputTable
// I limit the samples to 10000 pixels because calcMinMax is too slow
#define NUM_SAMPLES 10000;
void calcMinMax()
{
int minDepth = 64000;
int maxDepth = 0;
int len = width * height;
int crit = len / NUM_SAMPLES;
int steps = max (crit, 1);
int index = 0;
int sumCount = 0;
float sum = 0.0;
while (index < len)
{
int cnt = counterTable[index];
minDepth = cnt < minDepth & cnt > 0 ? cnt : minDepth;
maxDepth = cnt > maxDepth ? cnt : maxDepth;
sum += cnt > 0 ? cnt : 0.0f;
sumCount += cnt > 0 ? 1 : 0;
index += steps;
}
statsTable[0].minDepth = minDepth;
statsTable[0].maxDepth = maxDepth;
statsTable[0].avgDepth = sum / sumCount;
statsTable[0].loops += 1;
}
[numthreads(1, 1, 1)]
void CalcMinMax ( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )
{
switch (GI) // this switch is used to verify GI number (always 0)
{
case 0: calcMinMax();
break;
default: ;
break;
}
}
// * ** * ** * ** * ** * ** * ** * F# 程序(最小-最大部分) * ** * ** * ** * ** *
着色器设置:
use minMaxShaderCode = ShaderBytecode.CompileFromFile(shaderPath, "CalcMinMax", "cs_5_0")
minMaxShader <- new ComputeShader(device, minMaxShaderCode.Bytecode.Data )
着色器用法:
// ---------- CONNECT MinMap Shader
context.ComputeShader.Set(minMaxShader)
context.ComputeShader.SetUnorderedAccessView(STATS_SLOT, statsBuffer.BufferView)
context.ComputeShader.SetConstantBuffer(CONSTANT_SLOT, constantBuffer)
context.ComputeShader.SetUnorderedAccessView (COUNTER_SLOT, dataBuffer.BufferView)
context.Dispatch (1,1,1)
// ---------- DISCONNECT MinMap Shader
context.ComputeShader.SetConstantBuffer(CONSTANT_SLOT, null)
context.ComputeShader.SetUnorderedAccessView (STATS_SLOT, null)
context.ComputeShader.SetUnorderedAccessView (COUNTER_SLOT, null)
context.ComputeShader.Set (null)
阅读统计:
context.CopyResource(statsBuffer.DataBuffer, statsBuffer.StagingBuffer)
let boxer, stream = context.MapSubresource(statsBuffer.StagingBuffer, MapMode.Read, MapFlags.None)
calcStatistics <- stream.Read<CalcStatistics>()
context.UnmapSubresource(statsBuffer.DataBuffer, 0)