zoukankan      html  css  js  c++  java
  • Compute Shader 功能测试(二)

    在 上一篇 中使用ComputeShader进行了向量和矩阵的相乘计算,然后在C#代码中通过ComputeBuffer.GetData方法从GPU中读取计算结果,这个方法是一个同步操作,即调用时会堵塞调用线程,直到GPU返回数据为止,所以在需要读取的数据量很大时会有比较高的耗时,会导致游戏卡顿影响体验。
    Google了一番法线有异步的方法可以调用,在Unity2018版本以后增加了AsyncGPUReadbackAsyncGPUReadbackRequest类,可以实现异步方式从GPU读取数据,大致逻辑是:

    1. AsyncGPUReadback.Request 发起一个异步获取数据的请求,返回一个AsyncGPUReadbackRequest对象
    2. 在Update中每帧检测该异步请求是否完成,完成的话就去该请求对象中获取数据

    下面是主要部分的代码
    C#部分:

    void Dispach()
    {
       if (computeShader == null)
       {
           return;
       }
    
       int kernelIndex = -1;
       try
       {
           kernelIndex = computeShader.FindKernel(GetKernelName(method));
       }
       catch (Exception error)
       {
           Debug.LogFormat("Error: {0}", error.Message);
           return;
       }
    
       switch (method)
       {
           case EMethod.ComputerBuffer:
               if (m_comBuffer != null)
               {
                   m_comBuffer.Release();
               }
    
               // 初始化m_dataArr //
               InitDataArr();
    
               m_comBuffer = new ComputeBuffer(m_dataArr.Length, sizeof(float) * Stride);
               m_comBuffer.SetData(m_dataArr);
               computeShader.SetBuffer(kernelIndex, "ResultBuffer", m_comBuffer);
    
               // 在Shader中只需要用到X维的数据作为数组索引,因此只需要给X维的thread group设置数值,Y维和Z维的thread group数量为1即可 //
               computeShader.Dispatch(kernelIndex, 32, 1, 1);
               break;
       }
    }
    
    void GetResultAsync()
    {
       switch (method)
       {
           case EMethod.ComputerBuffer:
               if (m_comBuffer == null ||
                   m_objArr == null ||
                   m_dataArr == null)
               {
                   break;
               }
               m_processed = false;
               m_request = AsyncGPUReadback.Request(m_comBuffer, m_dataArr.Length * Stride, 0);
               m_asyncFrameNum = 0;
               break;
       }
    }
    
    void Update()
    {
        if (!m_processed)
        {
            m_asyncFrameNum++;
    
            if (m_request.done && !m_request.hasError)
            {
                m_processed = true;
    
                Profiler.BeginSample("GetDataFromGPU_Async");
                using (Timer timer = new Timer(Timer.ETimerLogType.Millisecond))
                {
                    // 方式2 //
                    m_request.GetData<DataStruct>(0).CopyTo(m_dataArr);
    
                    // 方式1, ToArray 方法会有GC产生 //
                    //m_dataArr = null;
                    //m_dataArr = m_request.GetData<DataStruct>(0).ToArray();
                }
    
                Profiler.EndSample();
    
                if (m_computeShaderWarmedUp)
                {
                    Callback();
                }
                else
                {
                    m_computeShaderWarmedUp = true;
                }
    
                Scene curScene = SceneManager.GetActiveScene();
                string sceneName = "";
                if (curScene != null)
                {
                    sceneName = curScene.name;
                }
    
                Debug.LogFormat("Async 方式等待的帧数: {0}, 场景名称: {1}", m_asyncFrameNum, sceneName);
            }
        }
    }
    
    // 初始化传给GPU的数据 //
    void InitDataArr()
    {
        if (m_dataArr == null)
        {
            m_dataArr = new DataStruct[MaxObjectNum];
        }
    
        const int PosRange = 10;
        for (int i = 0; i < MaxObjectNum; i++)
        {
            m_dataArr[i].pos = new Vector4(0, 0, 0, 1);
            m_dataArr[i].scale = Vector3.one;
    
            Matrix4x4 matrix = Matrix4x4.identity;
    
            // 位移信息 //
            matrix.m03 = (Random.value * 2 - 1) * PosRange;
            matrix.m13 = (Random.value * 2 - 1) * PosRange;
            matrix.m23 = (Random.value * 2 - 1) * PosRange;
    
            // 缩放信息 //
            matrix.m00 = Random.value * 2 + 1;        // 从[0,1]映射到[1,3] //
            matrix.m11 = Random.value * 2 + 1;
            matrix.m22 = Random.value * 2 + 1;
    
            m_dataArr[i].matrix = matrix;
        }
    }

    Shader部分和 上一篇 一样

    实验结果:

    1. 异步的延迟基本稳定在3帧。
    2. AsyncGPUReadbackRequest.GetData返回的NativeArray对象,尽量使用CopyTo方法把数据传递给自定义的数组,而少用ToArray方法,因为ToArray会产生GC而CopyTo不会。
    3. 测试场景中有100个物体,每个物体使用一个如下的结构体:
    struct DataStruct
    {
        public Vector4 pos;
        public Vector3 scale;
        public Matrix4x4 matrix;
    }

    每个结构体对象含有 4 + 3 + 4 * 4=23个float值,即一次需要从GPU读取的数据量是100 * 23 = 2300个float值,耗时情况如下:

    方法耗时
    AsyncGPUReadbackRequest.GetData 1.84, 0.01, 0.01, 0.01, 0.01, 0.01
    ComputeBuffer.GetData 1.12, 0.28, 1.10, 0.22, 0.57, 0.48

    可以看到 AsyncGPUReadbackRequest.GetData 方法除了第一次耗时比较多以外,后面的每次读取都稳定在0.01ms,因为在调用AsyncGPUReadbackRequest.GetData的时候异步操作已经结束,因此直接从AsyncGPUReadbackRequest 对象中读取数据并不需要花多少时间。

    关于 AsyncGPUReadbackRequest.GetData 第一次调用耗时较多的问题

    AsyncGPUReadbackRequest.GetData 第一次调用为什么耗时较多的问题目前还没有查到结果,目前的测试结果是只有第一次调用会出现耗时较多的情况,在后面的每次调用都基本稳定在0.01ms(2300个float数据),在切换场景(Single和Additive都试过)以后依然是0.01ms。这个现象给我的感觉有点像是没有进行ShaderWarmUp而引起的hiccup,但是很不幸我在Start中加入Shader.WarmupAllShaders() 后问题并没有解决。现在的解决办法是在正式从GPU回读数据之前,先在一个无关紧要的时机调用一次 AsyncGPUReadbackRequest.GetData 方法。

    这个问题我在Unity的论坛上 一个类似的问题 下面艾特了一个官方人员,但是目前并没有收到回复,还有Github上一个 开发者的 一个类似的测试工程,下载下来运行法线也存在第一次调用 AsyncGPUReadbackRequest.GetData 耗时明显多于后续调用的问题,给Hub主发邮件询问了一下,对方表示也不太清楚,看起来GPU的异步回读速度相比同步回读速度不太稳定,而且和Graphics的设置有关,还有就是在Metal平台上比DX11上更不稳定。目前就知道这么多了,如果有哪位大神凑巧知道详情请一定留言相告哈,或者有其他思路的童鞋也欢迎留言启发一下我,先行感谢了。

    参考链接:
    https://feedback.unity3d.com/suggestions/asynchronous-computebuffer-dot-getdata
    https://docs.unity3d.com/ScriptReference/Rendering.AsyncGPUReadbackRequest.GetData.html
    https://github.com/keijiro/AsyncCaptureTest

    转载:https://blog.csdn.net/h5502637/article/details/85637872

    博客园Jason_C技术交流群

    扫描二维码加入qq群:623307256,共同探讨工作中遇到的Unity相关的问题!

  • 相关阅读:
    第34天-文件_system (2013.09.04)
    第33天-文件I/O _2(2013.09.03)
    小项目 : 计算库函数中单词的个数第30天
    第32天-文件I/O _1(2013.09.02)
    嵌入式培训学习历程第二十九天
    大作业 :学生信息管理系统。。。
    嵌入式培训学习历程第二十六天
    读取一个文件中哪一行 的一个参数
    LINUX C 语言 快速获取调用SHELL命令后的结果
    C语言制造一个随机数
  • 原文地址:https://www.cnblogs.com/Jason-c/p/14307259.html
Copyright © 2011-2022 走看看