init
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2023-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 "Sascha Willems"
|
||||
NAME "HPP Timestamp queries"
|
||||
DESCRIPTION "Using timestamp queries to fetch timing information from the GPU, using vulkan.hpp"
|
||||
SHADER_FILES_GLSL
|
||||
"hdr/glsl/composition.vert"
|
||||
"hdr/glsl/composition.frag"
|
||||
"hdr/glsl/bloom.vert"
|
||||
"hdr/glsl/bloom.frag"
|
||||
"hdr/glsl/gbuffer.vert"
|
||||
"hdr/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"hdr/hlsl/composition.vert.hlsl"
|
||||
"hdr/hlsl/composition.frag.hlsl"
|
||||
"hdr/hlsl/bloom.vert.hlsl"
|
||||
"hdr/hlsl/bloom.frag.hlsl"
|
||||
"hdr/hlsl/gbuffer.vert.hlsl"
|
||||
"hdr/hlsl/gbuffer.frag.hlsl")
|
||||
@@ -0,0 +1,282 @@
|
||||
////
|
||||
- Copyright (c) 2023-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.
|
||||
-
|
||||
////
|
||||
:doctype: book
|
||||
:pp: {plus}{plus}
|
||||
|
||||
= Timestamp queries with 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/api/hpp_timestamp_queries[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
NOTE: A transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/timestamp_queries[Timestamp queries] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp.
|
||||
|
||||
This tutorial, along with the accompanying example code, shows how to use timestamp queries to measure timings on the GPU.
|
||||
|
||||
The sample, based on the HDR one, does multiple render passes and will use timestamp queries to get GPU timings for the different render passes.
|
||||
This is done by writing GPU timestamps at certain points within a command buffer.
|
||||
These can then be read on the host and used for approximate profiling and to e.g.
|
||||
improve performance where needed.
|
||||
|
||||
== Introduction
|
||||
|
||||
Vulkan offers several https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#queries[query types] that allow you to query different types of information from the GPU.
|
||||
One such query type is the https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#queries-timestamps[timestamp query].
|
||||
|
||||
This provides your application with a mechanism to time the execution of commands on the GPU.
|
||||
As with the other query types, a query pool is then used to either directly fetch or copy over the results to the host.
|
||||
|
||||
== A few important notes on timestamp queries
|
||||
|
||||
It's important to know that timestamp queries differ greatly from how timing can be done on the CPU with e.g.
|
||||
the high performance counter.
|
||||
This is mostly due to how a GPU's dispatches, overlaps and finishes work across different stages of the pipeline.
|
||||
So while technically you can specify any pipeline stage at which the timestamp should be written, a lot of stage combinations and orderings won't give meaningful result.
|
||||
This also means that you can't compare timestamps taken on different queues.
|
||||
|
||||
So while it may may sound reasonable to write timestamps for the vertex and fragment shader stage directly one after another, that will usually not return meaningful results due to how the GPU works.
|
||||
|
||||
And so for this example, we take the same approach as some popular CPU/GPU profilers by only using the top and bottom stages of the pipeline.
|
||||
This combination is known to give proper approximate timing results on most GPUs.
|
||||
|
||||
== Checking for support
|
||||
|
||||
Not all GPUs support timestamp queries, so before using them we need to make sure that they can be used.
|
||||
This differs slightly from checking other features with a simple `vk::Bool`.
|
||||
Here we need to check if the `timestampPeriod` limit of the physical device is greater than zero.
|
||||
If that's the case, timestamp queries are supported:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::PhysicalDeviceLimits const &device_limits = device->get_gpu().get_properties().limits;
|
||||
if (device_limits.timestampPeriod == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected device does not support timestamp queries!"};
|
||||
}
|
||||
----
|
||||
|
||||
Another limit we need to check is `timestampComputeAndGraphics`.
|
||||
If this is `true`, all graphics and compute pipelines support timestamp queries and the above check is sufficient.
|
||||
If not, we need to check if the queue we want to use supports timestamps:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
if (!device_limits.timestampComputeAndGraphics)
|
||||
{
|
||||
// Check if the graphics queue used in this sample supports time stamps
|
||||
vk::QueueFamilyProperties const &graphics_queue_family_properties = device->get_suitable_graphics_queue().get_properties();
|
||||
if (graphics_queue_family_properties.timestampValidBits == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected graphics queue family does not support timestamp queries!"};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Creating the query pool
|
||||
|
||||
As with all query types, we first need to create a pool for the timestamp queries.
|
||||
This is used to store and read back the results (see `prepare_time_stamp_queries`):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::QueryPoolCreateInfo query_pool_create_info({}, vk::QueryType::eTimestamp, static_cast<uint32_t>(time_stamps.size()));
|
||||
time_stamps_query_pool = get_device()->get_handle().createQueryPool(query_pool_create_info);
|
||||
----
|
||||
|
||||
The interesting parts are the `queryType`, which we set to `vk::QueryType::eTimestamp` for using timestamp queries and the `queryCount`, which is the maximum number of the the timestamp query result this pool can store.
|
||||
|
||||
For this sample we'll be using 6 time points, one for the start and one for the end of three render passes.
|
||||
|
||||
== Resetting the query pool
|
||||
|
||||
Before we can start writing data to the query pool, we need to reset it.
|
||||
When using Vulkan 1.0 or 1.1, this requires us to enable the `VK_EXT_host_query_reset` extension:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
add_device_extension(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
|
||||
----
|
||||
|
||||
With using Vulkan 1.2 this extension has become part of the core and we won't have to manually enable it.
|
||||
|
||||
Independent of this, we also need to enable the `hostQueryReset` physical device feature:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
auto &requested_extension_features = gpu.request_extension_features<vk::PhysicalDeviceHostQueryResetFeaturesEXT>();
|
||||
requested_extension_features.hostQueryReset = true;
|
||||
----
|
||||
|
||||
With features and extensions properly enabled, we can now reset the pool at the start of the command buffer, before writing the first timestamp.
|
||||
This is done using `vk::CommandBuffer::resetQueryPool`:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
...
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
command_buffer.resetQueryPool(time_stamps_query_pool, 0, static_cast<uint32_t>(time_stamps.size()));
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
== Writing time stamps
|
||||
|
||||
Unlike getting CPU side timing information that can be queried immediately, with GPU time stamps we need to tell the implementation inside a command buffer when/where to write timestamps instead.
|
||||
The results are then fetched afterwards (see below).
|
||||
|
||||
This is done inside the command buffer with `vk::CommandBuffer::writeTimestamp`.
|
||||
This function will request a timestamp to be written from the GPU for a certain pipeline stage and write that value to memory.
|
||||
|
||||
The most interesting part of calling this function is the `pipelineStage` argument.
|
||||
As noted earlier, it's technically possible to use any pipeline stage in here, not all pipeline stages will yield proper results due to how GPUs overlap work.
|
||||
It's also important to note that not all implementations are able to latch timers at all pipeline stages (e.g.
|
||||
if they don't have hardware that maps to a given stage) and may return timers at a later pipeline stage instead.
|
||||
|
||||
Calling this function also defines an execution dependency similar to a barrier on all commands that were submitted before it.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps_query_pool, 0);
|
||||
// Do some work
|
||||
for (int i = 0; i < draw_call_count; i++) {
|
||||
command_buffer.draw(...);
|
||||
}
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps_query_pool, 1);
|
||||
----
|
||||
|
||||
To measure GPU times for the draw calls(s) we first tell the GPU to write a timestamp at the `vk::PipelineStageFlagBits::eTopOfPipe` pipeline stage.
|
||||
This is not a real pipeline stage (as in e.g.
|
||||
the vertex or fragment stages) but a special constant that tells the GPU to write the timestamp when all previous commands have been processed by the GPU's command processor.
|
||||
This ensures that we get a timestamp right before starting on the draw calls we want to measure, which will be the base for calculating our delta time.
|
||||
|
||||
The second timestamp is written at the `vk::PipelineStageFlagBits::eBottomOfPipe` pipeline stage.
|
||||
Once again this is not a real pipeline stage, but it again tells the GPU to write the timestamp after all work has been finished.
|
||||
|
||||
== Getting the results
|
||||
|
||||
Reading back the results can be done in two ways:
|
||||
|
||||
* Copy the results into a `vk::Buffer` inside the command buffer using `vk::CommandBuffer::copyQueryPoolResults`
|
||||
* Get the results after the command buffer has finished executing using `vk::Device::getQueryPoolResults`
|
||||
|
||||
For our sample we'll use option two (see `get_time_stamp_results`):
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
queue.submit(submit_info);
|
||||
...
|
||||
// The number of timestamps changes if the bloom pass is disabled
|
||||
uint32_t count = bloom ? time_stamps.size() : time_stamps.size() - 2;
|
||||
|
||||
vk::Result result = device->get_handle().getQueryPoolResults(time_stamps_query_pool,
|
||||
0,
|
||||
count,
|
||||
time_stamps.size() * sizeof(uint64_t),
|
||||
time_stamps.data(),
|
||||
sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
|
||||
----
|
||||
|
||||
Most arguments are straightforward, e.g.
|
||||
where the data will be copied to (the `time_stamps` vector).
|
||||
The important part here are the `vk::QueryResultFlags ` flags used here.
|
||||
|
||||
`vk::QueryResultFlagBits::e64` will tell the api that we want to get the results as 64 bit values.
|
||||
Without this flag, we would only get 32 bit values.
|
||||
And since timestamp queries can operate in nanoseconds, only using 32 bits could result into an overflow.
|
||||
E.g.
|
||||
if your device has a `timestampPeriod` of 1, so that one increment in the result maps to exactly one nanosecond, with 32 bit precision you'd run into such an overflow after only about 0.43 seconds.
|
||||
|
||||
The `vk::QueryResultFlagBits::eWait` bit then tells the api to wait for all results to be available.
|
||||
So when using this flag the values written to our `time_stamps` vector is guaranteed to be available after calling `vk::Device::getQueryPoolResults`.
|
||||
This is fine for our use-case where we want to immediately access the results, but may introduce unnecessary stalls in other scenarios.
|
||||
|
||||
Alternatively you can use the `vk::QueryResultFlagBits::eWithAvailability` flag, which will let you poll the availability of the results and defer writing new timestamps until the results are available.
|
||||
This should be the preferred way of fetching the results in a real-world application.
|
||||
Using this flag an additional availability value is inserted after each query value.
|
||||
If that value becomes non-zero, the result is available.
|
||||
You then check availability before writing the timestamp again.
|
||||
|
||||
Here is a basic example of how this could look like for a single timestamp value:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// time_stamp_with_availibility[current_frame * 2] contains the queried timestamp
|
||||
// time_stamp_with_availibility[current_frame * 2 + 1] contains availability of the timestamp
|
||||
std::array<uint64_t, max_frames_in_flight * 2> time_stamp_with_availibility{};
|
||||
|
||||
void drawFrame()
|
||||
{
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
// Only write new timestamp if previous result is available
|
||||
if (time_stamp_with_availibility[current_frame * 2 + 1] != 0)
|
||||
{
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps_query_pool, 0);
|
||||
}
|
||||
|
||||
// Issue draw commands
|
||||
|
||||
command_buffer.end();
|
||||
|
||||
// Get deferred time stamp query for the current frame
|
||||
vk::Result result = device.getQueryPoolResults(time_stamps_query_pool,
|
||||
0,
|
||||
1,
|
||||
2 * sizeof(uint64_t),
|
||||
&time_stamp_with_availibility[current_frame * max_frames_in_flight],
|
||||
2 * sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWithAvailability);
|
||||
assert(result == vk::Result::eSuccess);
|
||||
|
||||
// Display time stamp for the current frame if available
|
||||
if (time_stamp_with_availibility[current_frame * 2 + 1] != 0) {
|
||||
std::cout << "Timestamp = " << time_stamp_with_availibility[current_frame * 2] << "\n";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Interpreting the results
|
||||
|
||||
After we have read back the results to the host, we are ready to interpret them.
|
||||
E.g.
|
||||
for displaying them in a user interface.
|
||||
|
||||
The results we got back do not actually contain a time value, but rather a number of "ticks".
|
||||
So to get the actual time value we need to translate these values first.
|
||||
|
||||
This is done using `timestampPeriod` limit of the physical device.
|
||||
It contains the number of nanoseconds it takes for a timestamp query value to be increased by 1 ("tick").
|
||||
|
||||
In our sample, we want to display the delta between two timestamps in milliseconds, so in addition to the above rule we also multiply the value accordingly.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vk::PhysicalDeviceLimits const &device_limits = device->get_gpu().get_properties().limits;
|
||||
float delta_in_ms = float(time_stamps[1] - time_stamps[0]) * device_limits.timestampPeriod / 1000000.0f;
|
||||
----
|
||||
|
||||
== vk::CommandBuffer::writeTimestamp2
|
||||
|
||||
The https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_synchronization2.html[VK_KHR_synchronization2] extension introduced `vk::CommandBuffer::writeTimestamp2`.
|
||||
This is pretty much the same as the `vk::CommandBuffer::writeTimestamp` function used in this sample, but adds support for some additional pipeline stages using `vk::PipelineStageFlags2`.
|
||||
|
||||
== Verdict
|
||||
|
||||
Even though timestamp queries are limited due to how a GPU works, they can still be useful for profiling and finding performance GPU bottlenecks.
|
||||
@@ -0,0 +1,821 @@
|
||||
/* Copyright (c) 2023-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Timestamp queries (based on the HDR sample), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#include "hpp_timestamp_queries.h"
|
||||
#include "core/hpp_queue.h"
|
||||
|
||||
HPPTimestampQueries::HPPTimestampQueries()
|
||||
{
|
||||
title = "Timestamp queries";
|
||||
}
|
||||
|
||||
HPPTimestampQueries::~HPPTimestampQueries()
|
||||
{
|
||||
if (has_device() && get_device().get_handle())
|
||||
{
|
||||
vk::Device device = get_device().get_handle();
|
||||
|
||||
time_stamps.destroy(device);
|
||||
bloom.destroy(device);
|
||||
composition.destroy(device);
|
||||
filter_pass.destroy(device);
|
||||
models.destroy(device, descriptor_pool);
|
||||
offscreen.destroy(device);
|
||||
textures.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
bool HPPTimestampQueries::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
assert(!prepared);
|
||||
if (HPPApiVulkanSample::prepare(options))
|
||||
{
|
||||
// Check if the selected device supports timestamps. A value of zero means no support.
|
||||
vk::PhysicalDeviceLimits const &device_limits = get_device().get_gpu().get_properties().limits;
|
||||
if (device_limits.timestampPeriod == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected device does not support timestamp queries!"};
|
||||
}
|
||||
|
||||
// Check if all queues support timestamp queries, if not we need to check on a per-queue basis
|
||||
if (!device_limits.timestampComputeAndGraphics)
|
||||
{
|
||||
// Check if the graphics queue used in this sample supports time stamps
|
||||
vk::QueueFamilyProperties const &graphics_queue_family_properties = get_device().get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0).get_properties();
|
||||
if (graphics_queue_family_properties.timestampValidBits == 0)
|
||||
{
|
||||
throw std::runtime_error{"The selected graphics queue family does not support timestamp queries!"};
|
||||
}
|
||||
}
|
||||
|
||||
prepare_camera();
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
prepare_offscreen_buffer();
|
||||
descriptor_pool = create_descriptor_pool();
|
||||
prepare_bloom();
|
||||
prepare_composition();
|
||||
prepare_models();
|
||||
prepare_time_stamps();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool HPPTimestampQueries::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
HPPApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = true;
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::build_command_buffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo command_buffer_begin_info;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
vk::CommandBuffer command_buffer = draw_cmd_buffers[i];
|
||||
command_buffer.begin(command_buffer_begin_info);
|
||||
|
||||
// Reset the timestamp query pool, so we can start fetching new values into it
|
||||
command_buffer.resetQueryPool(time_stamps.query_pool, 0, static_cast<uint32_t>(time_stamps.values.size()));
|
||||
|
||||
{
|
||||
/*
|
||||
First pass: Render scene to offscreen framebuffer
|
||||
*/
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, 0);
|
||||
|
||||
std::array<vk::ClearValue, 3> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
||||
vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
||||
vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = offscreen.render_pass,
|
||||
.framebuffer = offscreen.framebuffer,
|
||||
.renderArea = {{0, 0}, offscreen.extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
|
||||
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(offscreen.extent.width), static_cast<float>(offscreen.extent.height), 0.0f, 1.0f};
|
||||
command_buffer.setViewport(0, viewport);
|
||||
|
||||
vk::Rect2D scissor{{0, 0}, offscreen.extent};
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
// Skybox
|
||||
if (display_skybox)
|
||||
{
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, models.skybox.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, models.pipeline_layout, 0, models.skybox.descriptor_set, {});
|
||||
draw_model(models.skybox.meshes[0], command_buffer);
|
||||
}
|
||||
|
||||
// 3D object
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, models.objects.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, models.pipeline_layout, 0, models.objects.descriptor_set, {});
|
||||
draw_model(models.objects.meshes[models.object_index], command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
Second render pass: First bloom pass
|
||||
*/
|
||||
if (bloom.enabled)
|
||||
{
|
||||
vk::ClearValue clear_value(vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})));
|
||||
|
||||
// Bloom filter
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, 2);
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = filter_pass.render_pass,
|
||||
.framebuffer = filter_pass.framebuffer,
|
||||
.renderArea = {{0, 0}, filter_pass.extent},
|
||||
.clearValueCount = 1,
|
||||
.pClearValues = &clear_value};
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
|
||||
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(filter_pass.extent.width), static_cast<float>(filter_pass.extent.height), 0.0f, 1.0f};
|
||||
command_buffer.setViewport(0, viewport);
|
||||
|
||||
vk::Rect2D scissor{{0, 0}, filter_pass.extent};
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, bloom.pipeline_layout, 0, bloom.descriptor_set, {});
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, bloom.pipelines[1]);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, 3);
|
||||
}
|
||||
|
||||
/*
|
||||
Note: Explicit synchronization is not required between the render pass, as this is done implicitly via sub pass dependencies
|
||||
*/
|
||||
|
||||
/*
|
||||
Third render pass: Scene rendering with applied second bloom pass (when enabled)
|
||||
*/
|
||||
{
|
||||
std::array<vk::ClearValue, 2> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
|
||||
vk::ClearDepthStencilValue{0.0f, 0}}};
|
||||
|
||||
// Final composition
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, time_stamps.query_pool, bloom.enabled ? 4 : 2);
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_begin_info{.renderPass = render_pass,
|
||||
.framebuffer = framebuffers[i],
|
||||
.renderArea = {{0, 0}, extent},
|
||||
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
|
||||
.pClearValues = clear_values.data()};
|
||||
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
|
||||
|
||||
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f};
|
||||
command_buffer.setViewport(0, viewport);
|
||||
|
||||
vk::Rect2D scissor{{0, 0}, extent};
|
||||
command_buffer.setScissor(0, scissor);
|
||||
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, composition.pipeline_layout, 0, composition.descriptor_set, {});
|
||||
|
||||
// Scene
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, composition.pipeline);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
|
||||
// Bloom
|
||||
if (bloom.enabled)
|
||||
{
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, bloom.pipelines[0]);
|
||||
command_buffer.draw(3, 1, 0, 0);
|
||||
}
|
||||
|
||||
draw_ui(command_buffer);
|
||||
|
||||
command_buffer.endRenderPass();
|
||||
|
||||
command_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, time_stamps.query_pool, bloom.enabled ? 5 : 3);
|
||||
}
|
||||
|
||||
command_buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.combo_box("Object type", &models.object_index, object_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.input_float("Exposure", &ubo_params.exposure, 0.025f, "%.3f"))
|
||||
{
|
||||
update_params();
|
||||
}
|
||||
if (drawer.checkbox("Bloom", &bloom.enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Skybox", &display_skybox))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
if (drawer.header("timing"))
|
||||
{
|
||||
// Timestamps don't have a time unit themselves, but are read as timesteps
|
||||
// The timestampPeriod property of the device tells how many nanoseconds such a timestep translates to on the selected device
|
||||
float timestampFrequency = get_device().get_gpu().get_properties().limits.timestampPeriod;
|
||||
|
||||
drawer.text("Pass 1: Offscreen scene rendering: %.3f ms", static_cast<float>(time_stamps.values[1] - time_stamps.values[0]) * timestampFrequency / 1000000.0f);
|
||||
drawer.text("Pass 2: %s %.3f ms", (bloom.enabled ? "First bloom pass" : "Scene display"), static_cast<float>(time_stamps.values[3] - time_stamps.values[2]) * timestampFrequency / 1000000.0f);
|
||||
if (bloom.enabled)
|
||||
{
|
||||
drawer.text("Pass 3: Second bloom pass %.3f ms", static_cast<float>(time_stamps.values[5] - time_stamps.values[4]) * timestampFrequency / 1000000.0f);
|
||||
drawer.set_dirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::render(float delta_time)
|
||||
{
|
||||
if (prepared)
|
||||
{
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DeviceMemory HPPTimestampQueries::allocate_memory(vk::Image image)
|
||||
{
|
||||
vk::MemoryRequirements memory_requirements = get_device().get_handle().getImageMemoryRequirements(image);
|
||||
|
||||
vk::MemoryAllocateInfo memory_allocate_info{.allocationSize = memory_requirements.size,
|
||||
.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits,
|
||||
vk::MemoryPropertyFlagBits::eDeviceLocal)};
|
||||
|
||||
return get_device().get_handle().allocateMemory(memory_allocate_info);
|
||||
}
|
||||
|
||||
HPPTimestampQueries::FramebufferAttachment HPPTimestampQueries::create_attachment(vk::Format format, vk::ImageUsageFlagBits usage)
|
||||
{
|
||||
vk::Image image = create_image(format, usage);
|
||||
vk::DeviceMemory memory = allocate_memory(image);
|
||||
get_device().get_handle().bindImageMemory(image, memory, 0);
|
||||
vk::ImageView view =
|
||||
vkb::common::create_image_view(get_device().get_handle(), image, vk::ImageViewType::e2D, format, vkb::common::get_image_aspect_flags(usage, format));
|
||||
|
||||
return {format, image, memory, view};
|
||||
}
|
||||
|
||||
vk::DescriptorPool HPPTimestampQueries::create_descriptor_pool()
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 4}, {vk::DescriptorType::eCombinedImageSampler, 6}}};
|
||||
return get_device().get_handle().createDescriptorPool(
|
||||
{.maxSets = 4, .poolSizeCount = static_cast<uint32_t>(pool_sizes.size()), .pPoolSizes = pool_sizes.data()});
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTimestampQueries::create_bloom_pipeline(uint32_t direction)
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "bloom.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("hdr", "bloom.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Set constant parameters via specialization constants
|
||||
vk::SpecializationMapEntry specialization_map_entry{0, 0, sizeof(uint32_t)};
|
||||
|
||||
vk::SpecializationInfo specialization_info{1, &specialization_map_entry, sizeof(uint32_t), &direction};
|
||||
shader_stages[1].pSpecializationInfo = &specialization_info;
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.blendEnable = true,
|
||||
.srcColorBlendFactor = vk::BlendFactor::eOne,
|
||||
.dstColorBlendFactor = vk::BlendFactor::eOne,
|
||||
.colorBlendOp = vk::BlendOp::eAdd,
|
||||
.srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha,
|
||||
.dstAlphaBlendFactor = vk::BlendFactor::eDstAlpha,
|
||||
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
// Empty vertex input state, full screen triangles are generated by the vertex shader
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
{},
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eFront,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
bloom.pipeline_layout,
|
||||
direction == 1 ? render_pass : filter_pass.render_pass);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTimestampQueries::create_composition_pipeline()
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "composition.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("hdr", "composition.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
// Empty vertex input state, full screen triangles are generated by the vertex shader
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
{},
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eFront,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
{blend_attachment_state},
|
||||
depth_stencil_state,
|
||||
composition.pipeline_layout,
|
||||
render_pass);
|
||||
}
|
||||
|
||||
vk::RenderPass HPPTimestampQueries::create_filter_render_pass()
|
||||
{
|
||||
// Set up separate renderpass with references to the color and depth attachments
|
||||
vk::AttachmentDescription attachment_description{.format = filter_pass.color.format,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
|
||||
vk::AttachmentReference color_reference{.attachment = 0, .layout = vk::ImageLayout::eColorAttachmentOptimal};
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, .colorAttachmentCount = 1, .pColorAttachments = &color_reference};
|
||||
|
||||
return create_render_pass({attachment_description}, subpass);
|
||||
}
|
||||
|
||||
vk::Image HPPTimestampQueries::create_image(vk::Format format, vk::ImageUsageFlagBits usage)
|
||||
{
|
||||
vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent = {offscreen.extent.width, offscreen.extent.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = usage | vk::ImageUsageFlagBits::eSampled};
|
||||
|
||||
return get_device().get_handle().createImage(image_create_info);
|
||||
}
|
||||
|
||||
vk::Pipeline HPPTimestampQueries::create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite)
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages{load_shader("hdr", "gbuffer.vert.spv", vk::ShaderStageFlagBits::eVertex),
|
||||
load_shader("hdr", "gbuffer.frag.spv", vk::ShaderStageFlagBits::eFragment)};
|
||||
|
||||
// Set constant parameters via specialization constants
|
||||
vk::SpecializationMapEntry specialization_map_entry{0, 0, sizeof(uint32_t)};
|
||||
|
||||
// Set constant parameters via specialization constants
|
||||
vk::SpecializationInfo specialization_info{1, &specialization_map_entry, sizeof(uint32_t), &shaderType};
|
||||
shader_stages[0].pSpecializationInfo = &specialization_info;
|
||||
shader_stages[1].pSpecializationInfo = &specialization_info;
|
||||
|
||||
// Vertex bindings an attributes for model rendering
|
||||
// Binding description
|
||||
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
|
||||
|
||||
// Attribute descriptions
|
||||
std::vector<vk::VertexInputAttributeDescription> vertex_input_attributes = {{0, 0, vk::Format::eR32G32B32Sfloat, 0},
|
||||
{1, 0, vk::Format::eR32G32B32Sfloat, 3 * sizeof(float)}};
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_state{.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &vertex_input_binding,
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size()),
|
||||
.pVertexAttributeDescriptions = vertex_input_attributes.data()};
|
||||
|
||||
std::vector<vk::PipelineColorBlendAttachmentState> blend_attachment_states(2);
|
||||
blend_attachment_states[0].colorWriteMask =
|
||||
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
||||
blend_attachment_states[1].colorWriteMask = blend_attachment_states[0].colorWriteMask;
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state;
|
||||
depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater;
|
||||
depth_stencil_state.depthWriteEnable = depthTestAndWrite;
|
||||
depth_stencil_state.depthTestEnable = depthTestAndWrite;
|
||||
depth_stencil_state.back.compareOp = vk::CompareOp::eAlways;
|
||||
depth_stencil_state.front = depth_stencil_state.back;
|
||||
|
||||
return vkb::common::create_graphics_pipeline(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
shader_stages,
|
||||
vertex_input_state,
|
||||
vk::PrimitiveTopology::eTriangleList,
|
||||
0,
|
||||
vk::PolygonMode::eFill,
|
||||
cullMode,
|
||||
vk::FrontFace::eCounterClockwise,
|
||||
blend_attachment_states,
|
||||
depth_stencil_state,
|
||||
models.pipeline_layout,
|
||||
offscreen.render_pass);
|
||||
}
|
||||
|
||||
vk::RenderPass HPPTimestampQueries::create_offscreen_render_pass()
|
||||
{
|
||||
// Set up separate renderpass with references to the color and depth attachments
|
||||
std::vector<vk::AttachmentDescription> attachment_descriptions(3);
|
||||
|
||||
// Init attachment properties
|
||||
for (uint32_t i = 0; i < 3; ++i)
|
||||
{
|
||||
attachment_descriptions[i].samples = vk::SampleCountFlagBits::e1;
|
||||
attachment_descriptions[i].loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment_descriptions[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachment_descriptions[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachment_descriptions[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
attachment_descriptions[i].initialLayout = vk::ImageLayout::eUndefined;
|
||||
attachment_descriptions[i].finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
attachment_descriptions[2].finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
// Formats
|
||||
attachment_descriptions[0].format = offscreen.color[0].format;
|
||||
attachment_descriptions[1].format = offscreen.color[1].format;
|
||||
attachment_descriptions[2].format = offscreen.depth.format;
|
||||
|
||||
std::array<vk::AttachmentReference, 2> color_references{{{0, vk::ImageLayout::eColorAttachmentOptimal},
|
||||
{1, vk::ImageLayout::eColorAttachmentOptimal}}};
|
||||
|
||||
vk::AttachmentReference depth_reference{2, vk::ImageLayout::eDepthStencilAttachmentOptimal};
|
||||
|
||||
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
.colorAttachmentCount = static_cast<uint32_t>(color_references.size()),
|
||||
.pColorAttachments = color_references.data(),
|
||||
.pDepthStencilAttachment = &depth_reference};
|
||||
|
||||
return create_render_pass(attachment_descriptions, subpass);
|
||||
}
|
||||
|
||||
vk::RenderPass HPPTimestampQueries::create_render_pass(std::vector<vk::AttachmentDescription> const &attachment_descriptions, vk::SubpassDescription const &subpass_description)
|
||||
{
|
||||
// Use subpass dependencies for attachment layout transitions
|
||||
std::array<vk::SubpassDependency, 2> subpass_dependencies;
|
||||
|
||||
subpass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
subpass_dependencies[0].dstSubpass = 0;
|
||||
// End of previous commands
|
||||
subpass_dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eBottomOfPipe;
|
||||
subpass_dependencies[0].srcAccessMask = vk::AccessFlagBits::eNoneKHR;
|
||||
// Read/write from/to depth
|
||||
subpass_dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
||||
subpass_dependencies[0].dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||
// Write to attachment
|
||||
subpass_dependencies[0].dstStageMask |= vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
subpass_dependencies[0].dstAccessMask |= vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
|
||||
subpass_dependencies[1].srcSubpass = 0;
|
||||
subpass_dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
// End of write to attachment
|
||||
subpass_dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
subpass_dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
// Attachment later read using sampler in 'bloom[0]' pipeline
|
||||
subpass_dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
subpass_dependencies[1].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
|
||||
vk::RenderPassCreateInfo render_pass_create_info{.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size()),
|
||||
.pAttachments = attachment_descriptions.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass_description,
|
||||
.dependencyCount = static_cast<uint32_t>(subpass_dependencies.size()),
|
||||
.pDependencies = subpass_dependencies.data()};
|
||||
|
||||
return get_device().get_handle().createRenderPass(render_pass_create_info);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::draw()
|
||||
{
|
||||
HPPApiVulkanSample::prepare_frame();
|
||||
|
||||
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
|
||||
queue.submit(submit_info);
|
||||
|
||||
HPPApiVulkanSample::submit_frame();
|
||||
|
||||
// Read back the time stamp query results after the frame is finished
|
||||
get_time_stamp_results();
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::get_time_stamp_results()
|
||||
{
|
||||
// The number of timestamps changes if the bloom pass is disabled
|
||||
uint32_t count = static_cast<uint32_t>(bloom.enabled ? time_stamps.values.size() : time_stamps.values.size() - 2);
|
||||
|
||||
// Fetch the time stamp results written in the command buffer submissions
|
||||
// A note on the flags used:
|
||||
// vk::QueryResultFlagBits::e64: Results will have 64 bits. As time stamp values are on nano-seconds, this flag should always be used to avoid 32 bit overflows
|
||||
// vk::QueryResultFlagBits::eWait: Since we want to immediately display the results, we use this flag to have the CPU wait until the results are available
|
||||
vk::Result result = get_device().get_handle().getQueryPoolResults(time_stamps.query_pool,
|
||||
0,
|
||||
count,
|
||||
time_stamps.values.size() * sizeof(uint64_t),
|
||||
time_stamps.values.data(),
|
||||
sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
|
||||
assert(result == vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::load_assets()
|
||||
{
|
||||
// Models
|
||||
models.skybox.meshes.emplace_back(load_model("scenes/cube.gltf"));
|
||||
std::vector<std::string> filenames = {"geosphere.gltf", "teapot.gltf", "torusknot.gltf"};
|
||||
object_names = {"Sphere", "Teapot", "Torusknot"};
|
||||
for (auto file : filenames)
|
||||
{
|
||||
models.objects.meshes.emplace_back(load_model("scenes/" + file));
|
||||
}
|
||||
|
||||
// Transforms
|
||||
auto geosphere_matrix = glm::mat4(1.0f);
|
||||
models.transforms.push_back(geosphere_matrix);
|
||||
|
||||
auto teapot_matrix = glm::mat4(1.0f);
|
||||
teapot_matrix = glm::scale(teapot_matrix, glm::vec3(10.0f, 10.0f, 10.0f));
|
||||
teapot_matrix = glm::rotate(teapot_matrix, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
models.transforms.push_back(teapot_matrix);
|
||||
|
||||
auto torus_matrix = glm::mat4(1.0f);
|
||||
models.transforms.push_back(torus_matrix);
|
||||
|
||||
// Load HDR cube map
|
||||
textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::scene_graph::components::HPPImage::Color);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_bloom()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
|
||||
vk::Device device = get_device().get_handle();
|
||||
bloom.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
bloom.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &bloom.descriptor_set_layout});
|
||||
bloom.pipelines[0] = create_bloom_pipeline(1);
|
||||
bloom.pipelines[1] = create_bloom_pipeline(0);
|
||||
bloom.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, bloom.descriptor_set_layout);
|
||||
update_bloom_descriptor_set();
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_camera()
|
||||
{
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(0.0f, 0.0f, -4.0f));
|
||||
camera.set_rotation(glm::vec3(0.0f, 180.0f, 0.0f));
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
|
||||
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 256.0f, 0.1f);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_composition()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
|
||||
vk::Device device = get_device().get_handle();
|
||||
composition.descriptor_set_layout =
|
||||
device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
composition.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &composition.descriptor_set_layout});
|
||||
composition.pipeline = create_composition_pipeline();
|
||||
composition.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, composition.descriptor_set_layout);
|
||||
update_composition_descriptor_set();
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_models()
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings = {{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment}}};
|
||||
|
||||
vk::Device device = get_device().get_handle();
|
||||
models.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.data()});
|
||||
models.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &models.descriptor_set_layout});
|
||||
|
||||
models.objects.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, models.descriptor_set_layout);
|
||||
update_model_descriptor_set(models.objects.descriptor_set);
|
||||
models.objects.pipeline = create_models_pipeline(1, vk::CullModeFlagBits::eFront, true);
|
||||
|
||||
models.skybox.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, models.descriptor_set_layout);
|
||||
update_model_descriptor_set(models.skybox.descriptor_set);
|
||||
models.skybox.pipeline = create_models_pipeline(0, vk::CullModeFlagBits::eBack, false);
|
||||
}
|
||||
|
||||
// Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer)
|
||||
void HPPTimestampQueries::prepare_offscreen_buffer()
|
||||
{
|
||||
{
|
||||
offscreen.extent = extent;
|
||||
|
||||
// Color attachments
|
||||
|
||||
// We are using two 128-Bit RGBA floating point color buffers for this sample
|
||||
// In a performance or bandwidth-limited scenario you should consider using a format with lower precision
|
||||
offscreen.color[0] = create_attachment(vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eColorAttachment);
|
||||
offscreen.color[1] = create_attachment(vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eColorAttachment);
|
||||
// Depth attachment
|
||||
offscreen.depth = create_attachment(depth_format, vk::ImageUsageFlagBits::eDepthStencilAttachment);
|
||||
|
||||
offscreen.render_pass = create_offscreen_render_pass();
|
||||
|
||||
offscreen.framebuffer = vkb::common::create_framebuffer(
|
||||
get_device().get_handle(), offscreen.render_pass, {offscreen.color[0].view, offscreen.color[1].view, offscreen.depth.view}, offscreen.extent);
|
||||
|
||||
// Create sampler to sample from the color attachments
|
||||
offscreen.sampler = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(),
|
||||
offscreen.color[0].format, vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Bloom separable filter pass
|
||||
{
|
||||
filter_pass.extent = extent;
|
||||
|
||||
// Color attachments - needs to be a blendable format, so choose from a priority ordered list
|
||||
const std::vector<vk::Format> float_format_priority_list = {
|
||||
vk::Format::eR32G32B32A32Sfloat,
|
||||
vk::Format::eR16G16B16A16Sfloat // Guaranteed blend support for this
|
||||
};
|
||||
|
||||
vk::Format color_format = vkb::common::choose_blendable_format(get_device().get_gpu().get_handle(), float_format_priority_list);
|
||||
|
||||
// One floating point color buffer
|
||||
filter_pass.color = create_attachment(color_format, vk::ImageUsageFlagBits::eColorAttachment);
|
||||
|
||||
filter_pass.render_pass = create_filter_render_pass();
|
||||
filter_pass.framebuffer = vkb::common::create_framebuffer(get_device().get_handle(), filter_pass.render_pass, {filter_pass.color.view}, filter_pass.extent);
|
||||
filter_pass.sampler = vkb::common::create_sampler(get_device().get_gpu().get_handle(), get_device().get_handle(),
|
||||
filter_pass.color.format, vk::Filter::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::prepare_time_stamps()
|
||||
{
|
||||
// Create the query pool object used to get the GPU time tamps
|
||||
time_stamps.query_pool =
|
||||
vkb::common::create_query_pool(get_device().get_handle(), vk::QueryType::eTimestamp, static_cast<uint32_t>(time_stamps.values.size()));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void HPPTimestampQueries::prepare_uniform_buffers()
|
||||
{
|
||||
// Matrices vertex shader uniform buffer
|
||||
uniform_buffers.matrices = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
sizeof(ubo_matrices),
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Params
|
||||
uniform_buffers.params = std::make_unique<vkb::core::BufferCpp>(get_device(),
|
||||
sizeof(ubo_params),
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
update_params();
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::update_composition_descriptor_set()
|
||||
{
|
||||
std::array<vk::DescriptorImageInfo, 2> color_descriptors = {{{offscreen.sampler, offscreen.color[0].view, vk::ImageLayout::eShaderReadOnlyOptimal},
|
||||
{offscreen.sampler, filter_pass.color.view, vk::ImageLayout::eShaderReadOnlyOptimal}}};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> sampler_write_descriptor_sets = {{{.dstSet = composition.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &color_descriptors[0]},
|
||||
{.dstSet = composition.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &color_descriptors[1]}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(sampler_write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::update_bloom_descriptor_set()
|
||||
{
|
||||
std::array<vk::DescriptorImageInfo, 2> color_descriptors = {{{offscreen.sampler, offscreen.color[0].view, vk::ImageLayout::eShaderReadOnlyOptimal},
|
||||
{offscreen.sampler, offscreen.color[1].view, vk::ImageLayout::eShaderReadOnlyOptimal}}};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> sampler_write_descriptor_sets = {{{.dstSet = bloom.descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &color_descriptors[0]},
|
||||
{.dstSet = bloom.descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &color_descriptors[1]}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(sampler_write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::update_model_descriptor_set(vk::DescriptorSet descriptor_set)
|
||||
{
|
||||
vk::DescriptorBufferInfo matrix_buffer_descriptor{uniform_buffers.matrices->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
vk::DescriptorImageInfo environment_image_descriptor{textures.envmap.sampler,
|
||||
textures.envmap.image->get_vk_image_view().get_handle(),
|
||||
descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler,
|
||||
textures.envmap.image->get_vk_image_view().get_format())};
|
||||
|
||||
vk::DescriptorBufferInfo params_buffer_descriptor{uniform_buffers.params->get_handle(), 0, vk::WholeSize};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = descriptor_set,
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = &matrix_buffer_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.pImageInfo = &environment_image_descriptor},
|
||||
{.dstSet = descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
||||
.pBufferInfo = ¶ms_buffer_descriptor}}};
|
||||
|
||||
get_device().get_handle().updateDescriptorSets(write_descriptor_sets, {});
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::update_params()
|
||||
{
|
||||
uniform_buffers.params->convert_and_update(ubo_params);
|
||||
}
|
||||
|
||||
void HPPTimestampQueries::update_uniform_buffers()
|
||||
{
|
||||
ubo_matrices.projection = camera.matrices.perspective;
|
||||
ubo_matrices.modelview = camera.matrices.view * models.transforms[models.object_index];
|
||||
ubo_matrices.skybox_modelview = camera.matrices.view;
|
||||
ubo_matrices.inverse_modelview = glm::inverse(camera.matrices.view);
|
||||
uniform_buffers.matrices->convert_and_update(ubo_matrices);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_timestamp_queries()
|
||||
{
|
||||
return std::make_unique<HPPTimestampQueries>();
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/* Copyright (c) 2023-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Timestamp queries (based on the HDR sample), using vulkan.hpp
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hpp_api_vulkan_sample.h>
|
||||
|
||||
class HPPTimestampQueries : public HPPApiVulkanSample
|
||||
{
|
||||
public:
|
||||
HPPTimestampQueries();
|
||||
~HPPTimestampQueries();
|
||||
|
||||
private:
|
||||
struct Bloom
|
||||
{
|
||||
bool enabled = true;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipelines[2];
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipelines[0]);
|
||||
device.destroyPipeline(pipelines[1]);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
struct Composition
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyPipeline(pipeline);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
}
|
||||
};
|
||||
|
||||
// Framebuffer for offscreen rendering
|
||||
struct FramebufferAttachment
|
||||
{
|
||||
vk::Format format = {};
|
||||
vk::Image image = {};
|
||||
vk::DeviceMemory mem = {};
|
||||
vk::ImageView view = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyImageView(view);
|
||||
device.destroyImage(image);
|
||||
device.freeMemory(mem);
|
||||
}
|
||||
};
|
||||
|
||||
struct FilterPass
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
vk::DescriptorSet descriptor_set = {};
|
||||
vk::Pipeline pipeline = {};
|
||||
std::vector<std::unique_ptr<vkb::scene_graph::components::HPPSubMesh>> meshes = {};
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
// no need to free the descriptor_set, as it's implicitly free'd with the descriptor_pool
|
||||
device.destroyPipeline(pipeline);
|
||||
}
|
||||
};
|
||||
|
||||
struct Models
|
||||
{
|
||||
vk::DescriptorSetLayout descriptor_set_layout = {};
|
||||
vk::PipelineLayout pipeline_layout = {};
|
||||
Geometry objects = {};
|
||||
Geometry skybox = {};
|
||||
std::vector<glm::mat4> transforms = {};
|
||||
int32_t object_index = 0;
|
||||
|
||||
void destroy(vk::Device device, vk::DescriptorPool descriptor_pool)
|
||||
{
|
||||
objects.destroy(device, descriptor_pool);
|
||||
skybox.destroy(device, descriptor_pool);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
}
|
||||
};
|
||||
|
||||
struct Offscreen
|
||||
{
|
||||
vk::Extent2D extent = {};
|
||||
vk::Framebuffer framebuffer = {};
|
||||
FramebufferAttachment color[2] = {};
|
||||
FramebufferAttachment depth = {};
|
||||
vk::RenderPass render_pass = {};
|
||||
vk::Sampler sampler = {};
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(sampler);
|
||||
device.destroyFramebuffer(framebuffer);
|
||||
device.destroyRenderPass(render_pass);
|
||||
color[0].destroy(device);
|
||||
color[1].destroy(device);
|
||||
depth.destroy(device);
|
||||
}
|
||||
};
|
||||
|
||||
struct Textures
|
||||
{
|
||||
HPPTexture envmap;
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroySampler(envmap.sampler);
|
||||
}
|
||||
};
|
||||
|
||||
struct TimeStamps
|
||||
{
|
||||
std::array<uint64_t, 6> values; // GPU time stamps will be stored in an array
|
||||
vk::QueryPool query_pool; // A query pool is required to use GPU time stamps
|
||||
|
||||
void destroy(vk::Device device)
|
||||
{
|
||||
device.destroyQueryPool(query_pool);
|
||||
}
|
||||
};
|
||||
|
||||
struct UBOMatrices
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skybox_modelview;
|
||||
glm::mat4 inverse_modelview;
|
||||
float modelscale = 0.05f;
|
||||
};
|
||||
|
||||
struct UBOParams
|
||||
{
|
||||
float exposure = 1.0f;
|
||||
};
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferCpp> matrices;
|
||||
std::unique_ptr<vkb::core::BufferCpp> params;
|
||||
};
|
||||
|
||||
private:
|
||||
// from vkb::Application
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
virtual bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
// from vkb::VulkanSample
|
||||
virtual void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
|
||||
|
||||
// from HPPApiVulkanSample
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual void render(float delta_time) override;
|
||||
|
||||
vk::DeviceMemory allocate_memory(vk::Image image);
|
||||
FramebufferAttachment create_attachment(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::DescriptorPool create_descriptor_pool();
|
||||
vk::Pipeline create_bloom_pipeline(uint32_t direction);
|
||||
vk::Pipeline create_composition_pipeline();
|
||||
vk::RenderPass create_filter_render_pass();
|
||||
vk::Image create_image(vk::Format format, vk::ImageUsageFlagBits usage);
|
||||
vk::Pipeline create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite);
|
||||
vk::RenderPass create_offscreen_render_pass();
|
||||
vk::RenderPass create_render_pass(std::vector<vk::AttachmentDescription> const &attachment_descriptions, vk::SubpassDescription const &subpass_description);
|
||||
void draw();
|
||||
void get_time_stamp_results();
|
||||
void load_assets();
|
||||
void prepare_bloom();
|
||||
void prepare_camera();
|
||||
void prepare_composition();
|
||||
void prepare_models();
|
||||
void prepare_offscreen_buffer();
|
||||
void prepare_time_stamps();
|
||||
void prepare_uniform_buffers();
|
||||
void update_composition_descriptor_set();
|
||||
void update_bloom_descriptor_set();
|
||||
void update_model_descriptor_set(vk::DescriptorSet descriptor_set);
|
||||
void update_params();
|
||||
void update_uniform_buffers();
|
||||
|
||||
private:
|
||||
Bloom bloom;
|
||||
Composition composition;
|
||||
bool display_skybox = true;
|
||||
FilterPass filter_pass;
|
||||
Models models;
|
||||
std::vector<std::string> object_names;
|
||||
Offscreen offscreen;
|
||||
Textures textures;
|
||||
TimeStamps time_stamps;
|
||||
UBOMatrices ubo_matrices;
|
||||
UBOParams ubo_params;
|
||||
UniformBuffers uniform_buffers;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_hpp_timestamp_queries();
|
||||
Reference in New Issue
Block a user