From 1cd72f3d07eef72a7a87a23ffe68dd187c016b36 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 16 Feb 2025 12:07:15 +0100 Subject: [PATCH 1/2] New dynamic rendering multi sampling example Work-in-progress --- README.md | 4 + .../CMakeLists.txt | 35 ++ .../build.gradle | 65 +++ .../src/main/AndroidManifest.xml | 24 ++ .../vulkanSample/VulkanActivity.java | 58 +++ examples/CMakeLists.txt | 1 + .../dynamicrenderingmultisampling.cpp | 407 ++++++++++++++++++ 7 files changed, 594 insertions(+) create mode 100644 android/examples/dynamicrenderingmultisampling/CMakeLists.txt create mode 100644 android/examples/dynamicrenderingmultisampling/build.gradle create mode 100644 android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml create mode 100644 android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java create mode 100644 examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp diff --git a/README.md b/README.md index 245def37..1a11894c 100644 --- a/README.md +++ b/README.md @@ -446,6 +446,10 @@ Vulkan is an extensible api with lots of functionality added by extensions. Thes Shows usage of the VK_KHR_dynamic_rendering extension, which simplifies the rendering setup by no longer requiring render pass objects or framebuffers. +- [Dynamic rendering with multi sampling (VK_KHR_dynamic_rendering)](examples/dynamicrenderingmultisampling/) + + Based on the dynamic rendering sample, this sample shows how to do implement multi sampling with dynamic rendering. + - [Graphics pipeline library (VK_EXT_graphics_pipeline_library)](./examples/graphicspipelinelibrary) Uses the graphics pipeline library extensions to improve run-time pipeline creation. Instead of creating the whole pipeline at once, this sample pre builds shared pipeline parts like like vertex input state and fragment output state. These are then used to create full pipelines at runtime, reducing build times and possible hick-ups. diff --git a/android/examples/dynamicrenderingmultisampling/CMakeLists.txt b/android/examples/dynamicrenderingmultisampling/CMakeLists.txt new file mode 100644 index 00000000..aafb911d --- /dev/null +++ b/android/examples/dynamicrenderingmultisampling/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) + +set(NAME dynamicrenderingmultisampling) + +set(SRC_DIR ../../../examples/${NAME}) +set(BASE_DIR ../../../base) +set(EXTERNAL_DIR ../../../external) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES") + +file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp") + +add_library(native-lib SHARED ${EXAMPLE_SRC}) + +add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + +add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base) + +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + +include_directories(${BASE_DIR}) +include_directories(${EXTERNAL_DIR}) +include_directories(${EXTERNAL_DIR}/glm) +include_directories(${EXTERNAL_DIR}/imgui) +include_directories(${EXTERNAL_DIR}/tinygltf) +include_directories(${ANDROID_NDK}/sources/android/native_app_glue) + +target_link_libraries( + native-lib + native-app-glue + libbase + android + log + z +) diff --git a/android/examples/dynamicrenderingmultisampling/build.gradle b/android/examples/dynamicrenderingmultisampling/build.gradle new file mode 100644 index 00000000..de7f4824 --- /dev/null +++ b/android/examples/dynamicrenderingmultisampling/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.application' +apply from: '../gradle/outputfilename.gradle' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + defaultConfig { + applicationId "de.saschawillems.vulkanDynamicrenderingmulitsampling" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + ndk { + abiFilters rootProject.ext.abiFilters + } + externalNativeBuild { + cmake { + cppFlags "-std=c++14" + arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang' + } + } + } + sourceSets { + main.assets.srcDirs = ['assets'] + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +task copyTask { + copy { + from '../../common/res/drawable' + into "src/main/res/drawable" + include 'icon.png' + } + + copy { + from rootProject.ext.shaderPath + 'glsl/base' + into 'assets/shaders/glsl/base' + include '*.spv' + } + + copy { + from rootProject.ext.shaderPath + 'glsl/dynamicrendering' + into 'assets/shaders/glsl/dynamicrendering' + include '*.*' + } + + copy { + from rootProject.ext.assetPath + 'models' + into 'assets/models' + include 'voyager.gltf' + } + +} + +preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml b/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ccefddb6 --- /dev/null +++ b/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java new file mode 100644 index 00000000..12e14fc6 --- /dev/null +++ b/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de + * + * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + */ +package de.saschawillems.vulkanSample; + +import android.app.AlertDialog; +import android.app.NativeActivity; +import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; + +import java.util.concurrent.Semaphore; + +public class VulkanActivity extends NativeActivity { + + static { + // Load native library + System.loadLibrary("native-lib"); + } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + // Use a semaphore to create a modal dialog + + private final Semaphore semaphore = new Semaphore(0, true); + + public void showAlert(final String message) + { + final VulkanActivity activity = this; + + ApplicationInfo applicationInfo = activity.getApplicationInfo(); + final String applicationName = applicationInfo.nonLocalizedLabel.toString(); + + this.runOnUiThread(new Runnable() { + public void run() { + AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert); + builder.setTitle(applicationName); + builder.setMessage(message); + builder.setPositiveButton("Close", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + semaphore.release(); + } + }); + builder.setCancelable(false); + AlertDialog dialog = builder.create(); + dialog.show(); + } + }); + try { + semaphore.acquire(); + } + catch (InterruptedException e) { } + } +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ffc07fc9..baf7e7fa 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -109,6 +109,7 @@ set(EXAMPLES displacement distancefieldfonts dynamicrendering + dynamicrenderingmultisampling dynamicstate dynamicuniformbuffer gears diff --git a/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp b/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp new file mode 100644 index 00000000..c7154793 --- /dev/null +++ b/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp @@ -0,0 +1,407 @@ +/* + * Vulkan Example - Using Multi sampling with VK_KHR_dynamic_rendering + * + * Copyright (C) 2025 by Sascha Willems - www.saschawillems.de + * + * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + */ + +#include "vulkanexamplebase.h" +#include "VulkanglTFModel.h" + +class VulkanExample : public VulkanExampleBase +{ +public: + PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; + PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; + + VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; + + vkglTF::Model model; + + const VkSampleCountFlagBits multiSampleCount = VK_SAMPLE_COUNT_4_BIT; + + struct UniformData { + glm::mat4 projection; + glm::mat4 modelView; + glm::vec4 viewPos; + } uniformData; + vks::Buffer uniformBuffer; + + VkPipeline pipeline{ VK_NULL_HANDLE }; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; + + // Intermediate images used for multi sampling + struct RenderImage { + VkImage image{ VK_NULL_HANDLE }; + VkImageView view{ VK_NULL_HANDLE }; + VkDeviceMemory memory{ VK_NULL_HANDLE }; + }; + std::vector renderImages; + RenderImage depthStencilRenderImage; + + VulkanExample() : VulkanExampleBase() + { + title = "Multi sampling with dynamic rendering"; + camera.type = Camera::CameraType::lookat; + camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f)); + camera.setRotation(glm::vec3(-7.5f, 72.0f, 0.0f)); + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); + settings.overlay = false; + + enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + // The sample uses the extension (instead of Vulkan 1.2, where dynamic rendering is core) + enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + + // in addition to the extension, the feature needs to be explicitly enabled too by chaining the extension structure into device creation + enabledDynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + enabledDynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE; + + deviceCreatepNextChain = &enabledDynamicRenderingFeaturesKHR; + } + + ~VulkanExample() + { + if (device) { + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + uniformBuffer.destroy(); + for (auto i = 0; i < renderImages.size(); i++) { + vkDestroyImage(device, renderImages[i].image, nullptr); + vkDestroyImageView(device, renderImages[i].view, nullptr); + vkFreeMemory(device, renderImages[i].memory, nullptr); + } + } + } + + void setupRenderPass() override + { + // With VK_KHR_dynamic_rendering we no longer need a render pass, so we can skip the sample base render pass setup + renderPass = VK_NULL_HANDLE; + } + + void setupFrameBuffer() override + { + // With VK_KHR_dynamic_rendering we no longer need a frame buffer, so we can so skip the sample base framebuffer setup + // For multi sampling we need intermediate images that are then resolved to the final presentation image + renderImages.resize(swapChain.images.size()); + for (auto i = 0; i < renderImages.size(); i++) { + vkDestroyImage(device, renderImages[i].image, nullptr); + vkDestroyImageView(device, renderImages[i].view, nullptr); + vkFreeMemory(device, renderImages[i].memory, nullptr); + VkImageCreateInfo renderImageCI = vks::initializers::imageCreateInfo(); + renderImageCI.imageType = VK_IMAGE_TYPE_2D; + renderImageCI.format = swapChain.colorFormat; + renderImageCI.extent = { width, height, 1 }; + renderImageCI.mipLevels = 1; + renderImageCI.arrayLayers = 1; + renderImageCI.samples = multiSampleCount; + renderImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + renderImageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + renderImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VK_CHECK_RESULT(vkCreateImage(device, &renderImageCI, nullptr, &renderImages[i].image)); + VkMemoryRequirements memReqs{}; + vkGetImageMemoryRequirements(device, renderImages[i].image, &memReqs); + VkMemoryAllocateInfo memAllloc{}; + memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllloc.allocationSize = memReqs.size; + memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &renderImages[i].memory)); + VK_CHECK_RESULT(vkBindImageMemory(device, renderImages[i].image, renderImages[i].memory, 0)); + VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo(); + imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCI.image = renderImages[i].image; + imageViewCI.format = swapChain.colorFormat; + imageViewCI.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &renderImages[i].view)); + } + } + + // We need to override the default depth/stencil setup to create a depth image that supports multi sampling + void setupDepthStencil() override + { + VkImageCreateInfo imageCI{}; + imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.format = depthFormat; + imageCI.extent = { width, height, 1 }; + imageCI.mipLevels = 1; + imageCI.arrayLayers = 1; + imageCI.samples = multiSampleCount; + imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image)); + VkMemoryRequirements memReqs{}; + vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs); + VkMemoryAllocateInfo memAllloc{}; + memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllloc.allocationSize = memReqs.size; + memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &depthStencil.memory)); + VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.memory, 0)); + VkImageViewCreateInfo depthImageViewCI{}; + depthImageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthImageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthImageViewCI.image = depthStencil.image; + depthImageViewCI.format = depthFormat; + depthImageViewCI.subresourceRange.baseMipLevel = 0; + depthImageViewCI.subresourceRange.levelCount = 1; + depthImageViewCI.subresourceRange.baseArrayLayer = 0; + depthImageViewCI.subresourceRange.layerCount = 1; + depthImageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + // Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT + if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { + depthImageViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + VK_CHECK_RESULT(vkCreateImageView(device, &depthImageViewCI, nullptr, &depthStencil.view)); + } + + // Enable physical device features required for this example + virtual void getEnabledFeatures() + { + // Enable anisotropic filtering if supported + if (deviceFeatures.samplerAnisotropy) { + enabledFeatures.samplerAnisotropy = VK_TRUE; + }; + } + + void loadAssets() + { + const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; + model.loadFromFile(getAssetPath() + "models/voyager.gltf", vulkanDevice, queue, glTFLoadingFlags); + } + + void buildCommandBuffers() + { + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) + { + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + // With dynamic rendering there are no subpass dependencies, so we need to take care of proper layout transitions by using barriers + // This set of barriers prepares the color and depth images for output + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + renderImages[i].image, + 0, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + depthStencil.image, + 0, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1 }); + + // New structures are used to define the attachments used in dynamic rendering + VkRenderingAttachmentInfoKHR colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue.color = { 0.0f,0.0f,0.0f,0.0f }; + // When multi sampling is used, we use intermediate images to render and resolve to the swap chain images + colorAttachment.imageView = renderImages[i].view; + colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; + colorAttachment.resolveImageView = swapChain.imageViews[i]; + colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_GENERAL; + + // A single depth stencil attachment info can be used, but they can also be specified separately. + // When both are specified separately, the only requirement is that the image view is identical. + VkRenderingAttachmentInfoKHR depthStencilAttachment{}; + depthStencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + depthStencilAttachment.imageView = depthStencil.view; + depthStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthStencilAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfoKHR renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; + renderingInfo.renderArea = { 0, 0, width, height }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthStencilAttachment; + renderingInfo.pStencilAttachment = &depthStencilAttachment; + + // Begin dynamic rendering + vkCmdBeginRenderingKHR(drawCmdBuffers[i], &renderingInfo); + + VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + model.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayout); + + drawUI(drawCmdBuffers[i]); + + // End dynamic rendering + vkCmdEndRenderingKHR(drawCmdBuffers[i]); + + // This set of barriers prepares the color image for presentation, we don't need to care for the depth image + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + swapChain.images[i], + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + 0, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void setupDescriptors() + { + // Pool + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), + }; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + // Layout + const std::vector setLayoutBindings = { + // Binding 0 : Vertex shader uniform buffer + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + // Set + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + std::vector writeDescriptorSets = { + // Binding 0 : Vertex shader uniform buffer + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + } + + void preparePipelines() + { + // Layout + // Uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model) + const std::vector setLayouts = { + descriptorSetLayout, + vkglTF::descriptorSetLayoutImage, + }; + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(multiSampleCount, 0); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + std::array shaderStages{}; + + // We no longer need to set a renderpass for the pipeline create info + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(); + pipelineCI.layout = pipelineLayout; + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = static_cast(shaderStages.size()); + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV }); + + // New create info to define color, depth and stencil attachments at pipeline create time + VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo{}; + pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; + pipelineRenderingCreateInfo.colorAttachmentCount = 1; + pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChain.colorFormat; + pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat; + pipelineRenderingCreateInfo.stencilAttachmentFormat = depthFormat; + // Chain into the pipeline creat einfo + pipelineCI.pNext = &pipelineRenderingCreateInfo; + + shaderStages[0] = loadShader(getShadersPath() + "dynamicrendering/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getShadersPath() + "dynamicrendering/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffer, sizeof(uniformData), &uniformData)); + VK_CHECK_RESULT(uniformBuffer.map()); + updateUniformBuffers(); + } + + void updateUniformBuffers() + { + uniformData.projection = camera.matrices.perspective; + uniformData.modelView = camera.matrices.view; + uniformData.viewPos = camera.viewPos; + memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); + } + + void prepare() + { + VulkanExampleBase::prepare(); + + // Since we use an extension, we need to expliclity load the function pointers for extension related Vulkan commands + vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); + vkCmdEndRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR")); + + loadAssets(); + prepareUniformBuffers(); + setupDescriptors(); + preparePipelines(); + buildCommandBuffers(); + prepared = true; + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VulkanExampleBase::submitFrame(); + } + + virtual void render() + { + if (!prepared) + return; + updateUniformBuffers(); + draw(); + } +}; + +VULKAN_EXAMPLE_MAIN() From e4cf8a611629f5d4e1de513f3233a39c1763be21 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 16 Feb 2025 18:27:23 +0100 Subject: [PATCH 2/2] Use only one intermediate image --- .../dynamicrenderingmultisampling.cpp | 77 +++++++++---------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp b/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp index c7154793..b9837eae 100644 --- a/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp +++ b/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp @@ -34,13 +34,13 @@ public: VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Intermediate images used for multi sampling - struct RenderImage { + struct Image { VkImage image{ VK_NULL_HANDLE }; VkImageView view{ VK_NULL_HANDLE }; VkDeviceMemory memory{ VK_NULL_HANDLE }; }; - std::vector renderImages; - RenderImage depthStencilRenderImage; + Image renderImage; + Image depthStencilRenderImage; VulkanExample() : VulkanExampleBase() { @@ -74,11 +74,9 @@ public: vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); uniformBuffer.destroy(); - for (auto i = 0; i < renderImages.size(); i++) { - vkDestroyImage(device, renderImages[i].image, nullptr); - vkDestroyImageView(device, renderImages[i].view, nullptr); - vkFreeMemory(device, renderImages[i].memory, nullptr); - } + vkDestroyImage(device, renderImage.image, nullptr); + vkDestroyImageView(device, renderImage.view, nullptr); + vkFreeMemory(device, renderImage.memory, nullptr); } } @@ -92,37 +90,34 @@ public: { // With VK_KHR_dynamic_rendering we no longer need a frame buffer, so we can so skip the sample base framebuffer setup // For multi sampling we need intermediate images that are then resolved to the final presentation image - renderImages.resize(swapChain.images.size()); - for (auto i = 0; i < renderImages.size(); i++) { - vkDestroyImage(device, renderImages[i].image, nullptr); - vkDestroyImageView(device, renderImages[i].view, nullptr); - vkFreeMemory(device, renderImages[i].memory, nullptr); - VkImageCreateInfo renderImageCI = vks::initializers::imageCreateInfo(); - renderImageCI.imageType = VK_IMAGE_TYPE_2D; - renderImageCI.format = swapChain.colorFormat; - renderImageCI.extent = { width, height, 1 }; - renderImageCI.mipLevels = 1; - renderImageCI.arrayLayers = 1; - renderImageCI.samples = multiSampleCount; - renderImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; - renderImageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - renderImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VK_CHECK_RESULT(vkCreateImage(device, &renderImageCI, nullptr, &renderImages[i].image)); - VkMemoryRequirements memReqs{}; - vkGetImageMemoryRequirements(device, renderImages[i].image, &memReqs); - VkMemoryAllocateInfo memAllloc{}; - memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - memAllloc.allocationSize = memReqs.size; - memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &renderImages[i].memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, renderImages[i].image, renderImages[i].memory, 0)); - VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo(); - imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - imageViewCI.image = renderImages[i].image; - imageViewCI.format = swapChain.colorFormat; - imageViewCI.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &renderImages[i].view)); - } + vkDestroyImage(device, renderImage.image, nullptr); + vkDestroyImageView(device, renderImage.view, nullptr); + vkFreeMemory(device, renderImage.memory, nullptr); + VkImageCreateInfo renderImageCI = vks::initializers::imageCreateInfo(); + renderImageCI.imageType = VK_IMAGE_TYPE_2D; + renderImageCI.format = swapChain.colorFormat; + renderImageCI.extent = { width, height, 1 }; + renderImageCI.mipLevels = 1; + renderImageCI.arrayLayers = 1; + renderImageCI.samples = multiSampleCount; + renderImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + renderImageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + renderImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VK_CHECK_RESULT(vkCreateImage(device, &renderImageCI, nullptr, &renderImage.image)); + VkMemoryRequirements memReqs{}; + vkGetImageMemoryRequirements(device, renderImage.image, &memReqs); + VkMemoryAllocateInfo memAllloc{}; + memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllloc.allocationSize = memReqs.size; + memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &renderImage.memory)); + VK_CHECK_RESULT(vkBindImageMemory(device, renderImage.image, renderImage.memory, 0)); + VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo(); + imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCI.image = renderImage.image; + imageViewCI.format = swapChain.colorFormat; + imageViewCI.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &renderImage.view)); } // We need to override the default depth/stencil setup to create a depth image that supports multi sampling @@ -191,7 +186,7 @@ public: // This set of barriers prepares the color and depth images for output vks::tools::insertImageMemoryBarrier( drawCmdBuffers[i], - renderImages[i].image, + renderImage.image, 0, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, @@ -218,7 +213,7 @@ public: colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.clearValue.color = { 0.0f,0.0f,0.0f,0.0f }; // When multi sampling is used, we use intermediate images to render and resolve to the swap chain images - colorAttachment.imageView = renderImages[i].view; + colorAttachment.imageView = renderImage.view; colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; colorAttachment.resolveImageView = swapChain.imageViews[i]; colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_GENERAL;