366 lines
15 KiB
C++
366 lines
15 KiB
C++
/* 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>();
|
|
}
|