init
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2019-2021, 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.
|
||||
#
|
||||
|
||||
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(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Arm"
|
||||
NAME "Render Passes"
|
||||
DESCRIPTION "Appropriate use of render pass attachments."
|
||||
SHADER_FILES_GLSL
|
||||
"base.vert"
|
||||
"base.frag")
|
||||
@@ -0,0 +1,175 @@
|
||||
////
|
||||
- Copyright (c) 2019-2024, 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.
|
||||
-
|
||||
////
|
||||
= Appropriate use of render pass attachments
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/performance/render_passes[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Vulkan render-passes use attachments to describe input and output render targets.
|
||||
This sample shows how loading and storing attachments might affect performance on mobile.
|
||||
|
||||
During the creation of a render-pass, you can specify various color attachments and a depth-stencil attachment.
|
||||
Each of those is described by a https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkAttachmentDescription.html[`VkAttachmentDescription`] struct, which contains attributes to specify the https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkAttachmentLoadOp.html[load operation] (`loadOp`) and the https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkAttachmentStoreOp.html[store operation] (`storeOp`).
|
||||
This sample lets you choose between different combinations of these operations at runtime.
|
||||
|
||||
[,c]
|
||||
----
|
||||
VkAttachmentDescription desc = {};
|
||||
desc.loadOp = VK_ATTACHMENT_LOAD_OP_*;
|
||||
desc.storeOp = VK_ATTACHMENT_STORE_OP_*;
|
||||
----
|
||||
|
||||
== Color attachment load operation
|
||||
|
||||
The sample renders a scene with a render-pass using one color attachment, which is a swapchain image used for presentation.
|
||||
Since we do not need to read its content at the beginning of the pass, it would make sense to use `LOAD_OP_DONT_CARE` in order to avoid spending time loading it.
|
||||
|
||||
If we do not draw on the entire framebuffer, the frame might show random colors on the areas we do not draw on.
|
||||
In addition, it would show pixels drawn during previous frames.
|
||||
The solution consists in using `LOAD_OP_CLEAR` to clear the content of the framebuffer using a user-specified color.
|
||||
|
||||
[,c]
|
||||
----
|
||||
VkAttachmentDescription color_desc = {};
|
||||
color_desc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
|
||||
// Remember to set the clear value when beginning the render pass
|
||||
VkClearValue clear = {};
|
||||
clear.color = {0.5f, 0.5f, 0.5f, 1.0f};
|
||||
|
||||
VkRenderPassBeginInfo begin = {};
|
||||
begin.clearValueCount = 1;
|
||||
begin.pClearValues = &clear;
|
||||
----
|
||||
|
||||
Using the `LOAD_OP_LOAD` flag is the wrong choice in this case.
|
||||
Not only do we not use its content during this render-pass, it will cost us more in terms of bandwidth.
|
||||
|
||||
Below is a screenshot showing a scene rendered using `LOAD_OP_LOAD`:
|
||||
|
||||
image::./images/load_store.jpg[Using LOAD_OP_LOAD]
|
||||
|
||||
Comparing the read bandwidth values (_External Read Bytes_ graph), we observe a difference of `1533.9 - 933.7 = 600.2 MiB/s` if we select `LOAD_OP_CLEAR`
|
||||
|
||||
image::./images/clear_store.jpg[Using LOAD_OP_CLEAR]
|
||||
|
||||
We can estimate the bandwidth cost of loading/storing an uncompressed attachment as `width * height * bpp/8 * FPS [MiB/s]`.
|
||||
We calculate an estimate of `2220 * 1080 * (32/8) * ~60 = ~575 MiB/s`.
|
||||
The savings will be lower if the images are compressed, see xref:samples/performance/afbc/README.adoc[Enabling AFBC in your Vulkan Application].
|
||||
|
||||
== Depth attachment store operation
|
||||
|
||||
The render-pass also uses a depth attachment.
|
||||
In case we need to use it in a second render-pass, the right operation to set would be `STORE_OP_STORE`, because choosing `STORE_OP_DONT_CARE` means that the second render-pass will potentially load the wrong values.
|
||||
The sample does not have a second render-pass, therefore there is no need to store the depth attachment.
|
||||
|
||||
[,c]
|
||||
----
|
||||
VkAttachmentDescription depth_desc = {};
|
||||
depth_desc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
----
|
||||
|
||||
It is worth noticing that we can create a depth image with the `LAZILY_ALLOCATED` memory property, which means that it will be allocated by tile-based GPUs only if it is actually stored (by using `STORE_OP_STORE`).
|
||||
|
||||
[,c]
|
||||
----
|
||||
VmaAllocationCreateInfo depth_alloc = {};
|
||||
depth_alloc.preferredFlags = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
|
||||
----
|
||||
|
||||
image::./images/clear_dont_care.jpg[Using LOAD_OP_CLEAR and STORE_OP_DONT_CARE]
|
||||
|
||||
In this case the write transactions were reduced by `986.3 - 431.5 = 554.8 MiB/s`, again what we would roughly expect from storing the size of an uncompressed image at ~60 FPS.
|
||||
|
||||
The streamline trace shows us a more in-depth analysis of what is going on in the GPU.
|
||||
The delta between `LOAD_OP_LOAD` and `LOAD_OP_CLEAR` is evident at 10.4s having consistently less external reads.
|
||||
The delta between `STORE_OP_STORE` and `STORE_OP_DONT_CARE` is clear at 18.1s with the external write graphs plunging down.
|
||||
|
||||
image::./images/render_passes_streamline.png[Streamline]
|
||||
|
||||
== `vkCmdClear*` functions
|
||||
|
||||
Using the `vkCmdClear*` to clear the attachments is not needed as you can get the same result by using `LOAD_OP_CLEAR`.
|
||||
The following screenshot shows that by using that command the GPU will need ~6 million more fragment cycles per second.
|
||||
|
||||
image::./images/vk_cmd_clear.png[vkCmdClear]
|
||||
|
||||
While the `vkCmdClear*` functions can be used to clear images explicitly, on certain mobile devices this will result in a per-fragment clear shader which results in the additional workload demonstrated in the above screenshot.
|
||||
Despite this, the `vkCmdClear*` functions do have uses which are not covered by the loadOp operations, for example the `vkCmdClearAttachments` function can be used to clear a specific region within an attachment during a render pass.
|
||||
|
||||
== Depth image usage
|
||||
|
||||
Beyond setting the depth image usage bit to specify that it can be used as a `DEPTH_STENCIL_ATTACHMENT`, we can set the `TRANSIENT_ATTACHMENT` bit to tell the GPU that it can be used as a transient attachment which will only live for the duration of a single render-pass.
|
||||
Then if this is backed by `LAZILY_ALLOCATED` memory it will not even need physical storage.
|
||||
|
||||
[,c]
|
||||
----
|
||||
VkImageCreateInfo depth_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
depth_info.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
----
|
||||
|
||||
== Render area granularity
|
||||
|
||||
The render area provided to the render pass begin info struct should be tested against the `vkGetRenderAreaGranularity` to confirm that it is an optimal size.
|
||||
A render area is optimal when it satisfies all of the following conditions:
|
||||
|
||||
* The `offset.x` member in `renderArea` is a multiple of the width member of the horizontal granularity.
|
||||
* The `offset.y` member in `renderArea` is a multiple of the height of the vertical granularity.
|
||||
* Either the `extent.width` member in `renderArea` is a multiple of the horizontal granularity or `offset.x` + `extent.width` is equal to the width of the framebuffer in the `VkRenderPassBeginInfo`.
|
||||
* Either the `extent.height` member in `renderArea` is a multiple of the vertical granularity or `offset.y` + `extent.height` is equal to the height of the framebuffer in the `VkRenderPassBeginInfo`.
|
||||
|
||||
A non optimal render area may cause a negative impact to performance.
|
||||
More information on this is available https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkGetRenderAreaGranularity.html[here] and https://vulkan.lunarg.com/doc/view/1.0.33.0/linux/vkspec.chunked/ch07s04.html[here].
|
||||
|
||||
== Best-practice summary
|
||||
|
||||
*Do*
|
||||
|
||||
* Clear or invalidate each attachment at the start of a render pass using `loadOp = LOAD_OP_CLEAR` or `loadOp = LOAD_OP_DONT_CARE`.
|
||||
* Ensure that color/depth/stencil writes are not masked when clearing;
|
||||
you must clear the entire content of an attachment to get a fast clear of the tile memory.
|
||||
* Set the `VK_ATTACHMENT_LOAD_OP_DONT_CARE` flag to attachments not used as input for the render-pass.
|
||||
* Set up any attachment which is only live for the duration of a single render pass as a `TRANSIENT_ATTACHMENT` backed by `LAZILY_ALLOCATED` memory and ensure that the contents are invalidated at the end of the render pass using `storeOp = STORE_OP_DONT_CARE`.
|
||||
* If you know you are rendering to a sub-region of framebuffer use a scissor box to restrict the area of clearing and rendering required.
|
||||
|
||||
*Don't*
|
||||
|
||||
* Use `vkCmdClearColorImage()` or `vkCmdClearDepthStencilImage()` for any image which is used inside a render pass later;
|
||||
move the clear to the render pass `loadOp` setting.
|
||||
* Use `vkCmdClearAttachments()` inside a render pass when not needed as this is not free, unlike a clear or invalidate load operation.
|
||||
* Clear a render pass by manually writing a constant color using a shader program.
|
||||
* Use `loadOp = LOAD_OP_LOAD` unless your algorithm actually relies on the initial framebuffer state.
|
||||
* Set `loadOp` or `storeOp` for attachments which are not actually needed in the render pass;
|
||||
you'll generate a needless round-trip via tile-memory for that attachment.
|
||||
* Use `vkCmdBlitImage` as a way of upscaling a low-resolution game frame to native resolution if you will render UI/HUD directly on top of it with `loadOp = LOAD_OP_LOAD`;
|
||||
this will be an unnecessary round-trip to memory.
|
||||
|
||||
*Impact*
|
||||
|
||||
* Correct handling of render passes is critical;
|
||||
failing to follow this advice can result in significantly lower fragment shading performance and increased memory bandwidth due to the need to read non-cleared attachments into the tile memory at the start of rendering and write out non-invalidated attachments at the end of rendering.
|
||||
|
||||
*Debugging*
|
||||
|
||||
* Review API usage of https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkAttachmentDescription.html[attachments description].
|
||||
* Review API usage of https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCreateRenderPass.html[render pass creation], and any use of https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdClearColorImage.html[`vkCmdClearColorImage()`], https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdClearDepthStencilImage.html[`vkCmdClearDepthStencilImage()`] and https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdClearAttachments.html[`vkCmdClearAttachments()`].
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 260 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,179 @@
|
||||
/* Copyright (c) 2018-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 "render_passes.h"
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "filesystem/legacy.h"
|
||||
#include "gltf_loader.h"
|
||||
#include "gui.h"
|
||||
|
||||
#include "rendering/subpasses/forward_subpass.h"
|
||||
#include "stats/stats.h"
|
||||
|
||||
RenderPassesSample::RenderPassesSample()
|
||||
{
|
||||
auto &config = get_configuration();
|
||||
|
||||
config.insert<vkb::BoolSetting>(0, cmd_clear, false);
|
||||
config.insert<vkb::IntSetting>(0, load.value, 0);
|
||||
config.insert<vkb::IntSetting>(0, store.value, 0);
|
||||
|
||||
config.insert<vkb::BoolSetting>(1, cmd_clear, true);
|
||||
config.insert<vkb::IntSetting>(1, load.value, 1);
|
||||
config.insert<vkb::IntSetting>(1, store.value, 1);
|
||||
}
|
||||
|
||||
void RenderPassesSample::reset_stats_view()
|
||||
{
|
||||
if (load.value == VK_ATTACHMENT_LOAD_OP_LOAD)
|
||||
{
|
||||
get_gui().get_stats_view().reset_max_value(vkb::StatIndex::gpu_ext_read_bytes);
|
||||
}
|
||||
|
||||
if (store.value == VK_ATTACHMENT_STORE_OP_STORE)
|
||||
{
|
||||
get_gui().get_stats_view().reset_max_value(vkb::StatIndex::gpu_ext_write_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPassesSample::draw_gui()
|
||||
{
|
||||
auto lines = radio_buttons.size() + 1 /* checkbox */;
|
||||
if (camera->get_aspect_ratio() < 1.0f)
|
||||
{
|
||||
// In portrait, show buttons below heading
|
||||
lines = lines * 2;
|
||||
}
|
||||
|
||||
get_gui().show_options_window(
|
||||
/* body = */ [this, lines]() {
|
||||
// Checkbox vkCmdClear
|
||||
ImGui::Checkbox("Use vkCmdClearAttachments (color)", &cmd_clear);
|
||||
|
||||
// For every option set
|
||||
for (size_t i = 0; i < radio_buttons.size(); ++i)
|
||||
{
|
||||
// Avoid conflicts between buttons with identical labels
|
||||
ImGui::PushID(vkb::to_u32(i));
|
||||
|
||||
auto &radio_button = radio_buttons[i];
|
||||
|
||||
ImGui::Text("%s: ", radio_button->description);
|
||||
|
||||
if (camera->get_aspect_ratio() > 1.0f)
|
||||
{
|
||||
// In landscape, show all options following the heading
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
// For every option
|
||||
for (size_t j = 0; j < radio_button->options.size(); ++j)
|
||||
{
|
||||
ImGui::RadioButton(radio_button->options[j], &radio_button->value, vkb::to_u32(j));
|
||||
|
||||
if (j < radio_button->options.size() - 1)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
},
|
||||
/* lines = */ vkb::to_u32(lines));
|
||||
}
|
||||
|
||||
bool RenderPassesSample::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!VulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
get_stats().request_stats({vkb::StatIndex::gpu_fragment_cycles,
|
||||
vkb::StatIndex::gpu_ext_read_bytes,
|
||||
vkb::StatIndex::gpu_ext_write_bytes});
|
||||
|
||||
load_scene("scenes/sponza/Sponza01.gltf");
|
||||
|
||||
auto &camera_node = vkb::add_free_camera(get_scene(), "main_camera", get_render_context().get_surface_extent());
|
||||
camera = dynamic_cast<vkb::sg::PerspectiveCamera *>(&camera_node.get_component<vkb::sg::Camera>());
|
||||
|
||||
vkb::ShaderSource vert_shader("base.vert.spv");
|
||||
vkb::ShaderSource frag_shader("base.frag.spv");
|
||||
auto scene_subpass = std::make_unique<vkb::ForwardSubpass>(get_render_context(), std::move(vert_shader), std::move(frag_shader), get_scene(), *camera);
|
||||
|
||||
auto render_pipeline = std::make_unique<vkb::RenderPipeline>();
|
||||
render_pipeline->add_subpass(std::move(scene_subpass));
|
||||
|
||||
set_render_pipeline(std::move(render_pipeline));
|
||||
|
||||
create_gui(*window, &get_stats());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderPassesSample::draw_renderpass(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target)
|
||||
{
|
||||
std::vector<vkb::LoadStoreInfo> load_store{2};
|
||||
|
||||
// The load operation for the color attachment is selected by the user at run-time
|
||||
auto loadop = static_cast<VkAttachmentLoadOp>(load.value);
|
||||
load_store[0].load_op = loadop;
|
||||
load_store[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
|
||||
load_store[1].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
// Store operation for depth attachment is selected by the user at run-time
|
||||
load_store[1].store_op = static_cast<VkAttachmentStoreOp>(store.value);
|
||||
|
||||
get_render_pipeline().set_load_store(load_store);
|
||||
|
||||
auto &extent = render_target.get_extent();
|
||||
|
||||
set_viewport_and_scissor(command_buffer, extent);
|
||||
|
||||
auto &subpasses = get_render_pipeline().get_subpasses();
|
||||
command_buffer.begin_render_pass(render_target, load_store, get_render_pipeline().get_clear_value(), subpasses);
|
||||
|
||||
if (cmd_clear)
|
||||
{
|
||||
VkClearAttachment attachment = {};
|
||||
// Clear color only
|
||||
attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
attachment.clearValue = {0, 0, 0};
|
||||
attachment.colorAttachment = 0;
|
||||
|
||||
VkClearRect rect = {};
|
||||
rect.layerCount = 1;
|
||||
rect.rect.extent = extent;
|
||||
|
||||
command_buffer.clear(attachment, rect);
|
||||
}
|
||||
|
||||
assert(!subpasses.empty());
|
||||
subpasses[0]->draw(command_buffer);
|
||||
|
||||
get_gui().draw(command_buffer);
|
||||
|
||||
command_buffer.end_render_pass();
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_render_passes()
|
||||
{
|
||||
return std::make_unique<RenderPassesSample>();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* Copyright (c) 2018-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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iomanip> // setprecision
|
||||
#include <sstream> // stringstream
|
||||
|
||||
#include "scene_graph/components/perspective_camera.h"
|
||||
#include "vulkan_sample.h"
|
||||
|
||||
/**
|
||||
* @brief Appropriate use of render pass attachment operations
|
||||
*/
|
||||
class RenderPassesSample : public vkb::VulkanSampleC
|
||||
{
|
||||
public:
|
||||
RenderPassesSample();
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
void draw_gui() override;
|
||||
|
||||
/**
|
||||
* @brief Struct that contains radio button labeling
|
||||
* and the value which one is selected
|
||||
*/
|
||||
struct RadioButtonGroup
|
||||
{
|
||||
const char *description;
|
||||
|
||||
std::vector<const char *> options;
|
||||
|
||||
int value;
|
||||
};
|
||||
|
||||
struct CheckBox
|
||||
{
|
||||
const char *description;
|
||||
|
||||
bool value;
|
||||
};
|
||||
|
||||
private:
|
||||
void reset_stats_view() override;
|
||||
|
||||
void draw_renderpass(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target) override;
|
||||
|
||||
vkb::sg::PerspectiveCamera *camera{nullptr};
|
||||
|
||||
// Whether to use vkCmdClear or not
|
||||
bool cmd_clear = false;
|
||||
|
||||
RadioButtonGroup load{
|
||||
"Color attachment load operation",
|
||||
{"Load", "Clear", "Don't care"},
|
||||
0};
|
||||
|
||||
RadioButtonGroup store{
|
||||
"Depth attachment store operation",
|
||||
{"Store", "Don't care"},
|
||||
1};
|
||||
|
||||
std::vector<RadioButtonGroup *> radio_buttons = {&load, &store};
|
||||
|
||||
float frame_rate;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_render_passes();
|
||||
Reference in New Issue
Block a user