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,365 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* 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 "16bit_arithmetic.h"
#include "gui.h"
#include "stats/stats.h"
#include <random>
#include <scene_graph/components/camera.h>
static constexpr unsigned Width = 1024;
static constexpr unsigned Height = 1024;
static constexpr unsigned NumBlobs = 16;
KHR16BitArithmeticSample::KHR16BitArithmeticSample()
{
// Enables required extensions to use 16-bit storage.
// For this sample, this is not optional.
// This sample also serves as a tutorial on how to use 16-bit storage
// for SSBOs and push constants.
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
add_device_extension(VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, false);
add_device_extension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, false);
// Enables the extension which allows shaders to use 16-bit float and 8-bit integer arithmetic.
// This sample will only make use of 16-bit floats.
add_device_extension(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, true);
auto &config = get_configuration();
config.insert<vkb::BoolSetting>(0, khr_16bit_arith_enabled, false);
config.insert<vkb::BoolSetting>(1, khr_16bit_arith_enabled, true);
}
bool KHR16BitArithmeticSample::prepare(const vkb::ApplicationOptions &options)
{
if (!VulkanSample::prepare(options))
{
return false;
}
// Normally, we should see the immediate effect on frame times,
// but if we're somehow hitting 60 FPS, GPU cycles / s should go down while hitting vsync.
get_stats().request_stats({vkb::StatIndex::gpu_cycles, vkb::StatIndex::frame_times});
create_gui(*window, &get_stats());
// Set up some structs for the (color, depth) attachments in the default render pass.
load_store_infos.resize(2);
load_store_infos[0].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store_infos[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
load_store_infos[1].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store_infos[1].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
VkClearValue clear_value;
clear_value.color.float32[0] = 0.0f;
clear_value.color.float32[1] = 0.0f;
clear_value.color.float32[2] = 0.0f;
clear_value.color.float32[3] = 1.0f;
clear_values.push_back(clear_value);
clear_value.depthStencil.depth = 1.0f;
clear_value.depthStencil.stencil = 0;
clear_values.push_back(clear_value);
// Generate some random blobs to render and place them in a 4xfp16 data structure.
std::default_random_engine rng(42);
std::normal_distribution<float> position_dist(0.0f, 0.1f);
std::uniform_real_distribution<float> intensity_dist(0.4f, 0.8f);
std::uniform_real_distribution<float> falloff_dist(50.0f, 100.0f);
glm::vec4 initial_data_fp32[NumBlobs];
for (unsigned i = 0; i < NumBlobs; i++)
{
initial_data_fp32[i].x = position_dist(rng);
initial_data_fp32[i].y = position_dist(rng);
initial_data_fp32[i].z = intensity_dist(rng);
initial_data_fp32[i].w = falloff_dist(rng);
}
// Convert FP32 to FP16.
glm::uvec2 initial_data_fp16[NumBlobs];
for (unsigned i = 0; i < NumBlobs; i++)
{
initial_data_fp16[i].x = glm::packHalf2x16(initial_data_fp32[i].xy);
initial_data_fp16[i].y = glm::packHalf2x16(initial_data_fp32[i].zw);
}
// Upload the blob buffer.
auto &device = get_render_context().get_device();
blob_buffer = std::make_unique<vkb::core::BufferC>(device, sizeof(initial_data_fp16),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
auto staging_buffer = vkb::core::BufferC::create_staging_buffer(device, initial_data_fp16);
auto cmd = device.get_command_pool().request_command_buffer();
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE);
cmd->copy_buffer(staging_buffer, *blob_buffer, sizeof(initial_data_fp16));
vkb::BufferMemoryBarrier barrier;
barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
cmd->buffer_memory_barrier(*blob_buffer, 0, VK_WHOLE_SIZE, barrier);
cmd->end();
auto &queue = device.get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
queue.submit(*cmd, device.get_fence_pool().request_fence());
device.get_fence_pool().wait();
// Create the target image we render into in the main compute shader.
image = std::make_unique<vkb::core::Image>(device, VkExtent3D{Width, Height, 1},
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
image_view = std::make_unique<vkb::core::ImageView>(*image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT,
0, 0, 1, 1);
// Calculate valid filter
VkFilter filter = VK_FILTER_LINEAR;
vkb::make_filters_valid(get_device().get_gpu().get_handle(), image->get_format(), &filter);
VkSamplerCreateInfo sampler_create_info = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_create_info.magFilter = filter;
sampler_create_info.minFilter = filter;
sampler_create_info.maxLod = VK_LOD_CLAMP_NONE;
sampler = std::make_unique<vkb::core::Sampler>(device, sampler_create_info);
// Load shader modules.
auto &module =
device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT,
vkb::ShaderSource{"16bit_arithmetic/compute_buffer.comp.spv"});
compute_layout = &device.get_resource_cache().request_pipeline_layout({&module});
if (supported_extensions)
{
vkb::ShaderVariant variant;
if (supports_push_constant16)
{
auto &module_fp16 =
device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT,
vkb::ShaderSource{"16bit_arithmetic/compute_buffer_fp16.comp.spv"}, variant);
compute_layout_fp16 = &device.get_resource_cache().request_pipeline_layout({&module_fp16});
}
else
{
auto &module_fp16 =
device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT,
vkb::ShaderSource{"16bit_arithmetic/compute_buffer_fp16_fallback.comp.spv"}, variant);
compute_layout_fp16 = &device.get_resource_cache().request_pipeline_layout({&module_fp16});
}
}
else
{
compute_layout_fp16 = compute_layout;
}
// Setup the visualization subpass which is there to blit the final result to screen.
vkb::ShaderSource vertex_source{"16bit_arithmetic/visualize.vert.spv"};
vkb::ShaderSource fragment_source{"16bit_arithmetic/visualize.frag.spv"};
auto subpass = std::make_unique<VisualizationSubpass>(get_render_context(),
std::move(vertex_source),
std::move(fragment_source));
subpass->view = image_view.get();
subpass->sampler = sampler.get();
subpasses.emplace_back(std::move(subpass));
for (auto &subpass : subpasses)
{
subpass->prepare();
}
return true;
}
KHR16BitArithmeticSample::VisualizationSubpass::VisualizationSubpass(vkb::RenderContext &context,
vkb::ShaderSource &&vertex_source,
vkb::ShaderSource &&fragment_source) :
vkb::rendering::SubpassC(context, std::move(vertex_source), std::move(fragment_source))
{
set_output_attachments({0});
}
void KHR16BitArithmeticSample::VisualizationSubpass::draw(vkb::core::CommandBufferC &command_buffer)
{
command_buffer.bind_pipeline_layout(*layout);
// A depth-stencil attachment exists in the default render pass, make sure we ignore it.
vkb::DepthStencilState ds_state = {};
ds_state.depth_test_enable = VK_FALSE;
ds_state.stencil_test_enable = VK_FALSE;
ds_state.depth_write_enable = VK_FALSE;
ds_state.depth_compare_op = VK_COMPARE_OP_ALWAYS;
command_buffer.set_depth_stencil_state(ds_state);
command_buffer.bind_image(*view, *sampler, 0, 0, 0);
command_buffer.draw(3, 1, 0, 0);
}
void KHR16BitArithmeticSample::VisualizationSubpass::prepare()
{
auto &device = get_render_context().get_device();
auto &vert_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader());
auto &frag_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader());
std::vector<vkb::ShaderModule *> shader_modules{&vert_shader_module, &frag_shader_module};
layout = &device.get_resource_cache().request_pipeline_layout(shader_modules);
}
void KHR16BitArithmeticSample::request_gpu_features(vkb::PhysicalDevice &gpu)
{
// Required features.
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDevice16BitStorageFeatures,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,
storageBuffer16BitAccess);
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDevice16BitStorageFeatures,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,
uniformAndStorageBuffer16BitAccess);
// Optional features.
supported_extensions = REQUEST_OPTIONAL_FEATURE(gpu,
VkPhysicalDeviceFloat16Int8FeaturesKHR,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR,
shaderFloat16);
supports_push_constant16 =
REQUEST_OPTIONAL_FEATURE(gpu, VkPhysicalDevice16BitStorageFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, storagePushConstant16);
}
void KHR16BitArithmeticSample::draw_renderpass(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target)
{
if (khr_16bit_arith_enabled)
{
command_buffer.bind_pipeline_layout(*compute_layout_fp16);
}
else
{
command_buffer.bind_pipeline_layout(*compute_layout);
}
command_buffer.bind_buffer(*blob_buffer, 0, NumBlobs * sizeof(glm::uvec2), 0, 0, 0);
command_buffer.bind_image(*image_view, 0, 1, 0);
// Wait for fragment shader is done reading before we can write in compute.
vkb::ImageMemoryBarrier write_after_read_hazard;
write_after_read_hazard.src_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
write_after_read_hazard.dst_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
write_after_read_hazard.src_access_mask = 0;
write_after_read_hazard.dst_access_mask = VK_ACCESS_SHADER_WRITE_BIT;
write_after_read_hazard.old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
write_after_read_hazard.new_layout = VK_IMAGE_LAYOUT_GENERAL;
command_buffer.image_memory_barrier(*image_view, write_after_read_hazard);
// 16-bit push constants are supported by VK_KHR_16bit_storage, which is handy for conserving space without
// using many "unpack" instructions in the shader.
struct Push16
{
uint16_t num_blobs;
uint16_t fp16_seed;
int16_t range_x, range_y;
} push16 = {};
struct Push32
{
uint32_t num_blobs;
float fp32_seed;
int32_t range_x, range_y;
} push32 = {};
frame_count = (frame_count + 1u) & 511u;
float seed_value = 0.5f * glm::sin(glm::two_pi<float>() * (static_cast<float>(frame_count) / 512.0f));
push32.num_blobs = NumBlobs;
push32.fp32_seed = seed_value;
push32.range_x = 2;
push32.range_y = 1;
if (khr_16bit_arith_enabled && supports_push_constant16)
{
push16.num_blobs = push32.num_blobs;
push16.fp16_seed = static_cast<uint16_t>(glm::packHalf2x16(glm::vec2(push32.fp32_seed)));
push16.range_x = push32.range_x;
push16.range_y = push32.range_y;
command_buffer.push_constants(push16);
}
else
{
command_buffer.push_constants(push32);
}
command_buffer.set_specialization_constant(0, Width);
command_buffer.set_specialization_constant(1, Height);
// Workgroup size is (8, 8)
command_buffer.dispatch(Width / 8, Height / 8, 1);
vkb::ImageMemoryBarrier to_fragment_barrier;
to_fragment_barrier.old_layout = VK_IMAGE_LAYOUT_GENERAL;
to_fragment_barrier.new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
to_fragment_barrier.src_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
to_fragment_barrier.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
to_fragment_barrier.src_access_mask = VK_ACCESS_SHADER_WRITE_BIT;
to_fragment_barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT;
command_buffer.image_memory_barrier(*image_view, to_fragment_barrier);
// Blit result to screen and render UI.
command_buffer.begin_render_pass(render_target, load_store_infos, clear_values, subpasses);
command_buffer.set_viewport(0, {{0.0f, 0.0f, static_cast<float>(render_target.get_extent().width), static_cast<float>(render_target.get_extent().height), 0.0f, 1.0f}});
command_buffer.set_scissor(0, {{{0, 0}, render_target.get_extent()}});
subpasses.front()->draw(command_buffer);
get_gui().draw(command_buffer);
command_buffer.end_render_pass();
}
void KHR16BitArithmeticSample::draw_gui()
{
const char *label;
if (supported_extensions)
{
label = "Enable 16-bit arithmetic";
}
else
{
label = "16-bit arithmetic (unsupported features)";
}
get_gui().show_options_window(
/* body = */ [this, label]() {
if (!supported_extensions)
{
ImGui::Text("%s", label);
}
else
{
ImGui::Checkbox(label, &khr_16bit_arith_enabled);
}
},
/* lines = */ 1);
}
std::unique_ptr<vkb::VulkanSampleC> create_16bit_arithmetic()
{
return std::make_unique<KHR16BitArithmeticSample>();
}