7

As my question title says, I want update texture for every frame.

I got an idea : create a VkImage as a texture buffer with bellow configurations :
initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED
usage= VK_IMAGE_USAGE_SAMPLED_BIT

and it's memory type is VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT

In draw loop :

first frame :

  1. map texure data to VkImage(use vkMapMemory).
  2. change VkImage layout from VK_IMAGE_LAYOUT_PREINITIALIZED to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
  3. use this VkImage as texture buffer.

second frame:

The layout will be VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL after the first frame , can I map next texure data to this VkImage directly without change it's layout ? if I can not do that, which layout can I change this VkImage to ?

In vkspec 11.4 it says :

The new layout used in a transition must not be VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_PREINITIALIZED

So , I can not change layout back to _PREINITIALIZED .
Any help will be appreciated.

plasmacel
  • 8,183
  • 7
  • 53
  • 101
libei
  • 93
  • 1
  • 6

2 Answers2

14

For your case you do not need LAYOUT_PREINITIALIZED. That would only complicate your code (forcing you to provide separate code for the first frame).

LAYOUT_PREINITIALIZED is indeed a very special layout intended only for the start of the life of the Image. It is more useful for static textures.

Start with LAYOUT_UNDEFINED and use LAYOUT_GENERAL when you need to write the Image from CPU side.

I propose this scheme:

berfore render loop

  1. Create your VkImage with UNDEFINED

1st to Nth frame (aka render loop)

  1. Transition image to GENERAL
  2. Synchronize (likely with VkFence)
  3. Map the image, write it, unmap it (weell, the mapping and unmaping can perhaps be outside render loop)
  4. Synchronize (potentially done implicitly)
  5. Transition image to whatever layout you need next
  6. Do your rendering and whatnot
  7. start over at 1.

It is a naive implementation but should suffice for ordinary hobbyist uses.

Double buffered access can be implemented — that is e.g. VkBuffer for CPU access and VkImage of the same for GPU access. And VkCmdCopy* must be done for the data hand-off.

It is not that much more complicated than the above approach and there can be some performance benefits (if you need those at your stage of your project). You usually want your resources in device local memory, which often is not also host visible.

It would go something like:

berfore render loop

  1. Create your VkBuffer b with UNDEFINED backed by HOST_VISIBLE memory and map it
  2. Create your VkImage i with UNDEFINED backed by DEVICE_LOCAL memory
  3. Prepare your synchronization primitives between i and b: E.g. two Semaphores, or Events could be used or Barriers if the transfer is in the same queue

1st to Nth frame (aka render loop)

Operations on b and i can be pretty detached (even can be on different queues) so:

For b:

  1. Transition b to GENERAL
  2. Synchronize to CPU (likely waiting on VkFence or vkQueueIdle)
  3. invalidate(if non-coherent), write it, flush(if non-coherent)
  4. Synchronize to GPU (done implicitly if 3. before queue submission)
  5. Transition b to TRANSFER
  6. Synchronize to make sure i is not in use (likely waiting on a VkSemaphore)
  7. Transition i to TRANSFER
  8. Do vkCmdCopy* from b to i
  9. Synchronize to make known I am finished with i (likely signalling a VkSemaphore)
  10. start over at 1.

(The fence at 2. and semaphore at 6. have to be pre-signalled or skipped for first frame to work)

For i:

  1. Synchronize to make sure i is free to use (likely waiting on a VkSemaphore)
  2. Transition i to whatever needed
  3. Do your rendering
  4. Synchronize to make known I am finished with i (likely signalling a VkSemaphore)
  5. start over at 1.
krOoze
  • 12,301
  • 1
  • 20
  • 34
  • Thanks, I understand your single buffer scheme now, I can use `VkImageMemoryBarrier` to synchronize when to change layout. But as double buffers( that is the way I want to use), I do not know how to synchronize data copying when the next frame comes. Besides , double buffers means twice memory copy(cup `memcpy` and gpu `vkCmdCpy*` ) ,my frames come from video decoder , it updates frequently, Are you sure double buffers have more performance than single buffer in my case? – libei Nov 14 '16 at 02:53
  • @libei: Well, single buffering means *stopping the CPU entirely* until the GPU renders the frame. So unless you have plenty of CPU time lying around, double buffering is usually a reasonable performance-vs-memory tradeoff. It's the exact same reason why swap chains are usually buffered: so that the presentation system can read from one, while you render to another. – Nicol Bolas Nov 14 '16 at 03:31
  • @NicolBolas :Yes, I agree with you. Actually, my cpu take about 23ms to product a new texture data. – libei Nov 14 '16 at 09:18
  • @libei you could build your data in the mem-mapped pointer to avoid the `memcpy` if possible. Using the host visible memory directly by the GPU may in of itself be bad (it is often on the CPU and so the copy has to be done implicitly anyway). ;; Let's work out similar scheme for the DB case then.... – krOoze Nov 14 '16 at 09:21
  • @NicolBolas Not really, you can still do other work on the CPU, can't you? Just not with the single-buffered resource. – krOoze Nov 14 '16 at 09:21
5

You have a number of problems here.

First:

create a VkImage as a texture buffer

There's no such thing. The equivalent of an OpenGL buffer texture is a Vulkan buffer view. This does not use a VkImage of any sort. VkBufferViews do not have an image layout.

Second, assuming that you are working with a VkImage of some sort, you have recognized the layout problem. You cannot modify the memory behind the texture unless the texture is in the GENERAL layout (among other things). So you have to force a transition to that, wait until the transition command has actually completed execution, then do your modifications.

Third, Vulkan is asynchronous in its execution, and unlike OpenGL, it will not hide this from you. The image in question may still be accessed by the shader when you want to change it. So usually, you need to double buffer these things.

On frame 1, you set the data for image 1, then render with it. On frame 2, you set the data for image 2, then render with it. On frame 3, you overwrite the data for image 1 (using events to ensure that the GPU has actually finished frame 1).

Alternatively, you can use double-buffering without possible CPU waiting, by using staging buffers. That is, instead of writing to images directly, you write to host-visible memory. Then you use a vkCmdCopyBufferToImage command to copy that data into the image. This way, the CPU doesn't have to wait on events or fences to make sure that the image is in the GENERAL layout before sending data.

And BTW, Vulkan is not OpenGL. Mapping of memory is always persistent; there's no reason to unmap a piece of memory if you're going to map it every frame.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks, I learn more details about vulkan from your answer. Image 1 and image 2 should specify in `VkDescriptorSetLayoutBinding`? or you just mean there exist a image 3 that is specified in `VkDescriptorSetLayoutBinding`and with gpu local memoty? – libei Nov 14 '16 at 03:27
  • To modify the memory behind a texture doesn't also require the image to be created with `VK_IMAGE_TILING_LINEAR` (besides being in layout `GENERAL`)? Though `VK_IMAGE_LAYOUT_PREINITIALIZED` implies that, still it is worth mentioning. – plasmacel Jan 04 '21 at 17:11