init
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2019-2024, Sascha Willems
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 the "License";
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Texture mipmap generation"
|
||||
DESCRIPTION "Generate mipmaps for a texture"
|
||||
SHADER_FILES_GLSL
|
||||
"texture_mipmap_generation/glsl/texture.vert"
|
||||
"texture_mipmap_generation/glsl/texture.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"texture_mipmap_generation/hlsl/texture.vert.hlsl"
|
||||
"texture_mipmap_generation/hlsl/texture.frag.hlsl")
|
||||
@@ -0,0 +1,266 @@
|
||||
////
|
||||
- Copyright (c) 2019-2023, Sascha Willems
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
////
|
||||
= Run-time mip-map generation
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/texture_mipmap_generation[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Generates a complete texture mip-chain at runtime from a base image using image blits and proper image barriers.
|
||||
|
||||
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) this technique is often used for dynamic textures like cubemaps for reflections 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` that may be used for doing such operations in the background.
|
||||
To detect this, check for queue families with only the `VK_QUEUE_TRANSFER_BIT` set.
|
||||
|
||||
== Comparison
|
||||
|
||||
Without mip mapping:
|
||||
|
||||
image::./images/mip_mapping_off.jpg[Off,512px]
|
||||
|
||||
Using mip mapping with a bilinear filter:
|
||||
|
||||
image:./images/mip_mapping_bilinear.jpg[Bilinear,512px]
|
||||
|
||||
Using mip mapping with an anisotropic filter:
|
||||
|
||||
image:./images/mip_mapping_anisotropic.jpg[Anisotropic,512px]
|
||||
|
||||
== Requirements
|
||||
|
||||
To downsample from one mip level to the next, we will be using https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vkCmdBlitImage`].
|
||||
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 a custom shader to generate mip levels.
|
||||
The example uses the `VK_FORMAT_R8G8B8A8_SRGB` that should support these flags on most implementations.
|
||||
|
||||
*_Note:_* Use https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetPhysicalDeviceFormatProperties.html[`vkGetPhysicalDeviceFormatProperties`] to check if the format supports the blit flags first.
|
||||
|
||||
== 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.mip_levels = static_cast<uint32_t>(floor(log2(std::max(texture.width, texture.height))) + 1);
|
||||
----
|
||||
|
||||
This is then passed to the image creat info:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCreateInfo image_create_info = vkb::initializers::image_create_info();
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = format;
|
||||
image_create_info.mipLevels = texture.mip_levels;
|
||||
----
|
||||
|
||||
Setting the number of desired mip levels is necessary as this is used for allocating the correct amount of memory required 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 buffer_copy_region = {};
|
||||
buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
buffer_copy_region.imageSubresource.mipLevel = 0;
|
||||
buffer_copy_region.imageSubresource.baseArrayLayer = 0;
|
||||
buffer_copy_region.imageSubresource.layerCount = 1;
|
||||
buffer_copy_region.imageExtent.width = texture.width;
|
||||
buffer_copy_region.imageExtent.height = texture.height;
|
||||
buffer_copy_region.imageExtent.depth = 1;
|
||||
vkCmdCopyBufferToImage(copy_command, staging_buffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
|
||||
----
|
||||
|
||||
=== Prepare base mip level
|
||||
|
||||
As we are going to blit *_from_* the base mip-level just uploaded we also need to insert an image memory barrier that transitions the image layout to `TRANSFER_SRC` for the base mip level:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
copy_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1});
|
||||
----
|
||||
|
||||
=== 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 our blit.
|
||||
This is the previous mip level.
|
||||
The dimensions of the blit source are specified by srcOffset:
|
||||
// {% raw %}
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
for (int32_t i = 1; i < texture.mipLevels; i++)
|
||||
{
|
||||
VkImageBlit image_blit{};
|
||||
|
||||
// Source
|
||||
image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.srcSubresource.layerCount = 1;
|
||||
image_blit.srcSubresource.mipLevel = i - 1;
|
||||
image_blit.srcOffsets[1].x = int32_t(texture.width >> (i - 1));
|
||||
image_blit.srcOffsets[1].y = int32_t(texture.height >> (i - 1));
|
||||
image_blit.srcOffsets[1].z = 1;
|
||||
}
|
||||
----
|
||||
|
||||
// {% endraw %}
|
||||
|
||||
Setup for the destination mip level (1), with the dimensions for the blit destination specified in dstOffsets[1]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Destination
|
||||
image_blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.dstSubresource.layerCount = 1;
|
||||
image_blit.dstSubresource.mipLevel = i;
|
||||
image_blit.dstOffsets[1].x = int32_t(texture.width >> i);
|
||||
image_blit.dstOffsets[1].y = int32_t(texture.height >> i);
|
||||
image_blit.dstOffsets[1].z = 1;
|
||||
----
|
||||
|
||||
Before we can blit to this mip level, we need to transition it's image layout to `TRANSFER_DST`:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Prepare current mip level as image blit destination
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
----
|
||||
|
||||
Note that we set the `baseMipLevel` of the subresource range to `i`, 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 are in the proper layout (transfer source and destination) we can issue the https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html[`vkCmdBlitImage`] to copy from mip level (i-1) to mip level (i):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkCmdBlitImage(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
&image_blit,
|
||||
VK_FILTER_LINEAR);
|
||||
----
|
||||
|
||||
`vkCmdBlitImage` does the down sampling from mip level (i-1) to mip level (i) using a linear filter, if you need better or more advanced filtering for this you need to resort to using custom shaders for generating the mip chain instead of blitting.
|
||||
|
||||
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]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
}
|
||||
----
|
||||
|
||||
=== 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 above all levels will be in the `TRANSER_SRC` layout allowing us to transfer the whole image with a single barrier:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkb::insert_image_memory_barrier(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, texture.mip_levels, 0, 1});
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
=== Image View creation
|
||||
|
||||
The Image View also requires information about how many Mip Levels are used.
|
||||
This is specified in the `VkImageViewCreateInfo.subresourceRange.levelCount` field.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageViewCreateInfo view = vkb::initializers::image_view_create_info();
|
||||
view.image = texture.image;
|
||||
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view.format = format;
|
||||
view.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};
|
||||
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
view.subresourceRange.baseMipLevel = 0;
|
||||
view.subresourceRange.baseArrayLayer = 0;
|
||||
view.subresourceRange.layerCount = 1;
|
||||
view.subresourceRange.levelCount = texture.mip_levels;
|
||||
VK_CHECK(vkCreateImageView(device->get_handle(), &view, nullptr, &texture.view));
|
||||
----
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
@@ -0,0 +1,632 @@
|
||||
/* Copyright (c) 2019-2025, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Runtime mip map generation
|
||||
*/
|
||||
|
||||
#include "texture_mipmap_generation.h"
|
||||
|
||||
TextureMipMapGeneration::TextureMipMapGeneration()
|
||||
{
|
||||
zoom = -2.5f;
|
||||
rotation = {0.0f, 15.0f, 0.0f};
|
||||
title = "Texture MipMap generation";
|
||||
}
|
||||
|
||||
TextureMipMapGeneration::~TextureMipMapGeneration()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
for (auto sampler : samplers)
|
||||
{
|
||||
vkDestroySampler(get_device().get_handle(), sampler, nullptr);
|
||||
}
|
||||
}
|
||||
destroy_texture(texture);
|
||||
uniform_buffer.reset();
|
||||
}
|
||||
|
||||
// Enable physical device features required for this example
|
||||
void TextureMipMapGeneration::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Load the base texture containing only the first mip level and generate the whole mip-chain at runtime
|
||||
*/
|
||||
void TextureMipMapGeneration::load_texture_generate_mipmaps(std::string file_name)
|
||||
{
|
||||
// ktx1 doesn't know whether the content is sRGB or linear, but most tools save in sRGB, so assume that.
|
||||
VkFormat format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
|
||||
ktxTexture *ktx_texture;
|
||||
KTX_error_code result;
|
||||
|
||||
result = ktxTexture_CreateFromNamedFile(file_name.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktx_texture);
|
||||
// @todo: get format from libktx
|
||||
|
||||
if (ktx_texture == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Couldn't load texture");
|
||||
}
|
||||
texture.width = ktx_texture->baseWidth;
|
||||
texture.height = ktx_texture->baseHeight;
|
||||
// Calculate number of mip levels as per Vulkan specs:
|
||||
// numLevels = 1 + floor(log2(max(w, h, d)))
|
||||
texture.mip_levels = static_cast<uint32_t>(floor(log2(std::max(texture.width, texture.height))) + 1);
|
||||
|
||||
// Get device properties for the requested texture format
|
||||
// Check if the selected format supports blit source and destination, which is required for generating the mip levels
|
||||
// If this is not supported you could implement a fallback via compute shader image writes and stores
|
||||
VkFormatProperties formatProperties;
|
||||
vkGetPhysicalDeviceFormatProperties(get_device().get_gpu().get_handle(), format, &formatProperties);
|
||||
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) || !(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT))
|
||||
{
|
||||
throw std::runtime_error("Selected image format does not support blit source and destination");
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info = vkb::initializers::memory_allocate_info();
|
||||
VkMemoryRequirements memory_requirements = {};
|
||||
|
||||
ktx_uint8_t *ktx_image_data = ktx_texture->pData;
|
||||
ktx_size_t ktx_texture_size = ktx_texture->dataSize;
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
VkBuffer staging_buffer;
|
||||
VkDeviceMemory staging_memory;
|
||||
|
||||
VkBufferCreateInfo buffer_create_info = vkb::initializers::buffer_create_info();
|
||||
buffer_create_info.size = ktx_texture_size;
|
||||
// This buffer is used as a transfer source for the buffer copy
|
||||
buffer_create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_CHECK(vkCreateBuffer(get_device().get_handle(), &buffer_create_info, nullptr, &staging_buffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
vkGetBufferMemoryRequirements(get_device().get_handle(), staging_buffer, &memory_requirements);
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
// Get memory type index for a host visible buffer
|
||||
memory_allocate_info.memoryTypeIndex =
|
||||
get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &staging_memory));
|
||||
VK_CHECK(vkBindBufferMemory(get_device().get_handle(), staging_buffer, staging_memory, 0));
|
||||
|
||||
// Copy ktx image data into host local staging buffer
|
||||
uint8_t *data;
|
||||
VK_CHECK(vkMapMemory(get_device().get_handle(), staging_memory, 0, memory_requirements.size, 0, (void **) &data));
|
||||
memcpy(data, ktx_image_data, ktx_texture_size);
|
||||
vkUnmapMemory(get_device().get_handle(), staging_memory);
|
||||
|
||||
// now, the ktx_texture can be destroyed
|
||||
ktxTexture_Destroy(ktx_texture);
|
||||
|
||||
// Create optimal tiled target image on the device
|
||||
VkImageCreateInfo image_create_info = vkb::initializers::image_create_info();
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = format;
|
||||
image_create_info.mipLevels = texture.mip_levels;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_create_info.extent = {texture.width, texture.height, 1};
|
||||
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &texture.image));
|
||||
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), texture.image, &memory_requirements);
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
memory_allocate_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &texture.device_memory));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), texture.image, texture.device_memory, 0));
|
||||
|
||||
VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
// Optimal image will be used as destination for the copy, so we must transfer from our initial undefined image layout to the transfer destination layout
|
||||
vkb::image_layout_transition(copy_command, texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
// Copy the first mip of the chain, remaining mips will be generated
|
||||
VkBufferImageCopy buffer_copy_region = {};
|
||||
buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
buffer_copy_region.imageSubresource.mipLevel = 0;
|
||||
buffer_copy_region.imageSubresource.baseArrayLayer = 0;
|
||||
buffer_copy_region.imageSubresource.layerCount = 1;
|
||||
buffer_copy_region.imageExtent.width = texture.width;
|
||||
buffer_copy_region.imageExtent.height = texture.height;
|
||||
buffer_copy_region.imageExtent.depth = 1;
|
||||
vkCmdCopyBufferToImage(copy_command, staging_buffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
|
||||
|
||||
// Transition first mip level to transfer source so we can blit(read) from it
|
||||
vkb::image_layout_transition(copy_command, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
// Clean up staging resources
|
||||
vkDestroyBuffer(get_device().get_handle(), staging_buffer, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), staging_memory, nullptr);
|
||||
|
||||
// Generate the mip chain
|
||||
// ---------------------------------------------------------------
|
||||
// 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
|
||||
VkCommandBuffer blit_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
// Copy down mips from n-1 to n
|
||||
for (uint32_t i = 1; i < texture.mip_levels; i++)
|
||||
{
|
||||
VkImageBlit image_blit{};
|
||||
|
||||
// Source
|
||||
image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.srcSubresource.layerCount = 1;
|
||||
image_blit.srcSubresource.mipLevel = i - 1;
|
||||
image_blit.srcOffsets[1].x = static_cast<int32_t>(texture.width >> (i - 1));
|
||||
image_blit.srcOffsets[1].y = static_cast<int32_t>(texture.height >> (i - 1));
|
||||
image_blit.srcOffsets[1].z = 1;
|
||||
|
||||
// Destination
|
||||
image_blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_blit.dstSubresource.layerCount = 1;
|
||||
image_blit.dstSubresource.mipLevel = i;
|
||||
image_blit.dstOffsets[1].x = static_cast<int32_t>(texture.width >> i);
|
||||
image_blit.dstOffsets[1].y = static_cast<int32_t>(texture.height >> i);
|
||||
image_blit.dstOffsets[1].z = 1;
|
||||
|
||||
// Prepare current mip level as image blit destination
|
||||
vkb::image_layout_transition(blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
|
||||
// Blit from previous level
|
||||
vkCmdBlitImage(
|
||||
blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
&image_blit,
|
||||
VK_FILTER_LINEAR);
|
||||
|
||||
// Prepare current mip level as image blit source for next level
|
||||
vkb::image_layout_transition(blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, i, 1, 0, 1});
|
||||
}
|
||||
|
||||
// After the loop, all mip layers are in TRANSFER_SRC layout, so transition all to SHADER_READ
|
||||
vkb::image_layout_transition(blit_command,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, texture.mip_levels, 0, 1});
|
||||
|
||||
get_device().flush_command_buffer(blit_command, queue, true);
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// Calculate valid filter and mipmap modes
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), format, &filter, &mipmap_mode);
|
||||
|
||||
// Create samplers for different mip map demonstration cases
|
||||
samplers.resize(3);
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = filter;
|
||||
sampler.minFilter = filter;
|
||||
sampler.mipmapMode = mipmap_mode;
|
||||
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler.mipLodBias = 0.0f;
|
||||
sampler.compareOp = VK_COMPARE_OP_NEVER;
|
||||
sampler.minLod = 0.0f;
|
||||
sampler.maxLod = 0.0f;
|
||||
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
sampler.maxAnisotropy = 1.0;
|
||||
sampler.anisotropyEnable = VK_FALSE;
|
||||
|
||||
// Without mip mapping
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &samplers[0]));
|
||||
|
||||
// With mip mapping
|
||||
sampler.maxLod = static_cast<float>(texture.mip_levels);
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &samplers[1]));
|
||||
|
||||
// With mip mapping and anisotropic filtering (when supported)
|
||||
if (get_device().get_gpu().get_features().samplerAnisotropy)
|
||||
{
|
||||
sampler.maxAnisotropy = get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy;
|
||||
sampler.anisotropyEnable = VK_TRUE;
|
||||
}
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &samplers[2]));
|
||||
|
||||
// Create image view
|
||||
VkImageViewCreateInfo view = vkb::initializers::image_view_create_info();
|
||||
view.image = texture.image;
|
||||
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view.format = format;
|
||||
view.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};
|
||||
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
view.subresourceRange.baseMipLevel = 0;
|
||||
view.subresourceRange.baseArrayLayer = 0;
|
||||
view.subresourceRange.layerCount = 1;
|
||||
view.subresourceRange.levelCount = texture.mip_levels;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &view, nullptr, &texture.view));
|
||||
}
|
||||
|
||||
// Free all Vulkan resources used by a texture object
|
||||
void TextureMipMapGeneration::destroy_texture(Texture texture)
|
||||
{
|
||||
vkDestroyImageView(get_device().get_handle(), texture.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), texture.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), texture.device_memory, nullptr);
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::load_assets()
|
||||
{
|
||||
load_texture_generate_mipmaps(vkb::fs::path::get(vkb::fs::path::Assets, "textures/checkerboard_rgba.ktx"));
|
||||
scene = load_model("scenes/tunnel_cylinder.gltf");
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = {1.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, NULL);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
draw_model(scene, draw_cmd_buffers[i]);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
// Command buffer to be submitted to the queue
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
// Submit to queue
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::setup_descriptor_pool()
|
||||
{
|
||||
// Example uses one ubo and one image sampler
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_SAMPLER, 3),
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings =
|
||||
{
|
||||
// Binding 0 : Parameter uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
0),
|
||||
// Binding 1 : Fragment shader image sampler
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
// Binding 2 : Sampler array (3 descriptors)
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
2,
|
||||
3),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
static_cast<uint32_t>(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr, &descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*uniform_buffer);
|
||||
|
||||
VkDescriptorImageInfo image_descriptor;
|
||||
image_descriptor.imageView = texture.view;
|
||||
image_descriptor.sampler = VK_NULL_HANDLE;
|
||||
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
0,
|
||||
&buffer_descriptor),
|
||||
// Binding 1 : Fragment shader texture sampler
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
1,
|
||||
&image_descriptor)};
|
||||
|
||||
// Binding 2: Sampler array
|
||||
std::vector<VkDescriptorImageInfo> sampler_descriptors;
|
||||
for (auto i = 0; i < samplers.size(); i++)
|
||||
{
|
||||
sampler_descriptors.push_back({samplers[i], VK_NULL_HANDLE, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
|
||||
}
|
||||
VkWriteDescriptorSet write_descriptor_set{};
|
||||
write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
write_descriptor_set.dstSet = descriptor_set;
|
||||
write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
write_descriptor_set.descriptorCount = static_cast<uint32_t>(sampler_descriptors.size());
|
||||
write_descriptor_set.pImageInfo = sampler_descriptors.data();
|
||||
write_descriptor_set.dstBinding = 2;
|
||||
write_descriptor_set.dstArrayElement = 0;
|
||||
write_descriptor_sets.push_back(write_descriptor_set);
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_NONE,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_LESS_OR_EQUAL);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
shader_stages[0] = load_shader("texture_mipmap_generation", "texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("texture_mipmap_generation", "texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 1: UV
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(pipeline_layout, render_pass, 0);
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::prepare_uniform_buffers()
|
||||
{
|
||||
// Shared parameter uniform buffer block
|
||||
uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::update_uniform_buffers(float delta_time)
|
||||
{
|
||||
ubo.projection = camera.matrices.perspective;
|
||||
ubo.model = camera.matrices.view;
|
||||
ubo.model = glm::rotate(ubo.model, glm::radians(90.0f + timer * 360.0f), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
ubo.model = glm::scale(ubo.model, glm::vec3(0.5f));
|
||||
timer += delta_time * 0.005f;
|
||||
if (timer > 1.0f)
|
||||
{
|
||||
timer -= 1.0f;
|
||||
}
|
||||
uniform_buffer->convert_and_update(ubo);
|
||||
}
|
||||
|
||||
bool TextureMipMapGeneration::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::FirstPerson;
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 0.1f, 1024.0f);
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -12.5f));
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (rotate_scene)
|
||||
{
|
||||
update_uniform_buffers(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void TextureMipMapGeneration::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
drawer.checkbox("Rotate", &rotate_scene);
|
||||
if (drawer.slider_float("LOD bias", &ubo.lod_bias, 0.0f, static_cast<float>(texture.mip_levels)))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (drawer.combo_box("Sampler type", &ubo.sampler_index, sampler_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_texture_mipmap_generation()
|
||||
{
|
||||
return std::make_unique<TextureMipMapGeneration>();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* Copyright (c) 2019-2024, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Runtime mip map generation
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ktx.h>
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
// Vertex layout for this example
|
||||
struct TextureMipMapGenerationVertexStructure
|
||||
{
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
class TextureMipMapGeneration : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
struct Texture
|
||||
{
|
||||
VkImage image;
|
||||
VkImageLayout image_layout;
|
||||
VkDeviceMemory device_memory;
|
||||
VkImageView view;
|
||||
uint32_t width, height;
|
||||
uint32_t mip_levels;
|
||||
} texture;
|
||||
|
||||
// To demonstrate mip mapping and filtering this example uses separate samplers
|
||||
std::vector<std::string> sampler_names{"No mip maps", "Mip maps (bilinear)", "Mip maps (anisotropic)"};
|
||||
std::vector<VkSampler> samplers;
|
||||
|
||||
std::unique_ptr<vkb::sg::SubMesh> scene;
|
||||
|
||||
bool rotate_scene = false;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
float lod_bias = 0.0f;
|
||||
int32_t sampler_index = 2;
|
||||
} ubo;
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer;
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE;
|
||||
|
||||
TextureMipMapGeneration();
|
||||
~TextureMipMapGeneration();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void load_texture_generate_mipmaps(std::string file_name);
|
||||
void destroy_texture(Texture texture);
|
||||
void load_assets();
|
||||
void build_command_buffers() override;
|
||||
void draw();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers(float delta_time = 0.0f);
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void view_changed() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_texture_mipmap_generation();
|
||||
Reference in New Issue
Block a user