This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
@@ -0,0 +1,28 @@
# Copyright (c) 2021-2024, Holochip
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
#
# 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 "Holochip"
NAME "HPP Texture compression comparison"
DESCRIPTION "Compare the performance of different texture formats (ASTC, BC[x], ETC2, and PVRTC), using Vulkan-Hpp")
@@ -0,0 +1,27 @@
////
- Copyright (c) 2024, The Khronos Group
-
- 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.
-
////
= Texture compression comparison using Vulkan-Hpp
ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/performance/hpp_texture_compression_comparison[Khronos Vulkan samples github repository].
endif::[]
This sample demonstrates how to use different types of compressed GPU textures in a Vulkan application, and shows the timing benefits of each, using Vulkan-Hpp.
@@ -0,0 +1,319 @@
/* Copyright (c) 2021-2025, Holochip
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "hpp_texture_compression_comparison.h"
#include "core/hpp_queue.h"
namespace
{
constexpr std::array<const char *, 19> error_codes = {
"KTX_SUCCESS",
"KTX_FILE_DATA_ERROR",
"KTX_FILE_ISPIPE",
"KTX_FILE_OPEN_FAILED",
"KTX_FILE_OVERFLOW",
"KTX_FILE_READ_ERROR",
"KTX_FILE_SEEK_ERROR",
"KTX_FILE_UNEXPECTED_EOF",
"KTX_FILE_WRITE_ERROR",
"KTX_GL_ERROR",
"KTX_INVALID_OPERATION",
"KTX_INVALID_VALUE",
"KTX_NOT_FOUND",
"KTX_OUT_OF_MEMORY",
"KTX_TRANSCODE_FAILED",
"KTX_UNKNOWN_FILE_FORMAT",
"KTX_UNSUPPORTED_TEXTURE_TYPE",
"KTX_UNSUPPORTED_FEATURE",
"KTX_LIBRARY_NOT_LINKED",
};
std::string get_sponza_texture_filename(const std::string &short_name)
{
return vkb::fs::path::get(vkb::fs::path::Type::Assets) + "scenes/sponza/ktx2/" + short_name + "2";
}
class HPPCompressedImage : public vkb::scene_graph::components::HPPImage
{
public:
HPPCompressedImage(vkb::core::DeviceCpp &device,
const std::string &name,
std::vector<vkb::scene_graph::components::HPPMipmap> &&mipmaps,
vk::Format format) :
vkb::scene_graph::components::HPPImage(name, std::vector<uint8_t>{}, std::move(mipmaps))
{
vkb::scene_graph::components::HPPImage::set_format(format);
vkb::scene_graph::components::HPPImage::create_vk_image(device);
}
};
} // namespace
#define KTX_CHECK(x) \
do \
{ \
KTX_error_code err = x; \
if (err != KTX_SUCCESS) \
{ \
auto index = static_cast<uint32_t>(err); \
LOGE("Detected KTX error: {}", index < error_codes.size() ? error_codes[index] : ""); \
abort(); \
} \
} while (0)
HPPTextureCompressionComparison::HPPTextureCompressionComparison()
{
texture_compression_data = {
{nullptr, "", vk::Format::eR8G8B8A8Srgb, KTX_TTF_RGBA32, "KTX_TTF_RGBA32", "RGBA 32", true},
{&vk::PhysicalDeviceFeatures::textureCompressionBC, "", vk::Format::eBc7SrgbBlock, KTX_TTF_BC7_RGBA, "KTX_TTF_BC7_RGBA", "BC7"},
{&vk::PhysicalDeviceFeatures::textureCompressionBC, "", vk::Format::eBc3SrgbBlock, KTX_TTF_BC3_RGBA, "KTX_TTF_BC3_RGBA", "BC3"},
{&vk::PhysicalDeviceFeatures::textureCompressionASTC_LDR, "", vk::Format::eAstc4x4SrgbBlock, KTX_TTF_ASTC_4x4_RGBA, "KTX_TTF_ASTC_4x4_RGBA", "ASTC 4x4"},
{&vk::PhysicalDeviceFeatures::textureCompressionETC2, "", vk::Format::eEtc2R8G8B8A8SrgbBlock, KTX_TTF_ETC2_RGBA, "KTX_TTF_ETC2_RGBA", "ETC2"}};
}
void HPPTextureCompressionComparison::draw_gui()
{
get_gui().show_options_window(
[this]() {
if (ImGui::Combo(
"Compressed Format",
&current_gui_format,
[](void *user_data, int idx) -> char const * { return reinterpret_cast<HPPTextureCompressionData *>(user_data)[idx].gui_name.c_str(); },
texture_compression_data.data(),
static_cast<int>(texture_compression_data.size())))
{
require_redraw = true;
if (texture_compression_data[current_gui_format].is_supported)
{
current_format = current_gui_format;
}
}
const auto &current_gui_tc = texture_compression_data[current_gui_format];
if (current_gui_tc.is_supported)
{
ImGui::Text("Format name: %s", current_gui_tc.format_name.c_str());
ImGui::Text("Bytes: %f MB", static_cast<float>(current_benchmark.total_bytes) / 1024.f / 1024.f);
ImGui::Text("Compression Time: %f (ms)", current_benchmark.compress_time_ms);
}
else
{
ImGui::Text("%s not supported on this GPU.", current_gui_tc.short_name.c_str());
}
});
}
bool HPPTextureCompressionComparison::prepare(const vkb::ApplicationOptions &options)
{
if (vkb::VulkanSample<vkb::BindingType::Cpp>::prepare(options))
{
load_assets();
auto &camera_node = vkb::common::add_free_camera(get_scene(), "main_camera", get_render_context().get_surface_extent());
camera = &camera_node.get_component<vkb::sg::Camera>();
create_subpass();
get_stats().request_stats({vkb::StatIndex::frame_times, vkb::StatIndex::gpu_ext_read_bytes});
create_gui(*window, &get_stats());
prepare_gui();
return true;
}
return false;
}
void HPPTextureCompressionComparison::update(float delta_time)
{
if (require_redraw)
{
require_redraw = false;
assert(current_format >= 0 && static_cast<size_t>(current_format) < texture_compression_data.size());
current_benchmark = update_textures(texture_compression_data[current_format]);
}
VulkanSample<vkb::BindingType::Cpp>::update(delta_time);
}
std::pair<std::unique_ptr<vkb::scene_graph::components::HPPImage>, HPPTextureCompressionComparison::HPPTextureBenchmark>
HPPTextureCompressionComparison::compress(const std::string &filename,
HPPTextureCompressionComparison::HPPTextureCompressionData texture_format,
const std::string &name)
{
ktxTexture2 *ktx_texture{nullptr};
KTX_CHECK(ktxTexture2_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktx_texture));
HPPTextureBenchmark benchmark;
{
const auto start = std::chrono::high_resolution_clock::now();
KTX_CHECK(ktxTexture2_TranscodeBasis(ktx_texture, texture_format.ktx_format, 0));
const auto end = std::chrono::high_resolution_clock::now();
benchmark.compress_time_ms = static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) / 1000.f;
}
benchmark.total_bytes = ktx_texture->dataSize;
auto image = create_image(ktx_texture, name);
ktxTexture_Destroy((ktxTexture *) ktx_texture);
return {std::move(image), benchmark};
}
std::unique_ptr<vkb::scene_graph::components::HPPImage> HPPTextureCompressionComparison::create_image(ktxTexture2 *ktx_texture, const std::string &name)
{
std::unique_ptr<vkb::core::BufferCpp> staging_buffer =
std::make_unique<vkb::core::BufferCpp>(get_device(), ktx_texture->dataSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU);
memcpy(staging_buffer->map(), ktx_texture->pData, ktx_texture->dataSize);
const auto vk_format = static_cast<vk::Format>(ktx_texture->vkFormat);
vk::Extent3D extent{ktx_texture->baseWidth, ktx_texture->baseHeight, 1};
std::vector<vk::BufferImageCopy> buffer_copies;
std::unique_ptr<vkb::scene_graph::components::HPPImage> image_out;
{
std::vector<vkb::scene_graph::components::HPPMipmap> mip_maps;
for (uint32_t mip_level = 0; mip_level < ktx_texture->numLevels; ++mip_level)
{
vk::Extent3D mip_extent{extent.width >> mip_level, extent.height >> mip_level, 1};
if (!mip_extent.width || !mip_extent.height)
{
break;
}
ktx_size_t offset{0};
KTX_CHECK(ktxTexture_GetImageOffset((ktxTexture *) ktx_texture, mip_level, 0, 0, &offset));
vk::BufferImageCopy buffer_image_copy = {};
buffer_image_copy.imageSubresource = vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
buffer_image_copy.imageExtent = mip_extent;
buffer_image_copy.bufferOffset = static_cast<uint32_t>(offset);
buffer_copies.push_back(buffer_image_copy);
vkb::scene_graph::components::HPPMipmap mip_map;
mip_map.extent = buffer_image_copy.imageExtent;
mip_map.level = mip_level;
mip_map.offset = static_cast<uint32_t>(offset);
mip_maps.push_back(mip_map);
}
image_out = std::make_unique<HPPCompressedImage>(get_device(), name, std::move(mip_maps), vk_format);
}
auto &vkb_image = image_out->get_vk_image();
auto image = vkb_image.get_handle();
vk::ImageSubresourceRange subresource_range{vk::ImageAspectFlagBits::eColor, 0, static_cast<uint32_t>(buffer_copies.size()), 0, 1};
vk::CommandBuffer command_buffer = get_device().create_command_buffer(vk::CommandBufferLevel::ePrimary, true);
vkb::common::image_layout_transition(command_buffer, image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, subresource_range);
command_buffer.copyBufferToImage(staging_buffer->get_handle(), image, vk::ImageLayout::eTransferDstOptimal, buffer_copies);
vkb::common::image_layout_transition(
command_buffer, image, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, subresource_range);
get_device().flush_command_buffer(command_buffer, get_device().get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0).get_handle(), true);
return image_out;
}
void HPPTextureCompressionComparison::create_subpass()
{
vkb::ShaderSource vert_shader("base.vert.spv");
vkb::ShaderSource frag_shader("base.frag.spv");
auto scene_sub_pass = std::make_unique<vkb::rendering::subpasses::HPPForwardSubpass>(
get_render_context(), std::move(vert_shader), std::move(frag_shader), get_scene(), *camera);
auto render_pipeline = std::make_unique<vkb::rendering::HPPRenderPipeline>();
render_pipeline->add_subpass(std::move(scene_sub_pass));
set_render_pipeline(std::move(render_pipeline));
}
bool HPPTextureCompressionComparison::is_texture_format_supported(const HPPTextureCompressionData &tcd, vk::PhysicalDeviceFeatures const &device_features)
{
const bool supported_by_feature = tcd.feature_ptr && device_features.*tcd.feature_ptr;
const bool supported_by_extension = tcd.extension_name.length() && get_device().get_gpu().is_extension_supported(tcd.extension_name);
const bool supported_by_default = tcd.always_supported;
return supported_by_default || supported_by_feature || supported_by_extension;
}
void HPPTextureCompressionComparison::load_assets()
{
load_scene("scenes/sponza/Sponza01.gltf");
if (!has_scene())
{
throw std::runtime_error("Unable to load Sponza scene");
}
for (auto &&mesh : get_scene().get_components<vkb::scene_graph::components::HPPMesh>())
{
for (auto &&sub_mesh : mesh->get_submeshes())
{
auto material = sub_mesh->get_material();
for (auto &name_texture : material->get_textures())
{
vkb::scene_graph::components::HPPTexture *texture = name_texture.second;
auto image = texture->get_image();
textures.emplace_back(texture, image->get_name());
}
}
}
}
void HPPTextureCompressionComparison::prepare_gui()
{
const auto device_features = get_device().get_gpu().get_features();
for (auto &tc : texture_compression_data)
{
tc.is_supported = is_texture_format_supported(tc, device_features);
tc.gui_name = fmt::format(FMT_STRING("{:s} {:s}"), tc.short_name, tc.is_supported ? "" : "(not supported)");
}
}
HPPTextureCompressionComparison::HPPTextureBenchmark HPPTextureCompressionComparison::update_textures(const HPPTextureCompressionData &new_format)
{
HPPTextureBenchmark benchmark;
std::unordered_set<std::string> visited;
for (auto &&texture_filename : textures)
{
vkb::scene_graph::components::HPPTexture *texture = texture_filename.first;
assert(!!texture);
auto &internal_name = texture_filename.second;
if (!visited.count(internal_name))
{
auto filename = get_sponza_texture_filename(internal_name);
auto new_image = compress(filename, new_format, "");
texture_raw_data[internal_name].image = std::move(new_image.first);
texture_raw_data[internal_name].benchmark = new_image.second;
benchmark += new_image.second;
}
vkb::scene_graph::components::HPPImage *image = texture_raw_data[internal_name].image.get();
assert(image);
texture->set_image(*image);
visited.insert(internal_name);
}
// update the forward subpass to use the new textures
create_subpass();
return benchmark;
}
std::unique_ptr<HPPTextureCompressionComparison> create_hpp_texture_compression_comparison()
{
return std::make_unique<HPPTextureCompressionComparison>();
}
@@ -0,0 +1,91 @@
/* Copyright (c) 2021-2024, Holochip
* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#pragma once
#include "ktx.h"
#include "vulkan_sample.h"
class HPPTextureCompressionComparison : public vkb::VulkanSample<vkb::BindingType::Cpp>
{
public:
HPPTextureCompressionComparison();
private:
// from vkb::VulkanSample
void draw_gui() override;
bool prepare(const vkb::ApplicationOptions &options) override;
void update(float delta_time) override;
private:
struct HPPTextureCompressionData
{
vk::Bool32 vk::PhysicalDeviceFeatures::*feature_ptr = nullptr;
std::string extension_name = {};
vk::Format format = {};
ktx_transcode_fmt_e ktx_format = KTX_TTF_NOSELECTION;
std::string format_name = {};
std::string short_name = {};
bool always_supported = false;
std::string gui_name = {};
bool is_supported = {};
};
struct HPPTextureBenchmark
{
HPPTextureBenchmark &operator+=(const HPPTextureBenchmark &other)
{
total_bytes += other.total_bytes;
compress_time_ms += other.compress_time_ms;
frame_time_ms += other.frame_time_ms;
return *this;
}
vk::DeviceSize total_bytes = 0;
float compress_time_ms = 0.f;
float frame_time_ms = 0.f;
};
struct HPPSampleTexture
{
std::vector<uint8_t> raw_bytes;
std::unique_ptr<vkb::scene_graph::components::HPPImage> image;
HPPTextureBenchmark benchmark;
};
private:
std::pair<std::unique_ptr<vkb::scene_graph::components::HPPImage>, HPPTextureBenchmark> compress(
const std::string &filename, HPPTextureCompressionData texture_format, const std::string &name);
std::unique_ptr<vkb::scene_graph::components::HPPImage> create_image(ktxTexture2 *ktx_texture, const std::string &name);
void create_subpass();
bool is_texture_format_supported(const HPPTextureCompressionData &tcd, vk::PhysicalDeviceFeatures const &device_features);
void load_assets();
void prepare_gui();
HPPTextureBenchmark update_textures(const HPPTextureCompressionData &new_format);
private:
vkb::sg::Camera *camera = nullptr;
HPPTextureBenchmark current_benchmark = {};
int current_gui_format = 0;
int current_format = 0;
bool require_redraw = true;
std::vector<HPPTextureCompressionData> texture_compression_data;
std::unordered_map<std::string, HPPSampleTexture> texture_raw_data;
std::vector<std::pair<vkb::scene_graph::components::HPPTexture *, std::string>> textures;
};
std::unique_ptr<HPPTextureCompressionComparison> create_hpp_texture_compression_comparison();