【问题标题】:Handling Boundary Conditions in OpenCL/CUDA在 OpenCL/CUDA 中处理边界条件
【发布时间】:2015-03-18 06:03:05
【问题描述】:

给定一个 3D 统一网格,我想将边界单元格的值设置为相对于网格内最近邻的值。例如,给定一个 10x10x10 的网格,对于坐标 (0, 8, 8) 处的体素,我想设置一个值如下:val(0, 8, 8)=a*val(1,8 ,8)

由于 a 可以是任何实数,我认为在这种情况下不能使用纹理 + 采样器。此外,该方法也应该适用于普通缓冲区。

此外,由于边界体素坐标可以是网格的角、边或面的一部分,因此存在 26 (= 8 + 12 + 6) 种不同的选择来查找最近的邻居(例如,如果坐标位于 ( 0,0,0) 它在网格内最近的邻居将是 (1, 1, 1))。所以有很多潜在的分支。

在 OpenCL/CUDA 中是否有一种“优雅”的方式来实现这一点?另外,是否建议使用单独的内核处理边界?

【问题讨论】:

    标签: cuda opencl gpgpu


    【解决方案1】:

    在 CUDA 中处理边界最常用的方法是检查所有可能的边界条件并采取相应措施,即:

    • 如果“此元素”超出范围,则return(这在 CUDA 中非常有用,您可能会启动比严格必要的线程更多的线程,因此必须提前退出额外的线程以避免写入输出-of-bounds 内存)。
    • 如果“此元素”位于/靠近左边框(最小 x),则对左边框执行特殊操作。
    • 右、上、下(以及 3D 中的前后)边框相同。

    幸运的是,在大多数情况下,您可以使用 max/min 来简化这些操作,从而避免使用过多的 if。我喜欢用这种形式的表达方式:

    source_pixel_x = max(0, min(thread_2D_pos.x + j, MAX_X));
    source_pixel_y = ... // you get the idea
    

    这些表达式的结果总是限制在 0 和某个 MAX 之间,因此将 out_of_bounds 源像素钳制到边界像素。

    编辑:正如 DarkZeros 所评论的,使用clamp() 函数更容易(并且更不容易出错)。它不仅检查最小值和最大值,还允许像 float3 这样的向量类型,并分别钳制每个维度。见:clamp

    这是我作为练习做的一个例子,一个 2D 高斯模糊:

    __global__
    void gaussian_blur(const unsigned char* const inputChannel,
                       unsigned char* const outputChannel,
                       int numRows, int numCols,
                       const float* const filter, const int filterWidth)
    {
      const int2 thread_2D_pos = make_int2( blockIdx.x * blockDim.x + threadIdx.x,
                                            blockIdx.y * blockDim.y + threadIdx.y);
      const int thread_1D_pos = thread_2D_pos.y * numCols + thread_2D_pos.x;
    
      if (thread_2D_pos.x >= numCols || thread_2D_pos.y >= numRows)
      {
          return;  // "this output pixel" is out-of-bounds. Do not compute
      }
    
      int j, k, jn, kn, filterIndex = 0;
      float value = 0.0;
      int2 pixel_2D_pos;
      int pixel_1D_pos;
    
      // Now we'll process input pixels.
      // Note the use of max(0, min(thread_2D_pos.x + j, numCols-1)),
      // which is a way to clamp the coordinates to the borders.
      for(k = -filterWidth/2; k <= filterWidth/2; ++k)
      {
          pixel_2D_pos.y = max(0, min(thread_2D_pos.y + k, numRows-1));
          for(j = -filterWidth/2; j <= filterWidth/2; ++j,++filterIndex)
          {
              pixel_2D_pos.x = max(0, min(thread_2D_pos.x + j, numCols-1));
              pixel_1D_pos =  pixel_2D_pos.y * numCols + pixel_2D_pos.x;
    
              value += ((float)(inputChannel[pixel_1D_pos])) * filter[filterIndex];
          }
      }
    
        outputChannel[thread_1D_pos] = (unsigned char)value;
    } 
    

    【讨论】:

    • 我会使用 clamp(val, min, max) 一起使用 min() 和 max()。
    • 很好,@DarkZeros。看来我太喜欢我的老方法了。确实,clamp() 是一个更好的选择。
    【解决方案2】:

    在 OpenCL 中,您可以使用 Image3d 来处理您的 3d 网格。边界处理可以通过采样器和特定地址模式来实现:

    • CLK_ADDRESS_REPEAT - 超出范围的图像坐标被包装到有效范围。此地址模式只能与归一化坐标一起使用。如果不使用归一化坐标,这种寻址模式可能会生成未定义的图像坐标。
    • CLK_ADDRESS_CLAMP_TO_EDGE - 超出范围的图像坐标被限制在一定范围内。
    • CLK_ADDRESS_CLAMP32 - 超出范围的图像坐标将返回边框颜色。如果图像通道顺序为 CL_A、CL_INTENSITY、CL_RA、CL_ARGB、CL_BGRA 或 CL_RGBA,则边框颜色为 (0.0f, 0.0f, 0.0f, 0.0f),如果图像通道顺序为 (0.0f, 0.0f, 0.0f, 1.0f)通道顺序为 CL_R、CL_RG、CL_RGB 或 CL_LUMINANCE。
    • CLK_ADDRESS_NONE - 对于这种地址模式,程序员保证用于对图像元素进行采样的图像坐标指的是图像内部的位置;否则结果是不确定的。

    此外,您可以为插值定义过滤器模式(最近邻或线性)。

    这符合您的需求吗?否则,请向我们提供有关您的数据及其边界要求的更多详细信息。

    【讨论】:

    • 抱歉,我的问题不是很具体(我更新了我的问题),不幸的是,我认为我不能使用纹理。
    • 只是为了澄清:您想从最接近的内部值推断您的边界值吗?您的数据不会提供 - 可以说 - “船体”值,而只会提供内部值。在这种情况下,在整个数据集中使用线性插值/外推方案可能会很有用。除了一些优化代码之外,您的应用程序没有直接的 OpenCL 库支持。好吧,当然你可以使用像 float4 x = f * (float4) a + (1-f) * (float4) b 这样的代码在 ab 之间进行线性插值
    • 不一定外推。基本上,给定一个坐标(全局 id id),如果该坐标位于网格的边界(边界)上(例如,id.x == 0 表示坐标位于网格的左边界),那么我想知道哪个不是边界坐标的最近坐标,以便我可以获取该位置的值,对其进行一些操作并将其存储在边界坐标的位置。我想编写一个流体模拟,对于网格上的边界速度,我需要最近的“内部”单元格的速度值,将其乘以 -1 并将其存储在边界
    • 我猜,你的问题不是 3d 问题。每个维度中最近的坐标与维度的数量无关。所以对于一维情况,c = 0 的最近坐标是 1。对于 c = max,最近的坐标是 max-1。这在每个维度上都是有效的。你可以使用像float nearest = max(1, min(n-1,x)) 这样的最小/最大查询。 OpenCL 提供了按组件操作的 min-max-functions。这个方案应该适应你的网格数据(抱歉剩下的工作)。
    • 更好的解决方案当然是:float nearest = clamp(x,1, n-1)(见上面的答案)
    猜你喜欢
    • 2015-02-01
    • 2018-07-07
    • 2011-02-08
    • 2011-06-18
    • 2019-01-14
    • 1970-01-01
    • 2014-11-08
    • 2015-10-24
    • 1970-01-01
    相关资源
    最近更新 更多