Readme and blit flag bits check for texture mip chain generation example
This commit is contained in:
parent
b27ad33685
commit
b6cae7b7ee
4 changed files with 189 additions and 12 deletions
17
README.md
17
README.md
|
|
@ -41,15 +41,15 @@ Most basic example. Renders a colored triangle using an indexed vertex buffer. V
|
|||
This example is far more explicit than the other examples and is meant to be a starting point for learning Vulkan from the ground up. Much of the code is boilerplate that you'd usually encapsulate in helper functions and classes (which is what the other examples do).
|
||||
<br><br>
|
||||
|
||||
## [Texture mapping](texture/)
|
||||
## [(Texture mapping) Basic texture mapping](texture/)
|
||||
<img src="./screenshots/basic_texture.png" height="96px" align="right">
|
||||
|
||||
Shows how to upload a 2D texture to video memory for sampling in a shader. Loads a compressed texture into a host visible staging buffer and copies all mip levels to a device local optimal tiled image for best performance.
|
||||
Shows how to upload a 2D texture into video memory for sampling in a shader. Loads a compressed texture into a host visible staging buffer and copies all mip levels to a device local optimal tiled image for best performance.
|
||||
|
||||
Also demonstrates the use of (combined) image samplers. Samplers are detached from the actual texture image and only contain information on how a image is sampled in the shader.
|
||||
Also demonstrates the use of combined image samplers. Samplers are detached from the actual texture image and only contain information on how an image is sampled in the shader.
|
||||
<br><br>
|
||||
|
||||
## [Cubemap texture](texturecubemap/)
|
||||
## [(Texture mapping) Cube maps](texturecubemap/)
|
||||
<img src="./screenshots/texture_cubemap.jpg" height="96px" align="right">
|
||||
|
||||
Building on the basic texture loading example, a cubemap texture is loaded into a staging buffer and is copied over to a device local optimal image using buffer to image copies for all of it's faces and mip maps.
|
||||
|
|
@ -57,13 +57,20 @@ Building on the basic texture loading example, a cubemap texture is loaded into
|
|||
The demo then uses two different pipelines (and shader sets) to display the cubemap as a skybox (background) and as a source for reflections.
|
||||
<br><br>
|
||||
|
||||
## [Texture array](texturearray/)
|
||||
## [(Texture mapping) Texture arrays](texturearray/)
|
||||
<img src="./screenshots/texture_array.png" height="96px" align="right">
|
||||
|
||||
Texture arrays allow storing of multiple images in different layers without any interpolation between the layers.
|
||||
This example demonstrates the use of a 2D texture array with instanced rendering. Each instance samples from a different layer of the texture array.
|
||||
<br><br>
|
||||
|
||||
## [(Texture mapping) Run-time mip-map generation](texturemipmapgen/)
|
||||
<img src="./screenshots/texture_mipmap_gen.jpg" height="96px" align="right">
|
||||
|
||||
Generates a complete mip-chain at runtime (instead of using mip levels stored in texture file) by blitting from one mip level down to the next smaller size until the lower end of the mip chain (1x1 pixels is reached).
|
||||
|
||||
This is done using image blits and proper image memory barriers.
|
||||
|
||||
## [Text overlay (Multi pass)](textoverlay/)
|
||||
<img src="./screenshots/textoverlay.png" height="96px" align="right">
|
||||
|
||||
|
|
|
|||
BIN
screenshots/texture_mipmap_gen.jpg
Normal file
BIN
screenshots/texture_mipmap_gen.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
173
texturemipmapgen/README.md
Normal file
173
texturemipmapgen/README.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# Indirect drawing
|
||||
|
||||
<img src="../screenshots/texture_mipmap_gen.jpg" height="256px">
|
||||
|
||||
## Synopsis
|
||||
|
||||
Generates a complete texture mip-chain at runtime from a base image using image blits and proper image barriers.
|
||||
|
||||
## Requirements
|
||||
To downsample from one mip level to the next, we will be using [```vkCmdBlitImage```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html). This requires the format used to support the ```BLIT_SRC_BIT``` and the ```BLIT_DST_BIT``` flags. If these are not supported, the image format can't be used to blit and you'd either have to choose a different format or use e.g. a compute shader to generate mip levels. The example uses the ```VK_FORMAT_R8G8B8A8_UNORM``` that should support these flags on most implementations.
|
||||
|
||||
***Note:*** Use [```vkGetPhysicalDeviceFormatProperties```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetPhysicalDeviceFormatProperties.html) to check if the format supports the blit flags first.
|
||||
|
||||
## Description
|
||||
|
||||
This examples demonstrates how to generate a complete texture mip-chain at runtime instead of loading offline generated mip-maps from a texture file.
|
||||
|
||||
While usually not applied for textures stored on the disk (that usually have the mips generated offline and stored in the file, see [basic texture mapping example](../texture)) this technique is used textures are generated at runtime, e.g. when doing dynamic cubemaps or other render-to-texture effects.
|
||||
|
||||
Having mip-maps for runtime generated textures offers lots of benefits, both in terms of image stability and performance. Without mip mapping the image will become noisy, especially with high frequency textures (and texture components like specular) and using mip mapping will result in higher performance due to caching.
|
||||
|
||||
Though this example only generates one mip-chain for a single texture at the beginning this technique can also be used during normal frame rendering to generate mip-chains for dynamic textures.
|
||||
|
||||
Some GPUs also offer ```asynchronous transfer queues``` (check for queue families with only the ? ```VK_QUEUE_TRANSFER_BIT``` set) that may be used to speed up such operations.
|
||||
|
||||
## Points of interest
|
||||
|
||||
### Image setup
|
||||
Even though we'll only upload the first mip level initially, we create the image with number of desired mip levels. The following formula is used to calculate the number of mip levels based on the max. image extent:
|
||||
|
||||
```cpp
|
||||
texture.mipLevels = floor(log2(std::max(texture.width, texture.height))) + 1;
|
||||
```
|
||||
|
||||
This is then passed to the image creat info:
|
||||
|
||||
```cpp
|
||||
VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo();
|
||||
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageCreateInfo.format = format;
|
||||
imageCreateInfo.mipLevels = texture.mipLevels;
|
||||
...
|
||||
```
|
||||
|
||||
Setting the number of desired mip levels is necessary as this is used for allocating the right amount of memory for the image (```vkAllocateMemory```).
|
||||
|
||||
### Upload base mip level
|
||||
|
||||
Before generating the mip-chain we need to copy the image data loaded from disk into the newly generated image. This image will be the base for our mip-chain:
|
||||
|
||||
```cpp
|
||||
VkBufferImageCopy bufferCopyRegion = {};
|
||||
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
bufferCopyRegion.imageSubresource.mipLevel = 0;
|
||||
bufferCopyRegion.imageExtent.width = texture.width;
|
||||
bufferCopyRegion.imageExtent.height = texture.height;
|
||||
bufferCopyRegion.imageExtent.depth = 1;
|
||||
|
||||
vkCmdCopyBufferToImage(copyCmd, stagingBuffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
|
||||
```
|
||||
|
||||
### Prepare base mip level
|
||||
As we are going to blit ***from*** the base mip-level just uploaded we also need to set insert an image memory barrier that sets the image layout to ```TRANSFER_SRC``` for the base mip level:
|
||||
|
||||
```cpp
|
||||
VkImageSubresourceRange subresourceRange = {};
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
subresourceRange.levelCount = 1;
|
||||
subresourceRange.layerCount = 1;
|
||||
|
||||
vkTools::setImageLayout(
|
||||
copyCmd,
|
||||
texture.image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
subresourceRange);
|
||||
```
|
||||
|
||||
### Generating the mip-chain
|
||||
There are two different ways of generating the mip-chain. The first one is to blit down the whole mip-chain from level n-1 to n, the other way would be to always use the base image and blit down from that to all levels. This example uses the first one.
|
||||
|
||||
***Note:*** Blitting (same for copying) images is done inside of a command buffer that has to be submitted and as such has to be synchronized before using the new image with e.g. a ```vkFence```.
|
||||
|
||||
We simply loop over all remaining mip levels (level 0 was loaded from disk) and prepare a ```VkImageBlit``` structure for each blit from mip level i-1 to level i.
|
||||
|
||||
First the source for out blit. This is the previous mip level. The dimensions of the blit source are specified by srcOffset:
|
||||
```cpp
|
||||
for (int32_t i = 1; i < texture.mipLevels; i++)
|
||||
{
|
||||
VkImageBlit imageBlit{};
|
||||
|
||||
// Source
|
||||
imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
imageBlit.srcSubresource.layerCount = 1;
|
||||
imageBlit.srcSubresource.mipLevel = i-1;
|
||||
imageBlit.srcOffsets[1].x = int32_t(texture.width >> (i - 1));
|
||||
imageBlit.srcOffsets[1].y = int32_t(texture.height >> (i - 1));
|
||||
imageBlit.srcOffsets[1].z = 1;
|
||||
```
|
||||
Setup for the destination mip level (1), with the dimensions for the blit destination specified in dstOffsets[1]:
|
||||
```cpp
|
||||
// Destination
|
||||
imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
imageBlit.dstSubresource.layerCount = 1;
|
||||
imageBlit.dstSubresource.mipLevel = i;
|
||||
imageBlit.dstOffsets[1].x = int32_t(texture.width >> i);
|
||||
imageBlit.dstOffsets[1].y = int32_t(texture.height >> i);
|
||||
imageBlit.dstOffsets[1].z = 1;
|
||||
```
|
||||
|
||||
Before we can blit to this mip level, we need to transition it's image layout to ```TRANSFER_DST```:
|
||||
```cpp
|
||||
VkImageSubresourceRange mipSubRange = {};
|
||||
mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
mipSubRange.baseMipLevel = i;
|
||||
mipSubRange.levelCount = 1;
|
||||
mipSubRange.layerCount = 1;
|
||||
|
||||
// Transiton current mip level to transfer dest
|
||||
vkTools::setImageLayout(
|
||||
blitCmd,
|
||||
texture.image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
mipSubRange);
|
||||
```
|
||||
Note that we set the ```baseMipLevel``` member of the subresource range so the image memory barrier will only affect the one mip level we want to copy to.
|
||||
|
||||
Now that the mip level we want to copy from and the one we'll copy to have are in the proper layout (transfer source and destination) we can issue the [```vkCmdBlitImage```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html) to copy from mip level (i-1) to mip level (i):
|
||||
|
||||
```cpp
|
||||
vkCmdBlitImage(
|
||||
blitCmd,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
&imageBlit,
|
||||
VK_FILTER_LINEAR);
|
||||
```
|
||||
```vkCmdBlitImage``` does the (down) scaling from mip level (i-1) to mip level (i) using a linear filter.
|
||||
|
||||
After the blit is done we can use this mip level as a base for the next level, so we transition the layout from ```TRANSFER_DST_OPTIMAL``` to ```TRANSFER_SRC_OPTIMAL``` so we can use this level as transfer source for the next level:
|
||||
|
||||
```cpp
|
||||
vkTools::setImageLayout(
|
||||
blitCmd,
|
||||
texture.image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
mipSubRange);
|
||||
}
|
||||
```
|
||||
|
||||
### Final image layout transitions
|
||||
Once the loop is done we need to transition all mip levels of the image to their actual usage layout, which is ```SHADER_READ``` for this example. Note that after the loop all levels will be in the ```TRANSER_SRC``` layout allowing us to transfer the whole image at once:
|
||||
|
||||
```cpp
|
||||
subresourceRange.levelCount = texture.mipLevels;
|
||||
vkTools::setImageLayout(
|
||||
blitCmd,
|
||||
texture.image,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;,
|
||||
subresourceRange);
|
||||
```
|
||||
|
||||
Submitting that command buffer will result in an image with a complete mip-chain and all mip levels being transitioned to the proper image layout for shader reads.
|
||||
|
|
@ -141,7 +141,9 @@ public:
|
|||
// Get device properites for the requested texture format
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
|
||||
|
||||
// todo check blit flags
|
||||
// Mip-chain generation requires support for blit source and destination
|
||||
assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT);
|
||||
assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
|
||||
|
||||
VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo();
|
||||
VkMemoryRequirements memReqs = {};
|
||||
|
|
@ -228,18 +230,13 @@ public:
|
|||
|
||||
// Generate the mip chain
|
||||
// ---------------------------------------------------------------
|
||||
// We copy down the whole mip chain doint a blit from mip-1 to mip
|
||||
// We copy down the whole mip chain doing a blit from mip-1 to mip
|
||||
// An alternative way would be to always blit from the first mip level and sample that one down
|
||||
// todo: comment
|
||||
|
||||
VkCommandBuffer blitCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
// Copy down mips from n-1 to n
|
||||
for (int32_t i = 1; i < texture.mipLevels; i++)
|
||||
{
|
||||
int32_t mipWidth = texture.width >> i;
|
||||
int32_t mipHeight = texture.height >> i;
|
||||
|
||||
VkImageBlit imageBlit{};
|
||||
|
||||
// Source
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue