Unity中向Compute Shader收发double数组遇blittable类型错误求助
问题详情
收到错误提示:
ArgumentException: Array passed to ComputeBuffer.SetData(array) must be blittable.
Data.i is not blittable because it is not of value type (System.Double[])
Data.w is not blittable because it is not of value type (System.Double[])
调用栈信息:
UnityEngine.ComputeBuffer.SetData (System.Array data) (at <3be1a7ff939c43f181c0a10b5a0189ac>:0)
Script1.Compute () (at Assets/Script1.cs:46)
Script1.Start () (at Assets/Script1.cs:37)
用户尝试传输double[]数组到Compute Shader,相关代码如下:
C# 原代码
public class Script1 : MonoBehaviour { public double[] inputs; public double[] weights = { 3.1, 2.1, 8.7 }; float bias = 3; float output = 0; public ComputeShader compute1; ComputeBuffer buffer; public struct Data { public double[] i; public double[] w; public float b; public float o; } Data[] d; // Start is called before the first frame update void Start() { d = new Data[1]; d[0] = new Data { i = new double[] { 1.2, 5.1, 2.1 }, w = weights, b = bias, o = output }; buffer = new ComputeBuffer(d.Length, 56); Compute(); } void Compute() { float[] result = new float[2]; int kernelHandle = compute1.FindKernel("CSMain"); compute1.SetBuffer(kernelHandle, "buffer", buffer); buffer.SetData(d); buffer.SetData(result); compute1.Dispatch(kernelHandle, 24, 1, 1); buffer.GetData(result); } private void OnDestroy() { buffer.Dispose(); } }
Compute Shader 原代码
// Each #kernel tells which function to compile; you can have many kernels #pragma kernel CSMain // Create a RenderTexture with enableRandomWrite flag and set it // with cs.SetTexture RWTexture2D<float4> Result; struct d { double i[3], w[3]; float b, o; }; RWStructuredBuffer<d> buffer; [numthreads(24,1,1)] void CSMain (uint3 id : SV_DispatchThreadID) { // TODO: insert actual code here! float result[] = { buffer[0].i[0] * buffer[0].w[0] + buffer[0].i[1] * buffer[0].w[1] + buffer[0].i[2] * buffer[0].w[2] + buffer[0].b, 0 }; }
用户尝试过(System.Double)强制转换但无效,询问正确的Double数组收发方法。
核心问题原因
C#中的double[]是引用类型,而ComputeBuffer要求传输的结构体必须是blittable(可 blittable 值类型)——引用类型无法直接映射到GPU内存的连续结构,因此报错。而Compute Shader中定义的是固定长度的double[3]值类型数组,与C#端的引用类型数组内存布局不匹配。
修正方案
1. 修改C#端结构体为固定长度值类型数组
将C#的Data结构体中的double[]改为固定长度的fixed double数组,同时添加[StructLayout(LayoutKind.Sequential)]标记确保内存布局与Compute Shader一致:
using System.Runtime.InteropServices; public class Script1 : MonoBehaviour { public double[] inputs; public double[] weights = { 3.1, 2.1, 8.7 }; float bias = 3; float output = 0; public ComputeShader compute1; ComputeBuffer dataBuffer; [StructLayout(LayoutKind.Sequential)] public unsafe struct Data { public fixed double i[3]; // 固定长度值类型数组 public fixed double w[3]; public float b; public float o; } Data d; void Start() { // 初始化固定数组 unsafe { var inputVals = new double[] { 1.2, 5.1, 2.1 }; for (int i = 0; i < 3; i++) { d.i[i] = inputVals[i]; d.w[i] = weights[i]; } } d.b = bias; d.o = output; // 计算结构体大小:3*8 + 3*8 + 4 +4 = 56 dataBuffer = new ComputeBuffer(1, sizeof(Data)); Compute(); } void Compute() { // 单独创建结果缓冲区,避免内存冲突 ComputeBuffer resultBuffer = new ComputeBuffer(1, sizeof(float)*2); float[] result = new float[2]; int kernelHandle = compute1.FindKernel("CSMain"); compute1.SetBuffer(kernelHandle, "dataBuffer", dataBuffer); compute1.SetBuffer(kernelHandle, "resultBuffer", resultBuffer); dataBuffer.SetData(new Data[]{d}); resultBuffer.SetData(result); compute1.Dispatch(kernelHandle, 1, 1, 1); // 仅需1个线程处理单组数据 resultBuffer.GetData(result); Debug.Log($"计算结果:{result[0]}"); resultBuffer.Dispose(); } private void OnDestroy() { dataBuffer.Dispose(); } }
2. 修正Compute Shader代码
新增结果缓冲区,将计算结果写入缓冲区,确保结果能正确回传:
#pragma kernel CSMain struct d { double i[3], w[3]; float b, o; }; RWStructuredBuffer<d> dataBuffer; RWStructuredBuffer<float2> resultBuffer; // 新增结果缓冲区 [numthreads(1,1,1)] void CSMain (uint3 id : SV_DispatchThreadID) { double sum = dataBuffer[0].i[0] * dataBuffer[0].w[0] + dataBuffer[0].i[1] * dataBuffer[0].w[1] + dataBuffer[0].i[2] * dataBuffer[0].w[2]; resultBuffer[0] = float2(float(sum) + dataBuffer[0].b, 0); }
3. 关键注意事项
- blittable类型要求:GPU仅识别连续内存布局的数值类型,C#引用类型(如数组、类)无法直接传输,必须使用固定长度值类型数组或连续结构体。
- 内存布局一致性:C#结构体必须添加
[StructLayout(LayoutKind.Sequential)],确保与Compute Shader的结构体内存顺序完全匹配。 - 缓冲区分离:避免同一缓冲区既存数据又存结果,防止内存冲突,建议分开创建数据缓冲区和结果缓冲区。
- 线程调度合理性:根据实际处理的数据量调度线程,无需过度分配线程资源。
内容的提问来源于stack exchange,提问作者Ryan Hibbs

