用于部分传输的 Vulkan VkBufferImageCopy
Vulkan VkBufferImageCopy for partial transfer
简而言之,我的问题是当我尝试根据一组 offset/size 不匹配的脏矩形更新图像时。
所以,让我们展示一下问题。
这是正确渲染的对象:
这来自 Chromium Embedded Framework 并通过更新整个图像得到正确呈现 - 这通常是不必要的,CEF 为您提供了一个已更改且需要更新的矩形列表。
完整副本成功实现:
def copyBuffertoImageRegion(self, topleft, size, fullsize):
print(topleft, size, fullsize)
with CmdBuffer(self.interface, True) as cmdbuffers:
region = VkBufferImageCopy(
bufferOffset=0,
bufferRowLength=fullsize[0],
bufferImageHeight=fullsize[1],
imageSubresource=self.subresource,
imageExtent=size,
imageOffset=topleft
)
vkCmdCopyBufferToImage(
cmdbuffers[0],
self.staging.buffer,
self.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
region
)
这种情况下的打印结果为 (0,0,0) (1920, 1080, 1) (1920, 1080)
对于完整副本,这有效。
但是,一旦我尝试使用脏矩形,我就会打印这些值:
(88, 88, 0) (120, 120, 1) (1920, 1080)
(88, 88, 0) (120, 120, 1) (1920, 1080)
(88, 96, 0) (120, 120, 1) (1920, 1080)
(72, 80, 0) (152, 136, 1) (1920, 1080)
(72, 80, 0) (152, 136, 1) (1920, 1080)
(80, 80, 0) (144, 136, 1) (1920, 1080)
(80, 80, 0) (136, 136, 1) (1920, 1080)
(80, 88, 0) (152, 144, 1) (1920, 1080)
(88, 88, 0) (152, 144, 1) (1920, 1080)
如果我正确理解了 VkBufferImageCopy 命令,应该 正确吗?
脏矩形开始的第一个元组;左上点。第二个元组是矩形的宽度、高度和深度,最后一个元组是图像和缓冲区的完整大小。
但是,它看起来像这样:
https://imgur.com/qoggLz0
偏移量是 "wrong" - 图形的原点跳来跳去,我不确定范围。
如有任何帮助,我们将不胜感激。
编辑:可能进一步优化的更多信息:
传入数据的处理方式如下:
def fill(self, pointer, rects):
rect = self.combine_rects(rects)
ffi.memmove(self.mappedhostmemory, pointer, self.buffer.size)
with self.interface.main_lock:
self.image.transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
self.image.copyBuffertoImageRegion(*rect,self.interface.resolution)
self.image.transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
指针是这个指针:
https://github.com/cztomczak/cefpython/blob/master/api/PaintBuffer.md#getintpointer
并且 mappedhostmemory 是我保持映射的暂存缓冲区的映射内存。
所以,如果我能更多地限制 CEF 指针 -> vulkan 上传,甚至不抓取整个纹理并将其放入缓冲区,而是实际上只抓取脏部分,那就更好了。
有关系吗? combine_rects 获取脏 rect 列表,并将其变大,同时牢记队列系列的粒度。
编辑 2:
多亏了到目前为止的答案,越来越近了(谢谢!),但还没有完全解决:
https://imgur.com/a/Q7tAR
它仍然跳来跳去,但至少它不再是数据沙拉了。
这是通过设置
实现的
bufferOffset = (topleft[0]*fullsize[0]+topleft[1])*4
在这种情况下,我不需要确保它是 4 的倍数,因为它(至少在我的计算机上,稍后会为一般情况修复)的图像粒度为 (8,8 ,8).
def combine_rects(self, rects):
#start rect is *resolution, 0, 0
left, up, width, height = self.start_rect
for rect in rects:
rleft, rup, rwidth, rheight = rect
left = min(left, rleft)
up = min(up, rup)
width = max(width, rwidth)
height = max(height, rheight)
if width == self.interface.resolution[0] or height == self.interface.resolution[1]:
#issue full copy
return (0, 0, 0), (*self.interface.resolution, 1)
if self.granularity_important:#granularity != (1,1,1)
left = (left // self.granularity_x) * self.granularity_x
up = (up // self.granularity_y) * self.granularity_y
#safety buffer, as we may remove up to granularity-1 texels
width += self.granularity_x
height += self.granularity_y
width = width if width % self.granularity_x == 0 else width + self.granularity_x - width % self.granularity_x
height = height if height % self.granularity_y == 0 else height + self.granularity_y - height % self.granularity_y
return (left, up, 0), (width, height, 1)
如果这个函数有错误,把它放在这里。
我觉得你所在的地区有点不一致。
您的缓冲区的几何形状是什么?
如果它总是全尺寸的,而你只从它复制子矩形,那么 bufferOffset
不应该是 0
而是根据 topleft
设置(并向下舍入以满足其他限制)。
这可能是一个常见的错误,所以 Vulkan 规范说:
Note that imageOffset
does not affect addressing calculations for
buffer memory. Instead, bufferOffset
can be used to select the
starting address in buffer memory.
或者如果缓冲区实际上只是矩形,那么 bufferRowLength
和 bufferImageHeight
不应该是 fullsize
.
在缓冲区和图像之间进行复制时,您有两组参数。一个描述图像中感兴趣的位置;这些由 VkBufferImageCopy::image*
参数定义。另一个描述缓冲区内感兴趣的位置;这些由 VkBufferImageCopy::buffer*
参数定义。
imageExtent
对两者都很重要,因为它描述了将传输多少数据。它在图像的 space 中这样做,但它也会影响缓冲区内的感兴趣区域。
缓冲区当然不包含图像;它们包含任意数据。因此,您描述缓冲区中数据的方式与您描述图像的方式不同。
在缓冲区中,图像数据是紧密打包的;每个像素直接相邻。每个像素元素都按照其格式定义进行存储。复制区域的缓冲区部分由3个参数定义。
bufferRowLength
是从一行到下一行的像素数。 bufferImageHeight
是从一个纹理层到下一个纹理层的行数。
这些参数允许您从缓冲区中进行子选择。例如,如果您的缓冲区在逻辑上存储了一张 100x100 的图像,而您只想复制前 50x50 像素,您仍然提供 bufferRowLength/bufferImageHeight
个 100x100 的值。
imageExtent
会阻止它复制超过每个维度的第 50 个像素。 imageExtent
确定有多少数据被传输 from/to VkImage
to/from 缓冲区。因此,如果您将此设置为 50x50,您将获得所需的内容。
请注意,当我说 "first 50x50" 像素时,我指的是左上角的 50x50。如果你想从顶部复制-右边 50x50,那就有点挑战了。
bufferOffset
允许您指定图像数据开始的字节偏移量。并且因为您可以从图像的范围中单独指定行长度,所以您可以通过提供 50 * 元素大小的 bufferOffset
来实现传输 from/to 右上角的 50x50。缓冲区行 length/height 将与以前相同。
从图像坐标到缓冲区字节地址的映射方程如下:
address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * elementSize;
所以,如果你想转移 from/to 缓冲区左下角的 50x50,你可以这样做。将 bufferOffset
设置为:
elementSize * (50 * rowLength)
对于右下角的 50x50,您可以将 bufferOffset
设置为:
elementSize * ((50 * rowLength) + 50)
但是请注意,bufferOffset
必须是 4 的倍数(如果格式不是 depth/stencil,则必须是元素大小的倍数)。所以如果这是 R8 格式,这将不起作用,因为 50 不是 4 的倍数。
这也适用于 3D 图层副本。 bufferImageHeight
指定要跳过多少行才能到达下一层。
所以,要做你感兴趣的事情,你需要以下内容(注意:我不知道 Python,所以我只是在猜测语法):
bufferOffset = VkDeviceSize(((fullsize[0] * topleft.y) + topleft.x) * elementSize)
region = VkBufferImageCopy(
bufferOffset=bufferOffset ,
bufferRowLength=fullsize[0],
bufferImageHeight=fullsize[1],
imageSubresource=self.subresource,
imageExtent=size,
imageOffset=topleft
)
您需要根据相关格式计算 elementSize
。此外,上述代码仅适用于 2D 副本;对于 3D 副本,您还需要考虑 topleft.y
。
简而言之,我的问题是当我尝试根据一组 offset/size 不匹配的脏矩形更新图像时。
所以,让我们展示一下问题。
这是正确渲染的对象:
这来自 Chromium Embedded Framework 并通过更新整个图像得到正确呈现 - 这通常是不必要的,CEF 为您提供了一个已更改且需要更新的矩形列表。
完整副本成功实现:
def copyBuffertoImageRegion(self, topleft, size, fullsize):
print(topleft, size, fullsize)
with CmdBuffer(self.interface, True) as cmdbuffers:
region = VkBufferImageCopy(
bufferOffset=0,
bufferRowLength=fullsize[0],
bufferImageHeight=fullsize[1],
imageSubresource=self.subresource,
imageExtent=size,
imageOffset=topleft
)
vkCmdCopyBufferToImage(
cmdbuffers[0],
self.staging.buffer,
self.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
region
)
这种情况下的打印结果为 (0,0,0) (1920, 1080, 1) (1920, 1080) 对于完整副本,这有效。
但是,一旦我尝试使用脏矩形,我就会打印这些值:
(88, 88, 0) (120, 120, 1) (1920, 1080)
(88, 88, 0) (120, 120, 1) (1920, 1080)
(88, 96, 0) (120, 120, 1) (1920, 1080)
(72, 80, 0) (152, 136, 1) (1920, 1080)
(72, 80, 0) (152, 136, 1) (1920, 1080)
(80, 80, 0) (144, 136, 1) (1920, 1080)
(80, 80, 0) (136, 136, 1) (1920, 1080)
(80, 88, 0) (152, 144, 1) (1920, 1080)
(88, 88, 0) (152, 144, 1) (1920, 1080)
如果我正确理解了 VkBufferImageCopy 命令,应该 正确吗?
脏矩形开始的第一个元组;左上点。第二个元组是矩形的宽度、高度和深度,最后一个元组是图像和缓冲区的完整大小。
但是,它看起来像这样: https://imgur.com/qoggLz0
偏移量是 "wrong" - 图形的原点跳来跳去,我不确定范围。
如有任何帮助,我们将不胜感激。
编辑:可能进一步优化的更多信息:
传入数据的处理方式如下:
def fill(self, pointer, rects):
rect = self.combine_rects(rects)
ffi.memmove(self.mappedhostmemory, pointer, self.buffer.size)
with self.interface.main_lock:
self.image.transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
self.image.copyBuffertoImageRegion(*rect,self.interface.resolution)
self.image.transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
指针是这个指针: https://github.com/cztomczak/cefpython/blob/master/api/PaintBuffer.md#getintpointer
并且 mappedhostmemory 是我保持映射的暂存缓冲区的映射内存。
所以,如果我能更多地限制 CEF 指针 -> vulkan 上传,甚至不抓取整个纹理并将其放入缓冲区,而是实际上只抓取脏部分,那就更好了。
有关系吗? combine_rects 获取脏 rect 列表,并将其变大,同时牢记队列系列的粒度。
编辑 2: 多亏了到目前为止的答案,越来越近了(谢谢!),但还没有完全解决: https://imgur.com/a/Q7tAR
它仍然跳来跳去,但至少它不再是数据沙拉了。 这是通过设置
实现的bufferOffset = (topleft[0]*fullsize[0]+topleft[1])*4
在这种情况下,我不需要确保它是 4 的倍数,因为它(至少在我的计算机上,稍后会为一般情况修复)的图像粒度为 (8,8 ,8).
def combine_rects(self, rects):
#start rect is *resolution, 0, 0
left, up, width, height = self.start_rect
for rect in rects:
rleft, rup, rwidth, rheight = rect
left = min(left, rleft)
up = min(up, rup)
width = max(width, rwidth)
height = max(height, rheight)
if width == self.interface.resolution[0] or height == self.interface.resolution[1]:
#issue full copy
return (0, 0, 0), (*self.interface.resolution, 1)
if self.granularity_important:#granularity != (1,1,1)
left = (left // self.granularity_x) * self.granularity_x
up = (up // self.granularity_y) * self.granularity_y
#safety buffer, as we may remove up to granularity-1 texels
width += self.granularity_x
height += self.granularity_y
width = width if width % self.granularity_x == 0 else width + self.granularity_x - width % self.granularity_x
height = height if height % self.granularity_y == 0 else height + self.granularity_y - height % self.granularity_y
return (left, up, 0), (width, height, 1)
如果这个函数有错误,把它放在这里。
我觉得你所在的地区有点不一致。 您的缓冲区的几何形状是什么?
如果它总是全尺寸的,而你只从它复制子矩形,那么 bufferOffset
不应该是 0
而是根据 topleft
设置(并向下舍入以满足其他限制)。
这可能是一个常见的错误,所以 Vulkan 规范说:
Note that
imageOffset
does not affect addressing calculations for buffer memory. Instead,bufferOffset
can be used to select the starting address in buffer memory.
或者如果缓冲区实际上只是矩形,那么 bufferRowLength
和 bufferImageHeight
不应该是 fullsize
.
在缓冲区和图像之间进行复制时,您有两组参数。一个描述图像中感兴趣的位置;这些由 VkBufferImageCopy::image*
参数定义。另一个描述缓冲区内感兴趣的位置;这些由 VkBufferImageCopy::buffer*
参数定义。
imageExtent
对两者都很重要,因为它描述了将传输多少数据。它在图像的 space 中这样做,但它也会影响缓冲区内的感兴趣区域。
缓冲区当然不包含图像;它们包含任意数据。因此,您描述缓冲区中数据的方式与您描述图像的方式不同。
在缓冲区中,图像数据是紧密打包的;每个像素直接相邻。每个像素元素都按照其格式定义进行存储。复制区域的缓冲区部分由3个参数定义。
bufferRowLength
是从一行到下一行的像素数。 bufferImageHeight
是从一个纹理层到下一个纹理层的行数。
这些参数允许您从缓冲区中进行子选择。例如,如果您的缓冲区在逻辑上存储了一张 100x100 的图像,而您只想复制前 50x50 像素,您仍然提供 bufferRowLength/bufferImageHeight
个 100x100 的值。
imageExtent
会阻止它复制超过每个维度的第 50 个像素。 imageExtent
确定有多少数据被传输 from/to VkImage
to/from 缓冲区。因此,如果您将此设置为 50x50,您将获得所需的内容。
请注意,当我说 "first 50x50" 像素时,我指的是左上角的 50x50。如果你想从顶部复制-右边 50x50,那就有点挑战了。
bufferOffset
允许您指定图像数据开始的字节偏移量。并且因为您可以从图像的范围中单独指定行长度,所以您可以通过提供 50 * 元素大小的 bufferOffset
来实现传输 from/to 右上角的 50x50。缓冲区行 length/height 将与以前相同。
从图像坐标到缓冲区字节地址的映射方程如下:
address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * elementSize;
所以,如果你想转移 from/to 缓冲区左下角的 50x50,你可以这样做。将 bufferOffset
设置为:
elementSize * (50 * rowLength)
对于右下角的 50x50,您可以将 bufferOffset
设置为:
elementSize * ((50 * rowLength) + 50)
但是请注意,bufferOffset
必须是 4 的倍数(如果格式不是 depth/stencil,则必须是元素大小的倍数)。所以如果这是 R8 格式,这将不起作用,因为 50 不是 4 的倍数。
这也适用于 3D 图层副本。 bufferImageHeight
指定要跳过多少行才能到达下一层。
所以,要做你感兴趣的事情,你需要以下内容(注意:我不知道 Python,所以我只是在猜测语法):
bufferOffset = VkDeviceSize(((fullsize[0] * topleft.y) + topleft.x) * elementSize)
region = VkBufferImageCopy(
bufferOffset=bufferOffset ,
bufferRowLength=fullsize[0],
bufferImageHeight=fullsize[1],
imageSubresource=self.subresource,
imageExtent=size,
imageOffset=topleft
)
您需要根据相关格式计算 elementSize
。此外,上述代码仅适用于 2D 副本;对于 3D 副本,您还需要考虑 topleft.y
。