所以我有一些想解决的困境。我要绘制10,000个三角带。每个三角形对于整个带都具有z阶,并且在每个带中,最新的三角形需要具有比旧三角形更高的z阶。因此,三角形的z阶本质是(stripIndex / 10000) + (age / MAX_AGE),其中MAX_AGE大约为100帧。我还不确定这个数字。

开始时,所有存储该数据的数据都已初始化,并且使用某种形式的环形缓冲区,以便最近的三角形每个都位于一个新位置

此环形缓冲区非常有用,因为每个三角形带的每个帧仅需写入2个三角形,因此无需移动或移动数据。您只需要覆盖最旧的数据即可。

诀窍是我想在这些三角形条之间进行alpha混合,因此需要从前到后按顺序绘制几何图形。我正在尝试不同的布局,这将使我最小化绘图调用和所需的CPU压力。对我来说,这是一个尚未解决的问题,但我认为我将不得不进行10001次抽奖。但这不是这个问题的意义。

因此,在尝试执行此操作时,我遇到了无法解释的奇怪GPU现象。现在,我正在使用一个简单的无深度添加剂混合Metal管道进行渲染,如下所示。

id<MTLFunction> vertex_f = [MetalCore compileShader:vertex];
id<MTLFunction> fragment_f = [MetalCore compileShader:fragment];

MTLRenderPipelineDescriptor *descriptor = [[MTLRenderPipelineDescriptor alloc] init];
descriptor.sampleCount = 1;
descriptor.fragmentFunction = fragment_f;
descriptor.vertexFunction = vertex_f;
descriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
descriptor.colorAttachments[0].blendingEnabled = YES;
descriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
descriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
descriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;


正如我在一开始所说的那样,我创建了一个缓冲区,用于保存所有内容绘制每个条带所需的三角形数。每次缓冲区的大小都是相同的,并且三角形计数也是恒定的。缓冲区确实具有索引,并且该索引也是恒定的,并且在开始时创建。控制这些三角形条的基础模拟也是恒定的。但是,即使在多次试验中,在两种不同的内存布局条件下,帧速率也存在明显差异。我不知道为什么。

从现在开始,我将10000称为MAX_STRIPS

存储器配置1在第i个环形缓冲区中的第i个环形缓冲区中的三角形缓冲区中的第i*MAX_STRIPS*4 + n*4个位置处具有一个三角形。因此,在内存中,单个条形三角形非常分散,但是每帧都会更新内存的集中块。

内存配置2在第i个环形缓冲区的第i个环形缓冲区的三角形缓冲区中的n*MAX_AGE*4 + i*4位置处有一个三角形。所有单个三角形条带的顶点在内存中都靠在一起,但是写入更加分散。

无论出于何种原因,配置1比配置2花费的时间要长得多。

现在我可以理解CPU时间的微小差异,但是我没有得到的是GPU时间的差异。关于为什么会发生这种情况的任何线索。

下面比较了这两种方法的调试信息。左侧是内存配置1。右侧是内存配置2。突出显示了很大的不同。


这里是所有感兴趣的顶点着色器

struct VertexOutAlpha {
    float4 position [[ position ]];
    float4 color;
};
struct StripVertex {
    float3 data; //z value is the z value for the entire strip. Triangles will have different z positions
    float2 color;//x = color, y = time
};
vertex VertexOutAlpha strip_vertex(constant StripVertex * vertexBuffer [[ buffer(0) ]],
                              indexType vid [[ vertex_id ]],
                              constant matrix_float3x3* matrix [[buffer(1)]],
                              constant float* gameTime [[buffer(2)]]) {
    StripVertex vert = vertexBuffer[vid];
    VertexOutAlpha out;
    float dimmer = 1.0 - clamp(((*gameTime - 0.016) - vert.color.y) * 10.0, 0.0, 1.0);
    const float levelSize = 1.0 / (MAX_STRIPS + 0.01);
    out.position = float4((*matrix * float3(vert.data.x, vert.data.y, 1.0)).xy, vert.data.z + (1 - dimmer) * levelSize, 1.0);

     out.color = float4(HSVtoRGB(vert.color.x, 1.0, dimmer), 1.0);
    return out;
}


为所有感兴趣的人生成顶点缓冲区

    //triangleStripDataPtr is pre-allocated and alligned to the page size
    triangleStripDataBuffer = [self.device newBufferWithBytesNoCopy:triangleStripDataPtr length:4 * MAX_STRIPS *  MAX_AGE options:MTLResourceStorageModeShared deallocator:nil];
    assert(triangleStripDataBuffer != nil);


有人能很好地解释为什么会发生这种情况吗? >

评论

您说配置2需要更长的渲染时间,但是在您的性能分析屏幕快照中,配置2需要55ms而不是81ms。句子是否以“出于某种原因”开头?

它是。修改过的问题

#1 楼

我认为您正在看到缓存效果。 GPU将按顺序开始每个条带的顶点处理,并且在配置1中,GPU将为条带中的每个三角形获取不同的内存块(因为正如您所说,条带本身在此配置中在内存中并不连续) )。

如果我理解正确,则环形缓冲区中的每个条目都是一个三角形,因此,至少有3个顶点彼此相邻(在配置1中)。这具有不错的空间局部性,但是获取此内存可能还会为相邻顶点拉入内存,这将不会立即有用(因为它属于另一个条带)。

在配置2中,相邻的顶点将立即有用,因为它们是GPU需要为当前三角带渲染的下一个顶点。结果可能是总内存流量较少。

这是一个稍微简化的解释(例如,它清除了GPU上发生的并行性),但似乎与您观察到的相符。 。证明选择配置2而不是配置1的一种方法是,您可以在GPU上以CPU的代价来简化它的工作,但是,每帧CPU都有1 / MAX_AGE的工作要做GPU,因此优化CPU更新(“每帧更新集中的内存块”)的价值将降低。

评论


$ \ begingroup $
为什么会以非顺序的顺序获取内容?索引缓冲区使它线性地遍历结构。因此,在两种情况下,绘图调用和索引缓冲区都是相同的。抱歉,如果原始问题不清楚。我认为这很重要的唯一方法是gpu是否将索引缓冲区覆盖到对其空间有意义的内容。
$ \ endgroup $
–J.Doe
18年9月6日,0:48

$ \ begingroup $
@ J.Doe您是说三角形在两种配置之间最终以不同的条带(并因此在不同的绘制调用中)出现了吗?在这种情况下,我对您原始问题中的陈述感到困惑,即“内存中的单个条形三角形非常分散”(这是有关内存配置1的注释)。
$ \ endgroup $
– John Calsbeek
'18 Sep 6'在4:10

$ \ begingroup $
所有三角形的抽奖要求为1
$ \ endgroup $
–J.Doe
'18 Sep 6'在16:53

$ \ begingroup $
@ J.Doe-当您在内存配置1中说“集中的内存块已更新”而在配置2中说“写的更多分散”时,我不太理解您的说法。但是,我认为John是尝试告诉您,对于内存配置2,当GPU访问第一个三角形的数据时,将提取需要绘制的下一个三角形的数据。这是因为GPU会按块访问内存。这样整个块就被写入缓存了。对于下一个三角形,它可以直接从高速缓存中获取,而不是从内存中进行缓慢访问,因此可以提高速度。
$ \ endgroup $
–gallickgunner
'18 Sep 7'在16:41