我计划使用一个vulkan同步示例作为如何处理不经常更新的统一缓冲区的参考。具体来说,我在看这个:
vkBeginCommandBuffer(...);
// Submission guarantees the host write being complete, as per
// https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#synchronization-submission-host-writes
// So no need for a barrier before the transfer
// Copy the staging buffer contents to the vertex buffer
VkBufferCopy vertexCopyRegion = {
.srcOffset = stagingMemoryOffset,
.dstOffset = vertexMemoryOffset,
.size = vertexDataSize};
vkCmdCopyBuffer(
commandBuffer,
stagingBuffer,
vertexBuffer,
1,
&vertexCopyRegion);
// If the graphics queue and transfer queue are the same queue
if (isUnifiedGraphicsAndTransferQueue)
{
// If there is a semaphore signal + wait between this being submitted and
// the vertex buffer being used, then skip this pipeline barrier.
// Pipeline barrier before using the vertex data
// Note that this can apply to all buffers uploaded in the same way, so
// ideally batch all copies before this.
VkMemoryBarrier memoryBarrier = {
...
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TRANSFER_BIT , // srcStageMask
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, // dstStageMask
1, // memoryBarrierCount
&memoryBarrier, // pMemoryBarriers
...);
vkEndCommandBuffer(...);
vkQueueSubmit(unifiedQueue, ...);
}
else
{
// Pipeline barrier to start a queue ownership transfer after the copy
VkBufferMemoryBarrier bufferMemoryBarrier = {
...
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = transferQueueFamilyIndex,
.dstQueueFamilyIndex = graphicsQueueFamilyIndex,
.buffer = vertexBuffer,
...};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TRANSFER_BIT , // srcStageMask
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, // dstStageMask
1, // bufferMemoryBarrierCount
&bufferMemoryBarrier, // pBufferMemoryBarriers
...);
vkEndCommandBuffer(...);
// Ensure a semaphore is signalled here which will be waited on by the graphics queue.
vkQueueSubmit(transferQueue, ...);
// Record a command buffer for the graphics queue.
vkBeginCommandBuffer(...);
// Pipeline barrier before using the vertex buffer, after finalising the ownership transfer
VkBufferMemoryBarrier bufferMemoryBarrier = {
...
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
.srcQueueFamilyIndex = transferQueueFamilyIndex,
.dstQueueFamilyIndex = graphicsQueueFamilyIndex,
.buffer = vertexBuffer,
...};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, // dstStageMask
...
1, // bufferMemoryBarrierCount
&bufferMemoryBarrier, // pBufferMemoryBarriers
...);
vkEndCommandBuffer(...);
vkQueueSubmit(graphicsQueue, ...);
}
在此示例中,我将其简化为:
map updated buffer which is host coherent
perform transfer in transfer queue to device local memory
make sure to put a buffer memory barrier to handle the queue ownership transfer
perform normal draw commands
make sure to put a buffer memory barrier to handle receiving of buffer in queue ownership
然后,我是否必须放弃传输队列再次复制该数据的功能?这些示例似乎都没有提到它,但是我可能会错过它。我真的看不到如何为相同的绘制命令缓冲区添加另一个缓冲区屏障,因为即使我没有什么要转移的东西,它也会在下一次提交时停顿,所以我只需要提交另一个命令缓冲区即可在我提交下一个转移操作之前将所有权转移排队?
即
//begin with transfer ownership
submit(copy)
submit(ownership to graphics)
submit(draw)
submit(ownership to transfer)
submit(copy)
submit(ownership to graphics)
submit(draw)
submit(draw)
submit(draw)
submit(ownership to transfer)
submit(copy)
submit(ownership to graphics)
submit(draw)
如果是这种情况,我不确定如何处理绘制和传输以及复制和绘制之间的信号量信号。刚开始时很容易,但是由于多个nflight框架而变得奇怪,因为绘制提交之间没有依赖关系。基本上,我想我需要设置我提交的最新绘图命令中的任何一个,以使用信号量来表示所有权的转移,这将表示副本,这将表示图形的所有权,以及是否在单独的线程上然后,我将检查此副本是否已提交,并且需要等待图形传输的所有权并重置提交的副本支票。但是我不确定下一帧没有这种依赖性,会发生什么,并且可以先于时间顺序在上一帧之前完成吗?
只要您不介意数据变得不确定,就可以在任何队列族上使用资源(无需传输)。您仍然需要一个信号量,以确保没有内存危险。
旧规范:
注意
如果应用程序在从一个队列族转移到另一个队列族时不需要资源的内容保持有效,则应跳过所有权转移。
例子没有提及,因为它们只是例子。
至于同步(这是与QFOT分开的问题),一部分信号量信号vkQueueSubmit
按提交顺序覆盖了以前的所有内容。因此,当您提交副本时,您可以等待最后提交的信号表明信号量。这意味着在复制可以在另一个队列上开始之前,该队列上的绘图和任何先前的绘图已完成。
然后,您通过副本发出信号量,并在您提交的下一个第一张图纸上等待。这意味着该副本在绘图(和随后的任何绘图)在图形队列上读取它之前完成写入。
例如:
submit(copy, release ownership from tranfer, semaphore signal)
submit(semaphore wait, acquire ownership to graphics, draw)
submit(draw)
submit(draw)
submit(draw)
submit(draw)
submit(draw, semaphore signal)
submit(semaphore wait, copy, release ownership from tranfer, semaphore signal)
submit(semaphore wait, acquire ownership to graphics, draw)
submit(draw)
submit(draw)
etc
尽管注意上述方法实际上将两种访问序列化,但是它可能不是最优的。使用双缓冲(或通常是N缓冲)会更好。如果您有更多的缓冲区,则可以开始复制到一个缓冲区而不必担心它已经被其他对象使用。这意味着复制可以与平局并行发生,这将是非常好的。
嗯,由于我不需要资源在绘制和复制之间保持有效,因此可以跳过尝试将所有权转移到传输队列中的副本的操作,但是事后是否还需要转移所有权?在我的场景中,仍然不需要在绘图功能中对其进行定义(想象它是一些数据,例如对象的颜色参数,仅从用户侧定期设置,而不经常更新)
雅pp。您需要执行从副本到使用该副本结果的绘图的QFOT(即在传输qf上执行“释放”障碍,然后在信号量上执行“信号量”,然后在图形qf上匹配“获取”障碍)。抽奖之后,您只能(并且应该)仅将Buffer“偷”回传输qf。这可以通过仅使用信号量分隔两个访问来完成。(然后,数据具有未定义的内容,但是无论如何您都不会覆盖它们。尽管如此,信号量仍然必须存在,因此在复制尝试写入Buffer之前,绘制完成了读取。)
如果在飞行中有多次平局,那该如何工作?尽管另一个副本和下一个绘制命令之间有信号量,但传输副本可以在另一个已提交的绘制正在运行的同时完成,这不会引起问题吗?
@whn应用程序应该通过信号量将事情安排得井井有条,以免发生内存危险。如果将内容复制到缓冲区,并且同时有一些绘图可以读取它,则说明同步错误。您需要使用同步序列化这些访问,或者需要使用一些N缓冲。同步两个不同队列的唯一方法是使用信号量(或更安全地使用围栏)。
好的,因此,基本上,如果我想在运行时更改它,则必须使用某种双缓冲方案,否则,请将其插入信号量链(链接到交换链图像的链,交换图像通过信号量链接到我的实际渲染)对(显然,您说的其余内容,不要转移到副本,插入内存屏障从两端转移到副本,在前后同步信号量)?