用于部分转移的Vulkan VkBufferImageCopy

时间:2017-09-30 10:24:42

标签: python image chromium-embedded vulkan

简而言之,我的问题是,当我尝试根据一组脏矩形更新图像时,偏移/尺寸不匹配。

所以,让我们来说明问题。 这里的对象是正确渲染的: Example Figure 1

这来自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) 如需完整副本,则可以使用。

然而,一旦我尝试使用脏的rects,我打印例如这些值:

(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命令,那么应该是否正确?

脏兮兮的开始的第一个元组; topleft点。第二个元组是rect的宽度,高度和深度,最后一个元组是图像和缓冲区的完整大小。

但是,它看起来像这样: https://imgur.com/qoggLz0

偏移是"错误" - 图形的起源跳了起来,我不确定程度。

非常感谢任何帮助。

编辑:有关可能进一步优化的更多信息:

传入数据的处理方式如下:

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获取脏的列表,并在保留队列系列的同时制作一个大的rects。考虑到粒度。

编辑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)

如果此功能出现错误,请将其放在此处。

2 个答案:

答案 0 :(得分:3)

在缓冲区和图像之间进行复制时,您有两组参数。一个描述了图像中感兴趣的位置;这些是由VkBufferImageCopy::image*参数定义的。另一个描述了缓冲区内的感兴趣位置;这些是由VkBufferImageCopy::buffer*参数定义的。

imageExtent对两者都很重要,因为它描述了将传输多少数据。它在图像的空间中这样做,但它也会影响缓冲区内的感兴趣区域。

缓冲区当然不包含图像;它们包含任意数据。因此,您在缓冲区中描述数据的方式与使用图像的方式不同。

在缓冲区中,图像数据紧凑;每个像素与下一个像素直接相邻。并且每个像素元素按其格式定义存储。复制区域的缓冲区部分由3个参数定义。

bufferRowLength是从一行到下一行的像素数。 bufferImageHeight是从一个纹理图层到下一个纹理图层的行数。

这些参数允许您从缓冲区中进行子选择。例如,如果缓冲区逻辑上存储的图像为100x100,而您只想复制前50x50像素,则仍然会提供100x100的bufferRowLength/bufferImageHeight值。

imageExtent会阻止它复制到每个维度的第50个像素。 imageExtent确定从VkImage传输到缓冲区/从缓冲区传输的数据量。因此,如果您将其设置为50x50,那么您将获得所需的一切。

请注意,当我说"首先50x50"像素,我的意思是左上角50x50。如果您想从顶部右侧 50x50进行复制,那就更具挑战性了。

bufferOffset允许您指定图像数据开头的字节偏移量。并且因为您可以单独指定行长度与图像的范围,您可以通过提供50 *元素大小的bufferOffset来实现从右上角50x50的转移。缓冲行长度/高度将与之前相同。

从图像坐标到缓冲区字节地址映射的等式如下:

address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * elementSize;

因此,如果要从缓冲区的左下角50x50进行传输,可以执行此操作。将bufferOffset设置为:

elementSize * (50 * rowLength)

对于右下角50x50,您可以将bufferOffset设置为:

elementSize * ((50 * rowLength) + 50)

但请注意,bufferOffset必须是4的倍数(如果格式不是深度/模板,则为元素大小的倍数)。因此,如果这是一个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

答案 1 :(得分:1)

你所在的地区对我来说有些不一致。 缓冲区的几何结构是什么?

如果它总是全尺寸而您只复制子矩形,那么bufferOffset不应该是0,而是根据topleft设置(并向下舍入以满足其他限制)

这可能是一个常见错误,因此Vulkan规范说:

  

请注意,imageOffset不会影响地址计算   缓冲存储器。相反,bufferOffset 可以用于选择   缓冲存储器中的起始地址。


或者,如果缓冲区实际上只是矩形,则bufferRowLengthbufferImageHeight不应该是fullsize