DX11(SharpDX)计算着色器结果读取异常排查求助
DirectX 11计算着色器结果读取异常排查
我用SharpDX开发DirectX 11计算着色器,预期输出应为{2,2,2}, {5,5,5}, {8,8,8},但实际得到{2,5,8}, {0,0,0}, {0,0,0};将HLSL输出改为xyz时,结果变成{1,4,7}, {0,0,0}, {0,0,0}。确认计算着色器输入和逻辑没问题,问题应该出在GPU结果读取环节,求排查。
C#代码实现
float[] buffer = new float[]{1,2,3,4,5,6,7,8,9}; var vertexArray = MemoryMarshal.Cast<float, Vector3>(buffer).ToArray(); var device = new Device(DriverType.Hardware, DeviceCreationFlags.Debug); var context = device.ImmediateContext; // 编译计算着色器 var faceCsFile = Path.Combine(AppContext.BaseDirectory, "HLSL/Face_CS.hlsl"); var shaderBytecode = ShaderBytecode.CompileFromFile(faceCsFile, "CS", "cs_5_0"); var computeShader = new ComputeShader(device, shaderBytecode); shaderBytecode.Dispose(); var inputDesc = new BufferDescription() { SizeInBytes = SharpDX.Utilities.SizeOf<Vector3>() * vertexArray.Length, Usage = ResourceUsage.Default, BindFlags = BindFlags.ShaderResource | BindFlags.UnorderedAccess, OptionFlags = ResourceOptionFlags.BufferStructured, StructureByteStride = SharpDX.Utilities.SizeOf<Vector3>(), }; var verticeBuffer = Buffer.Create(device, vertexArray, inputDesc); device.ImmediateContext.UnmapSubresource(verticeBuffer, 0); var srvDesc = new ShaderResourceViewDescription() { Format = SharpDX.DXGI.Format.Unknown, Dimension = ShaderResourceViewDimension.Buffer, BufferEx = new ShaderResourceViewDescription.ExtendedBufferResource() { FirstElement = 0, ElementCount = vertexArray.Length, Flags = ShaderResourceViewExtendedBufferFlags.None } }; var vertexBufferView = new ShaderResourceView(device, verticeBuffer, srvDesc); context.ComputeShader.SetShaderResource(0, vertexBufferView); // 将输入和输出缓冲区绑定到计算着色器上 context.ComputeShader.Set(computeShader); var dataOut = new Vector3[vertexArray.Length]; var outDes = new BufferDescription() { SizeInBytes = SharpDX.Utilities.SizeOf<Vector3>() * vertexArray.Length, Usage = ResourceUsage.Default, BindFlags = BindFlags.UnorderedAccess, OptionFlags = ResourceOptionFlags.BufferStructured, StructureByteStride = SharpDX.Utilities.SizeOf<Vector3>(), }; var bufferOut = Buffer.Create(device, dataOut, outDes); // 创建用于指定着色器输出的UAV var uavDesc = new UnorderedAccessViewDescription() { Format = SharpDX.DXGI.Format.Unknown, Dimension = UnorderedAccessViewDimension.Buffer, Buffer = new UnorderedAccessViewDescription.BufferResource() { ElementCount = vertexArray.Length, } }; var bufferOutView = new UnorderedAccessView(device, bufferOut, uavDesc); context.ComputeShader.SetUnorderedAccessView(0, bufferOutView); int count = (vertexArray.Length + 1023) / 1024; context.Dispatch(count, 1, 1);// 启动计算着色器 var stagingBuffer = new Buffer(device, new BufferDescription { BindFlags = BindFlags.None, CpuAccessFlags = CpuAccessFlags.Read, OptionFlags = ResourceOptionFlags.None, SizeInBytes = dataOut.Length * SharpDX.Utilities.SizeOf<Vector3>(), Usage = ResourceUsage.Staging });// 创建一个暂存缓冲区 context.CopyResource(bufferOut, stagingBuffer);// 将输出缓冲区的内容复制到暂存缓冲区中 // 将暂存缓冲区映射到内存中 var dataBox = context.MapSubresource(stagingBuffer, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); Span<Vector3> result; unsafe{result = new Span<Vector3>((void*)dataBox.DataPointer, dataOut.Length);}// 读取数据 context.UnmapSubresource(stagingBuffer, 0);// 取消映射 foreach (var vec in result.ToArray()) Console.WriteLine(vec);
HLSL计算着色器代码
StructuredBuffer<float3> Vertex : register(t0); RWBuffer<float3> Out : register(u0); [numthreads(1024, 1, 1)] void CS(uint3 DTid : SV_DispatchThreadID) { uint id = DTid.x; Out[id]=Vertex[id].yyy; }
问题根源分析
暂存缓冲区属性不匹配
输出缓冲区是结构化缓冲区(ResourceOptionFlags.BufferStructured),但创建的暂存缓冲区未指定结构化属性,导致CPU映射时无法正确解析内存布局,把连续的float数据错误地按Vector3结构读取。GPU计算与CPU复制不同步
Dispatch调用后GPU可能还未完成计算就执行CopyResource,导致复制的是未初始化或不完整的数据。
修正方案
修正暂存缓冲区创建
让暂存缓冲区和输出缓冲区保持一致的结构化属性:var stagingBuffer = new Buffer(device, new BufferDescription { BindFlags = BindFlags.None, CpuAccessFlags = CpuAccessFlags.Read, OptionFlags = ResourceOptionFlags.BufferStructured, StructureByteStride = SharpDX.Utilities.SizeOf<Vector3>(), SizeInBytes = dataOut.Length * SharpDX.Utilities.SizeOf<Vector3>(), Usage = ResourceUsage.Staging });添加资源同步屏障
在Dispatch后、CopyResource前插入屏障,确保计算完成后再复制数据:context.Dispatch(count, 1, 1); // 添加资源屏障同步GPU计算 var barrier = new ResourceBarrier( ResourceBarrierType.Transition, ResourceBarrierFlags.None, bufferOut, ResourceState.UnorderedAccess, ResourceState.CopySource ); context.ResourceBarrier(barrier); // 执行复制操作 context.CopyResource(bufferOut, stagingBuffer);移除多余代码
删除device.ImmediateContext.UnmapSubresource(verticeBuffer, 0);,Buffer.Create已经完成资源初始化,无需手动Unmap。
内容的提问来源于stack exchange,提问作者ThraceShah
相关产品推荐
相关产品推荐

