Skip to content

Commit 523c66d

Browse files
Add Image Copy chapter (#315)
1 parent c9053c8 commit 523c66d

25 files changed

+342
-0
lines changed

README.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc`
236236

237237
* `VK_KHR_shader_atomic_int64`, `VK_EXT_shader_image_atomic_int64`, `VK_EXT_shader_atomic_float`, `VK_EXT_shader_atomic_float2`
238238

239+
== xref:{chapters}image_copies.adoc[Image Copies]
240+
241+
// include::{chapters}image_copies.adoc[]
242+
239243
== xref:{chapters}common_pitfalls.adoc[Common Pitfalls]
240244

241245
// include::{chapters}common_pitfalls.adoc[]

antora/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
** xref:{chapters}subgroups.adoc[]
5353
** xref:{chapters}shader_memory_layout.adoc[]
5454
** xref:{chapters}atomics.adoc[]
55+
** xref:{chapters}image_copies.adoc[]
5556
** xref:{chapters}common_pitfalls.adoc[]
5657
** xref:{chapters}hlsl.adoc[]
5758
** xref:{chapters}high_level_shader_language_comparison.adoc[]

chapters/image_copies.adoc

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright 2025 The Khronos Group, Inc.
2+
// SPDX-License-Identifier: CC-BY-4.0
3+
4+
// Required for both single-page and combined guide xrefs to work
5+
ifndef::chapters[:chapters:]
6+
ifndef::images[:images: images/]
7+
8+
[[image-copies]]
9+
= Image Copies
10+
11+
This is an overview of copying to and from a `VkImage` of various formats.
12+
13+
== Image Copy Permutations
14+
15+
There are 3 main ways to copy to/from a `VkImage`
16+
17+
[options="header"]
18+
|===
19+
| Copy Type | Original (Vulkan 1.0) | `VK_KHR_copy_commands2` (Vulkan 1.3) - Added a missing `pNext` in the structs | `VK_EXT_host_image_copy` (Vulkan 1.4) - allows copies on host without a `VkBuffer` or `VkCommandBuffer`
20+
| Buffer ↔ Image | `vkCmdCopyBufferToImage` | `vkCmdCopyBufferToImage2` | `vkCopyMemoryToImage`
21+
| Image ↔ Buffer | `vkCmdCopyImageToBuffer` | `vkCmdCopyImageToBuffer2` | `vkCopyImageToMemory`
22+
| Image ↔ Image | `vkCmdCopyImage` | `vkCmdCopyImage2` | `vkCopyImageToImage`
23+
|===
24+
25+
== Image Subresource
26+
27+
When you copy an image, you will need to specify an `image subresource` which is used to describe the part of the image being copied.
28+
29+
There are 3 structs used to describe the image subresource
30+
31+
- `VkImageSubresource` - single array layer, singe mip level
32+
- `VkImageSubresourceLayers` - multiple array layers, singe mip level
33+
- `VkImageSubresourceRange` - multiple array layers, multiple mip levels
34+
35+
=== Image Layout is Opaque
36+
37+
The reason you need a subresource of the image is because the image is an opaque object.
38+
When creating an image, the memory is not always going to be tightly packed together.
39+
When dealing with a CPU, you can normally assume a 2D or 3D image is just laid out in as large 1D buffer.
40+
GPU hardware has various memory alignment requirements, and will adjust the memory as required.
41+
42+
The following is a small example to show how two GPU can represent a `VkImage` layout differently.
43+
44+
image::{images}image_copies_buffer_vs_image.svg[image_copies_buffer_vs_image.svg]
45+
46+
=== Mip Levels
47+
48+
When you set `VkImageCreateInfo::mipLevels` you create a mipmap chain.
49+
50+
image::{images}image_copies_miplevels.svg[image_copies_miplevels.svg]
51+
52+
=== Array Layers
53+
54+
Imagine you wanted 4 `VkImage` that were the exact same layout, you would probably represent it like `VkImage my_images[4]`.
55+
Array layers are a way to have this just be represented in a single `VkImage`.
56+
57+
image::{images}image_copies_array_layers.svg[image_copies_array_layers.svg]
58+
59+
=== Aspect Mask
60+
61+
When dealing with something like a depth-stencil format the GPU might need to keep the depth and stencil aspect in different layout. Using the `VkImageAspectFlags` allows you to specify which part to copy.
62+
63+
image::{images}image_copies_depth_stencil.svg[image_copies_depth_stencil.svg]
64+
65+
== Addressing Calculation
66+
67+
When copying between a `VkBuffer`/`VkDeviceMemory` and `VkImage` the data in the non-image might not be tightly packed.
68+
69+
The `VkBufferImageCopy` (or `VkMemoryToImageCopy`) struct provides 3 fields to set where in the buffer to read/write the memory
70+
71+
- `bufferOffset` (where to start)
72+
- `bufferRowLength` (where the extent.y starts)
73+
- `bufferImageHeight` (where the extent.z starts)
74+
75+
[NOTE]
76+
====
77+
Setting all of these to zero means everything is tightly packed in the `VkBuffer`/`VkDeviceMemory`
78+
====
79+
80+
The link:https://docs.vulkan.org/spec/latest/chapters/copies.html#copies-buffers-images[spec addressing formula] is pretty standard, the one thing that can trip you up is that there is no overlapping memory between rows.
81+
In the following example, if you have a `{4,4,1}` image, the `rowExtent` is the `max(bufferRowLength, imageExtent.width)`.
82+
83+
image::{images}image_copies_buffer_row_length.svg[image_copies_buffer_row_length.svg]
84+
85+
== 2D Array and 3D
86+
87+
You are actually able to copy between an array of 2D images and a single 3D image.
88+
89+
Using the following two example `VkImage`
90+
91+
[source,c++]
92+
----
93+
// VkImage "A"
94+
VkImageCreateInfo::imageType = VK_IMAGE_TYPE_2D;
95+
VkImageCreateInfo::extent = {8, 8, 1};
96+
VkImageCreateInfo::arrayLayers = 8;
97+
98+
// VkImage "B"
99+
VkImageCreateInfo::imageType = VK_IMAGE_TYPE_3D;
100+
VkImageCreateInfo::extent = {8, 8, 8};
101+
VkImageCreateInfo::arrayLayers = 1;
102+
----
103+
104+
You can have a copy such as
105+
106+
[source,c++]
107+
----
108+
// Copying image A to B
109+
VkImageCopy copy;
110+
copy.extent = {8, 8, 8};
111+
112+
// 3D
113+
copy.srcSubresource.baseArrayLayer = 0;
114+
copy.srcSubresource.layerCount = 1;
115+
116+
// 2D array
117+
copy.dstSubresource.baseArrayLayer = 0;
118+
copy.dstSubresource.layerCount = 8;
119+
----
120+
121+
where the `extent.depth` is `8`, which is allowed for a 2D image because it has a `layerCount` of `8` to correspond to it.
122+
123+
=== MipLevel difference
124+
125+
You might be thinking what the difference is between a 3D image and 2D image with layers. One main difference is the mipchains they generate.
126+
127+
As an example, let's try to copy `miplevel 1`:
128+
129+
- The 3D extent would be `{4, 4, 4}`
130+
- The 2D extent would be `{4, 4}`, but it still has all 8 layer counts
131+
132+
This means you have to be careful when copying between the two
133+
134+
[source,c++]
135+
----
136+
// Copying image A to B miplevel 1
137+
VkImageCopy copy;
138+
copy.extent = {8, 8, 8};
139+
140+
// 3D
141+
copy.srcSubresource.baseArrayLayer = 0;
142+
copy.srcSubresource.layerCount = 1;
143+
copy.srcSubresource.mipLevel = 1;
144+
145+
// 2D array
146+
copy.dstSubresource.baseArrayLayer = 0;
147+
copy.dstSubresource.layerCount = 4; // matches the miplevel
148+
copy.srcSubresource.mipLevel = 1;
149+
----
150+
151+
== Compressed Image Copies
152+
153+
Dealing with compress images can be a bit tricky, the main thing is to first grasp the terminology of `texel` vs `texel block`
154+
155+
image::{images}image_copies_compressed_terminology.svg[image_copies_compressed_terminology.svg]
156+
157+
[NOTE]
158+
====
159+
Uncompressed formats (ex. `VK_FORMAT_R8G8B8A8_UNORM`), the `texel block` is `{1, 1, 1}` so it is the same a `texel` when using it.
160+
====
161+
162+
The block size, block extent, and other info can be found either in the spec, `vk.xml`, or even link:https://github.com/KhronosGroup/Vulkan-Utility-Libraries/blob/main/include/vulkan/utility/vk_format_utils.h[vk_format_utils.h in Vulkan-Utility-Libraries].
163+
164+
=== Copying Between Compressed and Uncompressed
165+
166+
Copying to and from a `VkBuffer`/`VkDeviceMemory` is straight forward, the `extent` is just the amount of `texels`, so it is the same when you created the image.
167+
168+
image::{images}image_copies_compressed_buffer.svg[image_copies_compressed_buffer.svg]
169+
170+
The tricky part is when you deal with a uncompressed image that has a block extent of `{1, 1, 1}`, here you will set the `VkImageCopy::extent` to match the `srcImage` (link:https://docs.vulkan.org/spec/latest/chapters/formats.html#formats-size-compatibility[details in spec]).
171+
172+
image::{images}image_copies_uncompress_to_compress.svg[image_copies_uncompress_to_compress.svg]
173+
174+
Some initial reactions might be "how are you copying 8 texels into 2?!"
175+
176+
The main things to realize is the "size" of each texel block in the above diagrams are 64-bits. If you try to copy different size blocks, you will get a validation error message.
177+
178+
image::{images}image_copies_mismatch_block_size.svg[image_copies_mismatch_block_size.svg]
179+
180+
==== Offsetting Into Compressed
181+
182+
The `extent`, `srcOffset`, and `dstOffset` are all defined in terms of `texels`. The following shows how to copy a single texel into each of the 3 texel blocks via a different offset.
183+
184+
image::{images}image_copies_dst_offset.svg[image_copies_dst_offset.svg]
185+
186+
=== Partial Texel Block
187+
188+
When using a compressed image, it is possible you might even up with a partially full texel block.
189+
190+
This can be from just setting the original extent that is not a multiple of the texel block extent.
191+
192+
image::{images}image_copies_non_power_of_two.svg[image_copies_non_power_of_two.svg]
193+
194+
This can also occur when you create miplevels.
195+
196+
image::{images}image_copies_block_format_mip.svg[image_copies_block_format_mip.svg]
197+
198+
This can also occur if creating a 1D compressed texture.
199+
200+
image::{images}image_copies_1d_compress.svg[image_copies_1d_compress.svg]
201+
202+
In all these examples, it is important to realize that you copy in terms of `texels` and not `texel blocks`
203+
204+
image::{images}image_copy_partial_texel_block.svg[image_copy_partial_texel_block.svg]
205+
206+
== Multi-Planar
207+
208+
Multi-planar formats are those with `_2PLANE` or `_3PLANE` suffix (xref:{chapters}extensions/VK_KHR_sampler_ycbcr_conversion.adoc[more about VK_KHR_sampler_ycbcr_conversion]).
209+
210+
When copying to and from these images, you do not operate on all format components in the image, but instead, you independently operate only on the format planes explicitly chosen.
211+
212+
Using `VK_FORMAT_G8_B8R8_2PLANE_420_UNORM` as an example, this contains two planes. From the link:https://docs.vulkan.org/spec/latest/chapters/formats.html#formats-compatible-planes[Plane Format Compatibility Table] in the spec (generated from the `vk.xml`) we can see that
213+
214+
* plane 0
215+
** compatible format `VK_FORMAT_R8_UNORM``
216+
** width divisor of `1`
217+
** height divisor of `1`
218+
* plane 1
219+
** compatible format `VK_FORMAT_R8G8_UNORM``
220+
** width divisor of `2`
221+
** height divisor of `2`
222+
223+
What this looks like in code is the following
224+
225+
[source,cpp]
226+
----
227+
VkBufferImageCopy region[2];
228+
region[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
229+
region[0].imageExtent = {width, height, 1};
230+
231+
region[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
232+
region[0].imageExtent = {width / 2, height / 2, 1};
233+
----
234+
235+
image::{images}image_copies_multi_planar.svg[image_copies_multi_planar.svg]

chapters/images/image_copies_1d_compress.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_array_layers.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_block_format_mip.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_buffer_row_length.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_buffer_vs_image.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_compressed_buffer.svg

Lines changed: 4 additions & 0 deletions
Loading

chapters/images/image_copies_compressed_terminology.svg

Lines changed: 4 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)