This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) 2020-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 "MSAA"
DESCRIPTION "How to efficiently use multisampling for MSAA."
SHADER_FILES_GLSL
"base.vert"
"base.frag"
"postprocessing/postprocessing.vert"
"postprocessing/outline.frag")
+217
View File
@@ -0,0 +1,217 @@
////
- Copyright (c) 2021-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.
-
////
= MSAA (Multisample anti-aliasing)
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/msaa[Khronos Vulkan samples github repository].
endif::[]
Aliasing is the result of under-sampling a signal.
In graphics this means computing the color of a pixel at a resolution that results in artifacts, commonly jaggies at model edges.
Multisample anti-aliasing (MSAA) is an efficient technique that reduces pixel sampling error.
In the figure below, the frame on the left was rendered with no anti-aliasing, whereas the same scene on the right uses 4X MSAA.
image::./images/example_comparison.png[No anti-aliasing (left) vs 4X MSAA (right)]
When computing the color of a pixel, the GPU will evaluate the color of a given primitive if it covers the centre coordinate of the pixel (and passes the depth test).
As shown in the figure below, with no anti-aliasing the fragment shader is evaluated for those pixels that pass this test, and they are shaded accordingly.
Depending on the pixel density, this single-sampled procedure may result in aliasing.
image::./images/steps_no_msaa.png[No anti-aliasing]
With multisample anti-aliasing, more than one location is tested within a pixel.
In the figure below there are four samples, so it is denoted 4X MSAA.
This effectively increases the resolution of each pixel, by storing a color value for each sample.
The fragment shader is still evaluated only once (using the centre coordinate) and the color result is stored as the value of those samples that lie within the primitive (and pass the depth test, which means the depth buffer also needs to be larger to accommodate multiple values per pixel).
In other words, the fragment shader value will be blended to all samples with coverage.
The final value for the pixel is calculated as the average of all samples.
This is known as the resolving step.
This results in different shades of primitive color at the edges, which reduces the aliasing effect.
image::./images/steps_msaa.png[4X MSAA]
In the figure above the samples within the pixel are positioned in a rotated grid.
Sampling coordinates are defined by the https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap24.html#primsrast-multisampling[spec].
Irregular patterns achieve https://pdfs.semanticscholar.org/ebd9/ddb08c4244fc7df00672cacb420212cdde54.pdf[better results] in horizontal and vertical edges.
Note that MSAA has no effect for pixels within the primitive, where all samples store the same color value.
MSAA is different from (and more efficient than) super-sampling anti-aliasing (SSAA) where the fragment shader is evaluated for each sample.
This would help reduce aliasing within primitives, but usually xref:samples/api/texture_mipmap_generation/README.adoc[mip-maps] mitigate this already.
To enable MSAA, first query http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap32.html#VkPhysicalDeviceLimits[`vkPhysicalDeviceLimits`] to select a supported level of MSAA e.g.
http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap32.html#VkSampleCountFlagBits[`VK_SAMPLE_COUNT_4_BIT`], and use this when creating the multisampled attachment, as well as when setting the http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap24.html#VkPipelineMultisampleStateCreateInfo[`rasterizationSamples`] member of http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap9.html#VkGraphicsPipelineCreateInfo[`pMultisampleState`] in the graphics pipeline.
As stated earlier for MSAA we do _not_ want to set http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap24.html#primsrast-sampleshading[sample shading] as this will enable the more expensive SSAA.
== Color resolve
4x MSAA can be particularly efficient in link:../pipeline_barriers/README.adoc#tile-based-rendering[tiler architectures], where the multi-sampled attachment is resolved in tile memory and can therefore be transient.
This is typically the case of the link:../render_passes/README.adoc#depth-attachment-store-operation[depth buffer] as shown below:
image:./images/no_msaa.png[No MSAA diagram] image:./images/screenshot_no_msaa.jpg[No MSAA sample]
It is important to avoid writing multisampled attachments back to main memory if they are not going to be needed after rendering the scene.
This means that the multisampled attachment must use `storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE` and `usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`, and allocate the image with the `LAZILY_ALLOCATED` memory property, as explained in the link:../render_passes/README.adoc#depth-attachment-store-operation[Render Passes tutorial].
----
// Multisampled attachment is transient
// This allows tilers to completely avoid writing out the multisampled attachment to memory,
// a considerable performance and bandwidth improvement
load_store[i_color_ms].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
----
To resolve color on write-back as shown below, configure the subpass so that http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkSubpassDescription[`pResolveAttachments`] points to the single-sampled attachment that we want the multisampled color to be resolved to, in this case the swapchain image.
----
// Good practice
// Enable write-back resolve to single-sampled attachment
subpass->set_color_resolve_attachments({i_swapchain});
----
image:./images/msaa_good.png[MSAA with write-back color resolve] image:./images/screenshot_color_writeback.jpg[MSAA with write-back color resolve sample]
With 4X MSAA enabled, we are rendering to a larger color attachment storing 4 color values for each pixel.
If this attachment remains in tile memory, the impact on performance remains minimal (3% bandwidth increase shown in the screenshots above) while the aliasing is considerably reduced at the edges.
As mentioned earlier this is due to the fact that the hardware can resolve (average the samples of) the multisampled attachment as the image is written back to main memory.
Vulkan offers an alternative way to explicitly define a separate resolve pass for the color attachment, using http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap18.html#vkCmdResolveImage[`vkCmdResolveImage`]:
----
// Bad practice
// Resolve multisampled attachment to destination, extremely expensive
vkCmdResolveImage(cmd_buf.get_handle(),
multisampled_img.get_handle(),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
swapchain_img.get_handle(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
to_u32(regions.size()), regions.data());
----
However this path requires storing the multisampled attachment (which in this case is 4 times larger than the framebuffer) at the end of the subpass and then read it back to the GPU in order to resolve it:
image:./images/msaa_bad.png[MSAA with color resolve in separate pass] image:./images/screenshot_color_separate.jpg[MSAA with separate color resolve sample]
This consumes much more bandwidth and is therefore not recommended if the same result can be achieved by using http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkSubpassDescription[`pResolveAttachments`] to resolve color on write-back.
To illustrate this the sample allows to toggle between resolving on write-back as opposed to in a separate pass, and to monitor the impact on bandwidth as a result.
On a high-end smartphone with a Mali-G76 as shown in the screenshots above, the difference in bandwidth could be explained as follows.
The sample is rendering 2168 x 1080 pixels which require 32 bits each (RGBA8, 4 bytes) at 60 FPS:
----
1X attachment: 2168 * 1080 * 4 * 60 = 562 bytes/s
----
This is multiplied by 4 if when we need to store 4 sample values per pixel:
----
4X attachment: 2168 * 1080 * 4 * 4 * 60 = 2247 bytes/s
----
Comparing the counter numbers shown in the screenshots above, both the read and write bandwidth increase by roughly the size of a 4X attachment, since the multisampled attachment needs to be written out at the end of the scene renderpass and then re-read to resolve the final color.
This means that a separate resolve pass is resulting in 5 GB/s increase in bandwidth.
Considering that in a mobile device such as this the external DDR bandwidth costs around 100 mW per GB/s, this overhead uses 500 mW (20%) out of an approximate 2.5 W device power budget, which is prohibitively expensive.
These counters can also be recorded with a profiler such as https://developer.arm.com/tools-and-software/graphics-and-gaming/arm-mobile-studio/components/streamline-performance-analyzer[Streamline], showing color resolve on write-back followed by separate resolve pass:
image::./images/streamline_writeback_separate.png[Streamline write-back resolve followed by separate resolve]
== Depth resolve
In all of the cases shown above the depth buffer has remained transient, regardless of MSAA.
This is because once the color is calculated and written out to the swapchain for presentation to the display, the depth can be discarded and therefore we recommend to link:../render_passes/README.adoc#depth-attachment-store-operation[configure load/store operations to avoid writing it out].
There are cases where we might need to save the depth attachment.
Consider a simple post-processing pass that samples both color and depth (bound as textures) in order to compute a screen-based effect such as https://en.wikipedia.org/wiki/Screen_space_ambient_occlusion[SSAO]:
image:./images/no_msaa_postprocessing.png[Postprocessing] image:./images/screenshot_no_msaa_postprocessing.jpg[Postprocessing sample]
In this case the increase in bandwidth corresponds to that of writing out 2 full-screen attachments, as expected.
With 4X MSAA the cost once again remains almost the same, as long as we remember to resolve both color and depth on write-back:
image:./images/msaa_good_postprocessing.png[Postprocessing with MSAA and write-back resolve of color and depth] image:./images/screenshot_color_depth_writeback.jpg[Postprocessing with MSAA and write-back resolve sample]
To resolve depth on write-back, http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap40.html#VK_KHR_depth_stencil_resolve[`VK_KHR_depth_stencil_resolve`] (promoted in http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap39.html#versions-1.2[Vulkan 1.2]) is required.
To configure the subpass, we must use a http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkSubpassDescription2[`VkSubpassDescription2`] and make `pNext` to point to a http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkSubpassDescriptionDepthStencilResolve[`VkSubpassDescriptionDepthStencilResolve`] structure.
This structure defines the single-sampled attachment that will be used to resolve depth:
----
// Good practice
// Multisampled attachment is transient
// This allows tilers to completely avoid writing out the multisampled attachment to memory,
// a considerable performance and bandwidth improvement
load_store[i_depth].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Enable write-back resolve to single-sampled attachment
subpass->set_depth_stencil_resolve_attachment(i_depth_resolve);
subpass->set_depth_stencil_resolve_mode(depth_resolve_mode);
----
Here we may also select how to resolve depth, by setting http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkResolveModeFlagBits[`depthResolveMode`] to one of the http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap32.html#VkPhysicalDeviceDepthStencilResolvePropertiesKHR[supported] options (the sample queries the device for supported modes and presents a drop-down selection list):
----
typedef enum VkResolveModeFlagBits {
VK_RESOLVE_MODE_NONE,
VK_RESOLVE_MODE_SAMPLE_ZERO_BIT,
VK_RESOLVE_MODE_AVERAGE_BIT,
VK_RESOLVE_MODE_MIN_BIT,
VK_RESOLVE_MODE_MAX_BIT
} VkResolveModeFlagBits;
----
In contrast to color, Vulkan does not offer an alternative way to resolve depth attachments (http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap18.html#vkCmdResolveImage[`vkCmdResolveImage`] does not support depth).
Therefore if http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap40.html#VK_KHR_depth_stencil_resolve[`VK_KHR_depth_stencil_resolve`] is not supported or properly configured, this pipeline will require an additional read-back of the multisampled depth attachment to carry out the post-processing effect:
image:./images/msaa_bad_postprocessing.png[Postprocessing with MSAA and color resolve in separate pass, no depth resolve] image:./images/screenshot_color_depth_separate.jpg[Postprocessing with MSAA no write-back resolve sample]
In the worst possible scenario shown above, where both multisampled depth and color are written out to main memory, the read bandwidth increases 2366 MiB/s (close to the bandwidth of a 4X attachment as calculated above) due to the color re-read required for separate resolve.
The write bandwidth increases 3951 MiB/s, which roughly corresponds to the difference between a 4X (2247 MiB/s) and a 1X (562 MiB/s) depth attachment (in this case depth is also 32bpp) i.e.
1685 MiB/s, plus the bandwidth required to write out an additional 4X color attachment i.e.
2247 MiB/s.
In total the read/write bandwidth increase is 6.3GB/s, a 302% increase with respect to the write-back resolve best practice and 630 mW of power (25% of budget) that could be saved to preserve battery life, achieve sustainable performance and an overall better user experience.
== Best practice summary
For most uses of multisampling it is possible to keep all of the data for the additional samples in the tile memory inside of the GPU, and resolve the value to a single pixel color as part of tile write-back.
This means that the additional bandwidth of those additional samples never hits external memory, which makes it exceptionally efficient.
MSAA can be integrated fully with Vulkan render passes, allowing a multisampled resolve to be explicitly specified at the end of a subpass.
*Do*
* Use 4x MSAA if possible;
it's not expensive and provides good image quality improvements.
* Use `loadOp = LOAD_OP_CLEAR` or `loadOp = LOAD_OP_DONT_CARE` for multisampled images.
* Use `storeOp = STORE_OP_DONT_CARE` for multisampled images.
* Use `LAZILY_ALLOCATED` memory to back the allocated multisampled images;
they do not need to be persisted into main memory and therefore do not need physical backing storage.
* Use `pResolveAttachments` in a subpass to automatically resolve a multisampled color buffer into a single-sampled color buffer.
* Use http://khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap40.html#VK_KHR_depth_stencil_resolve[`VK_KHR_depth_stencil_resolve`] in a subpass to automatically resolve a multisampled depth buffer into a single-sampled depth buffer.
Typically this is only useful if the depth buffer is going to be used further, in most cases it is transient and does not need to be resolved.
*Avoid*
* Avoid using `vkCmdResolveImage()`;
this has a significant negative impact on bandwidth and performance.
* Avoid using `loadOp = LOAD_OP_LOAD` for multisampled image attachments.
* Avoid using `storeOp = STORE_OP_STORE` for multisampled image attachments.
* Avoid using more than 4x MSAA without checking performance.
*Impact*
* Failing to get an inline resolve can result in substantially higher memory bandwidth and reduced performance;
manually writing and resolving a 4x MSAA 1080p surface at 60 FPS requires 3.9GB/s of memory bandwidth compared to just 500MB/s when using an inline resolve.
Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

+863
View File
@@ -0,0 +1,863 @@
/* Copyright (c) 2021-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 "msaa.h"
#include "common/vk_common.h"
#include "filesystem/legacy.h"
#include "gltf_loader.h"
#include "gui.h"
#include "rendering/postprocessing_renderpass.h"
#include "rendering/subpasses/forward_subpass.h"
#include "stats/stats.h"
namespace
{
const std::string to_string(VkSampleCountFlagBits count)
{
switch (count)
{
case VK_SAMPLE_COUNT_1_BIT:
return "No MSAA";
case VK_SAMPLE_COUNT_2_BIT:
return "2X MSAA";
case VK_SAMPLE_COUNT_4_BIT:
return "4X MSAA";
case VK_SAMPLE_COUNT_8_BIT:
return "8X MSAA";
case VK_SAMPLE_COUNT_16_BIT:
return "16X MSAA";
case VK_SAMPLE_COUNT_32_BIT:
return "32X MSAA";
case VK_SAMPLE_COUNT_64_BIT:
return "64X MSAA";
default:
return "Unknown";
}
}
const std::string to_string(VkResolveModeFlagBits mode)
{
switch (mode)
{
case VK_RESOLVE_MODE_NONE:
return "None";
case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT:
return "Sample 0";
case VK_RESOLVE_MODE_AVERAGE_BIT:
return "Average";
case VK_RESOLVE_MODE_MIN_BIT:
return "Min";
case VK_RESOLVE_MODE_MAX_BIT:
return "Max";
default:
return "Unknown";
}
}
} // namespace
MSAASample::MSAASample()
{
// Extension of interest in this sample (optional)
add_device_extension(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, true);
// Extension dependency requirements (given that instance API version is 1.0.0)
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, true);
add_device_extension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, true);
add_device_extension(VK_KHR_MAINTENANCE2_EXTENSION_NAME, true);
add_device_extension(VK_KHR_MULTIVIEW_EXTENSION_NAME, true);
auto &config = get_configuration();
// MSAA will be enabled by default if supported
// Batch mode will test the toggle between 1 or 2 renderpasses
// with writeback resolve of color and depth
config.insert<vkb::BoolSetting>(0, gui_run_postprocessing, false);
config.insert<vkb::BoolSetting>(1, gui_run_postprocessing, true);
}
bool MSAASample::prepare(const vkb::ApplicationOptions &options)
{
if (!VulkanSample::prepare(options))
{
return false;
}
prepare_supported_sample_count_list();
depth_writeback_resolve_supported = get_device().is_extension_enabled(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
if (depth_writeback_resolve_supported)
{
prepare_depth_resolve_mode_list();
}
load_scene("scenes/space_module/SpaceModule.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 scene_vs{"base.vert.spv"};
vkb::ShaderSource scene_fs{"base.frag.spv"};
auto scene_subpass = std::make_unique<vkb::ForwardSubpass>(get_render_context(), std::move(scene_vs), std::move(scene_fs), get_scene(), *camera);
scene_pipeline = std::make_unique<vkb::RenderPipeline>();
scene_pipeline->add_subpass(std::move(scene_subpass));
postprocessing_pipeline = std::make_unique<vkb::PostProcessingPipeline>(get_render_context(), vkb::ShaderSource{"postprocessing/postprocessing.vert.spv"});
postprocessing_pipeline->add_pass()
.add_subpass(vkb::ShaderSource{"postprocessing/outline.frag.spv"});
ms_depth_postprocessing_pipeline = std::make_unique<vkb::PostProcessingPipeline>(get_render_context(), vkb::ShaderSource{"postprocessing/postprocessing.vert.spv"});
ms_depth_postprocessing_pipeline->add_pass()
.add_subpass(vkb::ShaderSource{"postprocessing/outline_ms_depth.frag.spv"});
update_pipelines();
get_stats().request_stats({vkb::StatIndex::frame_times,
vkb::StatIndex::gpu_ext_read_bytes,
vkb::StatIndex::gpu_ext_write_bytes});
create_gui(*window, &get_stats());
return true;
}
void MSAASample::prepare_render_context()
{
get_render_context().prepare(1, std::bind(&MSAASample::create_render_target, this, std::placeholders::_1));
}
std::unique_ptr<vkb::RenderTarget> MSAASample::create_render_target(vkb::core::Image &&swapchain_image)
{
auto &device = swapchain_image.get_device();
auto &extent = swapchain_image.get_extent();
auto depth_format = vkb::get_suitable_depth_format(device.get_gpu().get_handle());
bool msaa_enabled = sample_count != VK_SAMPLE_COUNT_1_BIT;
VkImageUsageFlags depth_usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
VkImageUsageFlags depth_resolve_usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
if (run_postprocessing)
{
// Depth needs to be read by the postprocessing subpass
if (msaa_enabled && depth_writeback_resolve_supported && resolve_depth_on_writeback)
{
// Depth is resolved
depth_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
depth_resolve_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
else
{
// Postprocessing reads multisampled depth
depth_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
depth_resolve_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
}
else
{
// Depth attachments are transient
depth_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
depth_resolve_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
vkb::core::Image depth_image{device,
extent,
depth_format,
depth_usage,
VMA_MEMORY_USAGE_GPU_ONLY,
sample_count};
vkb::core::Image depth_resolve_image{device,
extent,
depth_format,
depth_resolve_usage,
VMA_MEMORY_USAGE_GPU_ONLY,
VK_SAMPLE_COUNT_1_BIT};
VkImageUsageFlags color_ms_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (ColorResolve::OnWriteback == color_resolve_method)
{
// Writeback resolve means that the multisampled attachment
// can be discarded at the end of the render pass
color_ms_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
else if (ColorResolve::SeparatePass == color_resolve_method)
{
// Multisampled attachment will be stored and
// resolved outside the render pass
color_ms_usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
vkb::core::Image color_ms_image{device,
extent,
swapchain_image.get_format(),
color_ms_usage,
VMA_MEMORY_USAGE_GPU_ONLY,
sample_count};
VkImageUsageFlags color_resolve_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (run_postprocessing)
{
if (ColorResolve::SeparatePass == color_resolve_method)
{
// The multisampled color image will be resolved
// to this attachment with a transfer operation
color_resolve_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
// The resolved color image will be read by the postprocessing
// renderpass
color_resolve_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
vkb::core::Image color_resolve_image{device,
extent,
swapchain_image.get_format(),
color_resolve_usage,
VMA_MEMORY_USAGE_GPU_ONLY,
VK_SAMPLE_COUNT_1_BIT};
scene_load_store.clear();
std::vector<vkb::core::Image> images;
// Attachment 0 - Swapchain
// Used by the scene renderpass if postprocessing is disabled
// Used by the postprocessing renderpass if postprocessing is enabled
i_swapchain = 0;
images.push_back(std::move(swapchain_image));
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE});
// Attachment 1 - Depth
// Always used by the scene renderpass, may or may not be multisampled
i_depth = 1;
images.push_back(std::move(depth_image));
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
// Attachment 2 - Multisampled color
// Used by the scene renderpass if MSAA is enabled
i_color_ms = 2;
images.push_back(std::move(color_ms_image));
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
// Attachment 3 - Resolved color
// Used as an output by the scene renderpass if MSAA and postprocessing are enabled
// Used as an input by the postprocessing renderpass
i_color_resolve = 3;
images.push_back(std::move(color_resolve_image));
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
// Attachment 4 - Resolved depth
// Used for writeback depth resolve if MSAA is enabled and the required extension is supported
i_depth_resolve = 4;
images.push_back(std::move(depth_resolve_image));
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
color_atts = {i_swapchain, i_color_ms, i_color_resolve};
depth_atts = {i_depth, i_depth_resolve};
return std::make_unique<vkb::RenderTarget>(std::move(images));
}
void MSAASample::update(float delta_time)
{
if ((gui_run_postprocessing != last_gui_run_postprocessing) ||
(gui_sample_count != last_gui_sample_count) ||
(gui_color_resolve_method != last_gui_color_resolve_method) ||
(gui_resolve_depth_on_writeback != last_gui_resolve_depth_on_writeback) ||
(gui_depth_resolve_mode != last_gui_depth_resolve_mode))
{
run_postprocessing = gui_run_postprocessing;
sample_count = gui_sample_count;
color_resolve_method = gui_color_resolve_method;
resolve_depth_on_writeback = gui_resolve_depth_on_writeback;
depth_resolve_mode = gui_depth_resolve_mode;
update_pipelines();
last_gui_run_postprocessing = gui_run_postprocessing;
last_gui_sample_count = gui_sample_count;
last_gui_color_resolve_method = gui_color_resolve_method;
last_gui_resolve_depth_on_writeback = gui_resolve_depth_on_writeback;
last_gui_depth_resolve_mode = gui_depth_resolve_mode;
}
VulkanSample::update(delta_time);
}
void MSAASample::update_pipelines()
{
bool msaa_enabled = sample_count != VK_SAMPLE_COUNT_1_BIT;
if (run_postprocessing)
{
update_for_scene_and_postprocessing(msaa_enabled);
}
else
{
update_for_scene_only(msaa_enabled);
}
// Default swapchain usage flags
std::set<VkImageUsageFlagBits> swapchain_usage = {VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT};
if (ColorResolve::SeparatePass == color_resolve_method && !run_postprocessing)
{
// The multisampled color image will be resolved
// to the swapchain with a transfer operation
swapchain_usage.insert(VK_IMAGE_USAGE_TRANSFER_DST_BIT);
}
get_device().wait_idle();
get_render_context().update_swapchain(swapchain_usage);
}
void MSAASample::update_for_scene_only(bool msaa_enabled)
{
auto &scene_subpass = scene_pipeline->get_active_subpass();
scene_subpass->set_sample_count(sample_count);
if (msaa_enabled)
{
// Render multisampled color, to be resolved to the swapchain
use_multisampled_color(scene_subpass, scene_load_store, i_swapchain);
}
else
{
// Render color to the swapchain
use_singlesampled_color(scene_subpass, scene_load_store, i_swapchain);
}
// Depth attachment is transient, it will not be needed after the renderpass
// If it is multisampled, there is no need to resolve it
scene_load_store[i_depth].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
disable_depth_writeback_resolve(scene_subpass, scene_load_store);
// Auxiliary single-sampled color attachment is not used
scene_load_store[i_color_resolve].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Update the scene renderpass
scene_pipeline->set_load_store(scene_load_store);
}
void MSAASample::update_for_scene_and_postprocessing(bool msaa_enabled)
{
auto &scene_subpass = scene_pipeline->get_active_subpass();
scene_subpass->set_sample_count(sample_count);
// The color and depth attachments will be the input of the postprocessing renderpass
if (msaa_enabled)
{
// Resolve multisampled color to an intermediate attachment
use_multisampled_color(scene_subpass, scene_load_store, i_color_resolve);
// Store multisampled depth
// Resolve it first if enabled and supported,
store_multisampled_depth(scene_subpass, scene_load_store);
}
else
{
// Render color to an intermediate attachment
use_singlesampled_color(scene_subpass, scene_load_store, i_color_resolve);
// Store single-sampled depth
scene_load_store[i_depth].store_op = VK_ATTACHMENT_STORE_OP_STORE;
disable_depth_writeback_resolve(scene_subpass, scene_load_store);
}
// Swapchain is not used in the scene renderpass
scene_load_store[i_swapchain].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Update the scene renderpass
scene_pipeline->set_load_store(scene_load_store);
}
void MSAASample::use_multisampled_color(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store,
uint32_t resolve_attachment)
{
// Render to multisampled color attachment
subpass->set_output_attachments({i_color_ms});
// Resolve color
if (ColorResolve::OnWriteback == color_resolve_method)
{
// Good practice
// Multisampled attachment is transient
// This allows tilers to completely avoid writing out the multisampled attachment to memory,
// a considerable performance and bandwidth improvement
load_store[i_color_ms].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Enable writeback resolve to single-sampled attachment
subpass->set_color_resolve_attachments({resolve_attachment});
// Save resolved attachment
load_store[resolve_attachment].store_op = VK_ATTACHMENT_STORE_OP_STORE;
}
else if (ColorResolve::SeparatePass == color_resolve_method)
{
// Bad practice
// Save multisampled color attachment, will be resolved outside the renderpass
// Storing multisampled color should be avoided
load_store[i_color_ms].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Disable writeback resolve
subpass->set_color_resolve_attachments({});
load_store[resolve_attachment].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
}
}
void MSAASample::use_singlesampled_color(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store,
uint32_t output_attachment)
{
// Render to a single-sampled attachment
subpass->set_output_attachments({output_attachment});
load_store[output_attachment].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Multisampled color attachment is not used
load_store[i_color_ms].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Disable writeback resolve
subpass->set_color_resolve_attachments({});
}
void MSAASample::store_multisampled_depth(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store)
{
if (depth_writeback_resolve_supported && resolve_depth_on_writeback)
{
// Good practice
// Multisampled attachment is transient
// This allows tilers to completely avoid writing out the multisampled attachment to memory,
// a considerable performance and bandwidth improvement
load_store[i_depth].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Enable writeback resolve to single-sampled attachment
subpass->set_depth_stencil_resolve_attachment(i_depth_resolve);
subpass->set_depth_stencil_resolve_mode(depth_resolve_mode);
// Save resolved attachment
load_store[i_depth_resolve].store_op = VK_ATTACHMENT_STORE_OP_STORE;
}
else
{
// Bad practice
// Save multisampled depth attachment, which cannot be resolved outside the renderpass
// Storing multisampled depth should be avoided
load_store[i_depth].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Disable writeback resolve
disable_depth_writeback_resolve(subpass, load_store);
}
}
void MSAASample::disable_depth_writeback_resolve(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store)
{
// Auxiliary single-sampled depth attachment is not used
load_store[i_depth_resolve].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Disable writeback resolve
subpass->set_depth_stencil_resolve_attachment(VK_ATTACHMENT_UNUSED);
subpass->set_depth_stencil_resolve_mode(VK_RESOLVE_MODE_NONE);
}
void MSAASample::draw(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target)
{
auto &views = render_target.get_views();
auto swapchain_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
{
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
memory_barrier.new_layout = swapchain_layout;
memory_barrier.src_access_mask = 0;
memory_barrier.dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
for (auto &i_color : color_atts)
{
assert(i_color < views.size());
command_buffer.image_memory_barrier(views[i_color], memory_barrier);
render_target.set_layout(i_color, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
}
{
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
memory_barrier.src_access_mask = 0;
memory_barrier.dst_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
if (run_postprocessing)
{
// Synchronize depth with previous depth resolve operation
memory_barrier.dst_stage_mask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.dst_access_mask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
}
for (auto &i_depth : depth_atts)
{
assert(i_depth < views.size());
command_buffer.image_memory_barrier(views[i_depth], memory_barrier);
render_target.set_layout(i_depth, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
}
}
auto &extent = render_target.get_extent();
VkViewport viewport{};
viewport.width = static_cast<float>(extent.width);
viewport.height = static_cast<float>(extent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
command_buffer.set_viewport(0, {viewport});
VkRect2D scissor{};
scissor.extent = extent;
command_buffer.set_scissor(0, {scissor});
scene_pipeline->draw(command_buffer, render_target);
if (!run_postprocessing)
{
// If postprocessing is enabled the GUI will be drawn
// at the end of the postprocessing renderpass
if (has_gui())
{
get_gui().draw(command_buffer);
}
}
command_buffer.end_render_pass();
bool msaa_enabled = sample_count != VK_SAMPLE_COUNT_1_BIT;
if (msaa_enabled && ColorResolve::SeparatePass == color_resolve_method)
{
if (run_postprocessing)
{
resolve_color_separate_pass(command_buffer, views, i_color_resolve, swapchain_layout);
}
else
{
resolve_color_separate_pass(command_buffer, views, i_swapchain, swapchain_layout);
}
}
if (run_postprocessing)
{
// Run a second renderpass
postprocessing(command_buffer, render_target, swapchain_layout, msaa_enabled);
}
{
// Prepare swapchain for presentation
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = swapchain_layout;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
memory_barrier.src_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
assert(i_swapchain < views.size());
command_buffer.image_memory_barrier(views[i_swapchain], memory_barrier);
}
}
void MSAASample::postprocessing(vkb::core::CommandBufferC &command_buffer,
vkb::RenderTarget &render_target,
VkImageLayout &swapchain_layout,
bool msaa_enabled)
{
auto depth_attachment = (msaa_enabled && depth_writeback_resolve_supported && resolve_depth_on_writeback) ? i_depth_resolve : i_depth;
bool multisampled_depth = msaa_enabled && !(depth_writeback_resolve_supported && resolve_depth_on_writeback);
std::string depth_sampler_name = multisampled_depth ? "ms_depth_sampler" : "depth_sampler";
glm::vec4 near_far = {camera->get_far_plane(), camera->get_near_plane(), -1.0f, -1.0f};
// Select the currently active pipeline
auto &pipeline = multisampled_depth ? ms_depth_postprocessing_pipeline : postprocessing_pipeline;
auto &postprocessing_pass = pipeline->get_pass(0);
postprocessing_pass.set_uniform_data(near_far);
auto &postprocessing_subpass = postprocessing_pass.get_subpass(0);
// Unbind sampled images to prevent invalid image transitions on unused images
postprocessing_subpass.unbind_sampled_image("depth_sampler");
postprocessing_subpass.unbind_sampled_image("ms_depth_sampler");
postprocessing_subpass.get_fs_variant().clear();
postprocessing_subpass
.bind_sampled_image(depth_sampler_name, {depth_attachment, nullptr, nullptr, depth_writeback_resolve_supported && resolve_depth_on_writeback})
.bind_sampled_image("color_sampler", i_color_resolve);
// Second render pass
// NOTE: Color and depth attachments are automatically transitioned to be bound as textures
pipeline->draw(command_buffer, render_target);
if (has_gui())
{
get_gui().draw(command_buffer);
}
command_buffer.end_render_pass();
}
void MSAASample::resolve_color_separate_pass(vkb::core::CommandBufferC &command_buffer,
const std::vector<vkb::core::ImageView> &views,
uint32_t color_destination,
VkImageLayout &color_layout)
{
{
// The multisampled color is the source of the resolve operation
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.src_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
memory_barrier.dst_access_mask = VK_ACCESS_TRANSFER_READ_BIT;
assert(i_color_ms < views.size());
command_buffer.image_memory_barrier(views[i_color_ms], memory_barrier);
}
VkImageSubresourceLayers subresource = {0};
subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresource.layerCount = 1;
VkImageResolve image_resolve = {0};
image_resolve.srcSubresource = subresource;
image_resolve.dstSubresource = subresource;
image_resolve.extent = VkExtent3D{get_render_context().get_surface_extent().width, get_render_context().get_surface_extent().height, 1};
{
// Prepare destination image for transfer operation
auto color_new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = color_layout;
memory_barrier.new_layout = color_new_layout;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.src_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
memory_barrier.dst_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
color_layout = color_new_layout;
assert(color_destination < views.size());
command_buffer.image_memory_barrier(views[color_destination], memory_barrier);
}
// Resolve multisampled attachment to destination, extremely expensive
command_buffer.resolve_image(views[i_color_ms].get_image(), views.at(color_destination).get_image(), {image_resolve});
// Transition attachments out of transfer stage
{
auto color_new_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = color_layout;
memory_barrier.new_layout = color_new_layout;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
memory_barrier.dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
color_layout = color_new_layout;
command_buffer.image_memory_barrier(views[color_destination], memory_barrier);
}
{
vkb::ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barrier.src_access_mask = VK_ACCESS_TRANSFER_READ_BIT;
memory_barrier.dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
command_buffer.image_memory_barrier(views[i_color_ms], memory_barrier);
}
}
void MSAASample::prepare_supported_sample_count_list()
{
VkPhysicalDeviceProperties gpu_properties;
vkGetPhysicalDeviceProperties(get_device().get_gpu().get_handle(), &gpu_properties);
VkSampleCountFlags supported_by_depth_and_color = gpu_properties.limits.framebufferColorSampleCounts & gpu_properties.limits.framebufferDepthSampleCounts;
// All possible sample counts are listed here from most to least preferred as default
// On Mali GPUs 4X MSAA is recommended as best performance/quality trade-off
std::vector<VkSampleCountFlagBits> counts = {VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_8_BIT,
VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_64_BIT,
VK_SAMPLE_COUNT_1_BIT};
for (auto &count : counts)
{
if (supported_by_depth_and_color & count)
{
supported_sample_count_list.push_back(count);
if (sample_count == VK_SAMPLE_COUNT_1_BIT)
{
// Set default sample count based on the priority defined above
sample_count = count;
gui_sample_count = count;
last_gui_sample_count = count;
}
}
}
}
void MSAASample::prepare_depth_resolve_mode_list()
{
if (get_instance().is_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
{
VkPhysicalDeviceProperties2KHR gpu_properties{};
gpu_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
VkPhysicalDeviceDepthStencilResolvePropertiesKHR depth_resolve_properties{};
depth_resolve_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES_KHR;
gpu_properties.pNext = static_cast<void *>(&depth_resolve_properties);
vkGetPhysicalDeviceProperties2KHR(get_device().get_gpu().get_handle(), &gpu_properties);
if (depth_resolve_properties.supportedDepthResolveModes == 0)
{
LOGW("No depth stencil resolve modes supported");
depth_writeback_resolve_supported = false;
}
else
{
// All possible modes are listed here from most to least preferred as default
std::vector<VkResolveModeFlagBits> modes = {VK_RESOLVE_MODE_SAMPLE_ZERO_BIT, VK_RESOLVE_MODE_MIN_BIT,
VK_RESOLVE_MODE_MAX_BIT, VK_RESOLVE_MODE_AVERAGE_BIT};
for (auto &mode : modes)
{
if (depth_resolve_properties.supportedDepthResolveModes & mode)
{
supported_depth_resolve_mode_list.push_back(mode);
if (depth_resolve_mode == VK_RESOLVE_MODE_NONE)
{
// Set default mode based on the priority defined above
depth_resolve_mode = mode;
gui_depth_resolve_mode = mode;
last_gui_depth_resolve_mode = mode;
}
}
}
}
}
}
void MSAASample::draw_gui()
{
auto msaa_enabled = sample_count != VK_SAMPLE_COUNT_1_BIT;
const bool landscape = camera->get_aspect_ratio() > 1.0f;
uint32_t lines = landscape ? 3 : 4;
get_gui().show_options_window(
[this, msaa_enabled, landscape]() {
ImGui::AlignTextToFramePadding();
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.4f);
if (ImGui::BeginCombo("##sample_count", to_string(gui_sample_count).c_str()))
{
for (size_t n = 0; n < supported_sample_count_list.size(); n++)
{
bool is_selected = (gui_sample_count == supported_sample_count_list[n]);
if (ImGui::Selectable(to_string(supported_sample_count_list[n]).c_str(), is_selected))
{
gui_sample_count = supported_sample_count_list[n];
}
if (is_selected)
{
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
if (landscape)
{
ImGui::SameLine();
}
ImGui::Checkbox("Post-processing (2 renderpasses)", &gui_run_postprocessing);
ImGui::Text("Resolve color: ");
ImGui::SameLine();
if (msaa_enabled)
{
ImGui::RadioButton("On writeback", &gui_color_resolve_method, ColorResolve::OnWriteback);
ImGui::SameLine();
ImGui::RadioButton("Separate", &gui_color_resolve_method, ColorResolve::SeparatePass);
}
else
{
ImGui::Text("n/a");
}
ImGui::Text("Resolve depth: ");
ImGui::SameLine();
if (msaa_enabled && run_postprocessing)
{
if (depth_writeback_resolve_supported)
{
ImGui::Checkbox("##resolve_depth", &gui_resolve_depth_on_writeback);
ImGui::SameLine();
ImGui::Text("On writeback");
ImGui::SameLine();
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f);
if (ImGui::BeginCombo("##resolve_mode", to_string(gui_depth_resolve_mode).c_str()))
{
for (int n = 0; n < supported_depth_resolve_mode_list.size(); n++)
{
bool is_selected = (gui_depth_resolve_mode == supported_depth_resolve_mode_list[n]);
if (ImGui::Selectable(to_string(supported_depth_resolve_mode_list[n]).c_str(), is_selected))
{
gui_depth_resolve_mode = supported_depth_resolve_mode_list[n];
}
if (is_selected)
{
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
}
else
{
ImGui::Text("Not supported");
}
}
else
{
ImGui::Text("n/a");
}
},
lines);
}
std::unique_ptr<vkb::VulkanSampleC> create_msaa()
{
return std::make_unique<MSAASample>();
}
+283
View File
@@ -0,0 +1,283 @@
/* Copyright (c) 2023-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 "rendering/postprocessing_pipeline.h"
#include "rendering/render_pipeline.h"
#include "scene_graph/components/perspective_camera.h"
#include "vulkan_sample.h"
/**
* @brief MSAA Sample
*
* This sample shows the benefits of multisample anti-aliasing (MSAA) and how to
* resolve the multisampled attachments with minimum impact on performance.
*
* The UI controls allow the user to choose between different levels of MSAA
* and to select whether or not to resolve the color and depth attachments
* within the render pass.
*
* Resolving within the renderpass is very efficient on mobile since usually
* tilers can resolve the multisampled attachments on writeback to
* main memory. This means on mobile it is possible to get considerable
* quality improvements with relatively little cost.
*
* The alternative to resolving within the renderpass is to have a separate
* color resolve pass (using vkCmdResolveImage), which is much less efficient.
*
* Resolving on writeback was only possible with color attachments, but
* VK_KHR_depth_stencil_resolve (promoted in Vulkan 1.2) makes it possible to
* also resolve the depth attachment within the renderpass.
*
* Without this extension there is no simple alternative to resolve depth
* in a separate pass (vkCmdResolveImage does not support depth).
*
* Usually the depth attachment is transient (it is not needed outside the
* render pass) but some postprocessing effects require it as an input texture.
* Without the extension, if MSAA is enabled in the geometry pass, the
* multisampled depth attachment would be have to be written out to be
* consumed by the postprocessing renderpass.
*
* As with unresolved color, writing out unresolved depth attachments is very
* bandwidth intensive and therefore depth-based postprocessing was
* usually avoided on mobile platforms.
*
* This sample shows how to use the extension to also resolve the depth
* attachment on writeback and use it in a simple postprocessing pass.
*/
class MSAASample : public vkb::VulkanSampleC
{
public:
MSAASample();
virtual ~MSAASample() = default;
virtual bool prepare(const vkb::ApplicationOptions &options) override;
virtual void update(float delta_time) override;
virtual void draw(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target) override;
void draw_gui() override;
private:
vkb::sg::PerspectiveCamera *camera{nullptr};
virtual void prepare_render_context() override;
std::unique_ptr<vkb::RenderTarget> create_render_target(vkb::core::Image &&swapchain_image);
/**
* @brief Scene pipeline
* Render and light the scene (optionally using MSAA)
*/
std::unique_ptr<vkb::RenderPipeline> scene_pipeline{};
/**
* @brief Postprocessing pipeline
* Read in the output color and depth attachments from the
* scene subpass and use them to apply a screen-based effect
*/
std::unique_ptr<vkb::PostProcessingPipeline> postprocessing_pipeline{};
/**
* @brief Postprocessing pipeline using multi-sampled depth
* Read in the output color and depth attachments from the
* scene subpass and use them to apply a screen-based effect
*/
std::unique_ptr<vkb::PostProcessingPipeline> ms_depth_postprocessing_pipeline{};
/**
* @brief Update MSAA options and accordingly set the load/store
* attachment operations for the renderpasses
* This will trigger a swapchain recreation
*/
void update_pipelines();
/**
* @brief Update pipelines given that there will be a single
* renderpass for rendering the scene and GUI only
*/
void update_for_scene_only(bool msaa_enabled);
/**
* @brief Update pipelines given that there will be two renderpasses
* The first renderpass will draw the scene and save the output
* color and depth attachments which will be read in by
* a postprocessing renderpass
*/
void update_for_scene_and_postprocessing(bool msaa_enabled);
/**
* @brief If true the postprocessing renderpass is enabled
*/
bool run_postprocessing{false};
/**
* @brief Submits a postprocessing renderpass which binds full screen color
* and depth attachments and uses them to apply a screen-based effect
* It also draws the GUI
*/
void postprocessing(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target, VkImageLayout &swapchain_layout, bool msaa_enabled);
/**
* @brief Enables MSAA if set to more than 1 sample per pixel
* (e.g. sample count 4 enables 4X MSAA)
*/
VkSampleCountFlagBits sample_count{VK_SAMPLE_COUNT_1_BIT};
/**
* @brief List of MSAA levels supported by the platform
*/
std::vector<VkSampleCountFlagBits> supported_sample_count_list{};
/**
* @brief Queries the Vulkan device to construct the list of supported
* sample counts
*/
void prepare_supported_sample_count_list();
enum ColorResolve : int
{
OnWriteback = 0,
SeparatePass = 1
};
/**
* @brief Selects how to resolve the color attachment, either on writeback
* (efficient) or in a separate pass (inefficient)
*/
int color_resolve_method{ColorResolve::OnWriteback};
/**
* @brief Sets the multisampled color attachment as the output attachment
* and configures the resolve operation to resolve_attachment
* as well as the load/store operations of color attachments
* Note that MSAA will not have any effect in the postprocessing
* renderpass since it only renders a texture on single full-screen
* triangle and MSAA only works on primitive edges
*/
void use_multisampled_color(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store,
uint32_t resolve_attachment);
/**
* @brief Sets the single-sampled output_attachment as the output attachment,
* disables color resolve and updates the load/store operations of
* color attachments
*/
void use_singlesampled_color(std::unique_ptr<vkb::rendering::SubpassC> &subpass,
std::vector<vkb::LoadStoreInfo> &load_store,
uint32_t output_attachment);
/**
* @brief Submits a transfer operation to resolve the multisampled color attachment
* to the given single-sampled resolve attachment
* color_layout is an in-out parameter that holds the last known layout
* of the resolve attachment, and may be used for any further transitions
*/
void resolve_color_separate_pass(vkb::core::CommandBufferC &command_buffer,
const std::vector<vkb::core::ImageView> &views,
uint32_t color_destination,
VkImageLayout &color_layout);
/**
* @brief If true, the platform supports the VK_KHR_depth_stencil_resolve extension
* and therefore can resolve the depth attachment on writeback
*/
bool depth_writeback_resolve_supported{false};
/**
* @brief If true, enable writeback depth resolve
* If false the multisampled depth attachment will be stored
* (only if postprocessing is enabled since the attachment is
* otherwise unused)
*/
bool resolve_depth_on_writeback{true};
/**
* @brief Store the multisampled depth attachment, resolved to a single-sampled
* attachment if depth resolve on writeback is supported
* Update the load/store operations of the depth attachments
*/
void store_multisampled_depth(std::unique_ptr<vkb::rendering::SubpassC> &subpass, std::vector<vkb::LoadStoreInfo> &load_store);
/**
* @brief Disables depth writeback resolve and updates the load/store operations of
* the depth resolve attachment
*/
void disable_depth_writeback_resolve(std::unique_ptr<vkb::rendering::SubpassC> &subpass, std::vector<vkb::LoadStoreInfo> &load_store);
/**
* @brief Selects the depth resolve mode (e.g. min or max sample values)
*/
VkResolveModeFlagBits depth_resolve_mode{VK_RESOLVE_MODE_NONE};
/**
* @brief List of depth resolve modes supported by the platform
*/
std::vector<VkResolveModeFlagBits> supported_depth_resolve_mode_list{};
/**
* @brief Queries the Vulkan device to construct the list of supported
* depth resolve modes
*/
void prepare_depth_resolve_mode_list();
/* Helpers for managing attachments */
uint32_t i_swapchain{0};
uint32_t i_depth{0};
uint32_t i_color_ms{0};
uint32_t i_color_resolve{0};
uint32_t i_depth_resolve{0};
std::vector<uint32_t> color_atts{};
std::vector<uint32_t> depth_atts{};
std::vector<vkb::LoadStoreInfo> scene_load_store{};
/* Helpers for managing GUI input */
bool gui_run_postprocessing{false};
bool last_gui_run_postprocessing{false};
VkSampleCountFlagBits gui_sample_count{VK_SAMPLE_COUNT_1_BIT};
VkSampleCountFlagBits last_gui_sample_count{VK_SAMPLE_COUNT_1_BIT};
int gui_color_resolve_method{ColorResolve::OnWriteback};
int last_gui_color_resolve_method{ColorResolve::OnWriteback};
bool gui_resolve_depth_on_writeback{true};
bool last_gui_resolve_depth_on_writeback{true};
VkResolveModeFlagBits gui_depth_resolve_mode{VK_RESOLVE_MODE_NONE};
VkResolveModeFlagBits last_gui_depth_resolve_mode{VK_RESOLVE_MODE_NONE};
};
std::unique_ptr<vkb::VulkanSampleC> create_msaa();