From d990430f2de5b6765110644314f1fbc27d380e3a Mon Sep 17 00:00:00 2001 From: swinston Date: Fri, 17 Oct 2025 14:08:24 -0700 Subject: [PATCH] Add C language examples alongside C++ in Vulkan tutorials - Include equivalent C code snippets for all Vulkan-related examples to enhance tutorial compatibility. - Annotate source blocks to specify programming languages for clarity (`[source,multilang,c++,c]`). - Improve cross-language consistency in method signatures, helper functions, and setup instructions. --- en/01_Overview.adoc | 23 ++- .../01_Presentation/00_Window_surface.adoc | 57 ++++++- .../01_Vertex_buffer_creation.adoc | 161 ++++++++++++++++-- en/07_Depth_buffering.adoc | 99 ++++++++++- en/10_Multisampling.adoc | 46 ++++- en/14_Android.adoc | 37 ++-- 6 files changed, 369 insertions(+), 54 deletions(-) diff --git a/en/01_Overview.adoc b/en/01_Overview.adoc index f3f394be..015733f4 100644 --- a/en/01_Overview.adoc +++ b/en/01_Overview.adoc @@ -28,7 +28,7 @@ with the existing APIs somehow. This resulted in less than ideal abstractions Aside from these new features, the past decade also saw an influx of mobile and embedded devices with powerful graphics hardware. These mobile GPUs have different architectures based on their energy and space requirements. -One such example is https://en.wikipedia.org/wiki/Tiled_rendering[tiled rendering], +One such example is https://en.wikipedia.org/wiki/Tiled_rendering[tiled rendering], which would benefit from improved performance by offering the programmer more control over this functionality. Another limitation originating from the age of these APIs is limited @@ -261,10 +261,12 @@ developed by LunarG. We'll look into installing this SDK in the next chapter. Functions have a lower case `vk` prefix, types like enumerations and structs have a `Vk` prefix and enumeration values have a `VK_` prefix. The API heavily uses structs to provide parameters to functions. -For example, object creation generally follows this pattern: +For example, object creation generally follows this pattern in both the C API and the C++ RAII wrapper: -[,c++] +[source,multilang,c++,c] +.Object creation pattern ---- +// START c VkXXXCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; createInfo.pNext = nullptr; @@ -276,6 +278,12 @@ if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { std::cerr << "failed to create object" << std::endl; return false; } +// END c + +// START c++ +auto createInfo = vk::xxx(); +auto object = vk::raii::XXX(context, createInfo); +// END c++ ---- Many structures in Vulkan require you to explicitly specify the type of @@ -291,15 +299,6 @@ error code. The specification describes which error codes each function can return and what they mean. -To help illustrate the utility of using the RAII C++ Vulkan abstraction; this -is the same code written with our modern API: - -[,c++] ----- -auto createInfo = vk::xxx(); -auto object = vk::raii::XXX(context, createInfo); ----- - Failure of such calls is reported by C++ exceptions. The exception will respond with more information about the error including the aforementioned vkResult, this enables us to check multiple commands from one call and keep diff --git a/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.adoc b/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.adoc index 530b5e67..1d506ebf 100644 --- a/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.adoc +++ b/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.adoc @@ -37,9 +37,16 @@ works in this tutorial. Start by adding a `surface` class member right below the debug callback. -[,c++] +[source,multilang,c++,c] +.Surface member ---- +// START c++ vk::raii::SurfaceKHR surface = nullptr; +// END c++ + +// START c +VkSurfaceKHR surface = VK_NULL_HANDLE; +// END c ---- Although the `VkSurfaceKHR` object and its usage is platform-agnostic, its @@ -72,12 +79,22 @@ Because a window surface is a Vulkan object, it comes with a important parameters: `hwnd` and `hinstance`. These are the handles to the window and the process. -[,c++] +[source,multilang,c++,c] +.Win32 surface create info ---- +// START c++ +vk::Win32SurfaceCreateInfoKHR createInfo{ + .hwnd = glfwGetWin32Window(window), + .hinstance = GetModuleHandle(nullptr) +}; +// END c++ + +// START c VkWin32SurfaceCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.hwnd = glfwGetWin32Window(window); createInfo.hinstance = GetModuleHandle(nullptr); +// END c ---- The `glfwGetWin32Window` function is used to get the raw `HWND` from the GLFW @@ -91,11 +108,18 @@ Technically, this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions, you don't need to explicitly load it. -[,c++] +[source,multilang,c++,c] +.Create the Win32 surface ---- +// START c++ +surface = vk::raii::SurfaceKHR(instance, createInfo); +// END c++ + +// START c if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } +// END c ---- The process is similar for other platforms like Linux, where @@ -125,15 +149,26 @@ void createSurface() { The GLFW call takes simple parameters instead of a struct which makes the implementation of the function very straightforward: -[,c++] +[source,multilang,c++,c] +.GLFW window surface creation ---- +// START c++ void createSurface() { - VkSurfaceKHR _surface; + VkSurfaceKHR _surface; if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { throw std::runtime_error("failed to create window surface!"); } surface = vk::raii::SurfaceKHR(instance, _surface); } +// END c++ + +// START c +void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } +} +// END c ---- However, as you see in the above, GLFW only deals with the Vulkan C API. @@ -165,9 +200,17 @@ to our window surface. The function to check for that is queue family index and surface as parameters. Add a call to it in the same loop as the `VK_QUEUE_GRAPHICS_BIT`: -[,c++] +[source,multilang,c++,c] +.Presentation support query ---- -VkBool32 presentSupport = physicalDevice.getSurfaceSupportKHR( graphicsIndex, *surface ); +// START c++ +VkBool32 presentSupport = physicalDevice.getSurfaceSupportKHR(graphicsIndex, *surface); +// END c++ + +// START c +VkBool32 presentSupport = VK_FALSE; +vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, graphicsIndex, surface, &presentSupport); +// END c ---- Then check the value of the boolean and store the presentation family diff --git a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc index e3eba4c9..d599c432 100644 --- a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc @@ -39,9 +39,20 @@ void createVertexBuffer() { Creating a buffer requires us to fill a `VkBufferCreateInfo` structure. -[,c++] +[source,multilang,c++,c] +.Buffer create info ---- +// START c++ vk::BufferCreateInfo bufferInfo({}, sizeof(vertices[0]) * vertices.size(), vk::BufferUsageFlagBits::eVertexBuffer, vk::SharingMode::eExclusive); +// END c++ + +// START c +VkBufferCreateInfo bufferInfo{}; +bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; +bufferInfo.size = sizeof(vertices[0]) * vertices.size(); +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +// END c ---- The first field of the constructor is the flags, used to configure sparse buffer memory, which is not relevant right now. @@ -63,8 +74,10 @@ We'll leave it at the default value of `0`. We can now create the buffer with `vkCreateBuffer`. Define a class member to hold the buffer handle and call it `vertexBuffer`. -[,c++] +[source,multilang,c++,c] +.Vertex buffer handle and creation ---- +// START c++ vk::raii::Buffer vertexBuffer = nullptr; ... @@ -73,6 +86,27 @@ void createVertexBuffer() { vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive }; vertexBuffer = vk::raii::Buffer(device, bufferInfo); } +// END c++ + +// START c +VkBuffer vertexBuffer = VK_NULL_HANDLE; + +void createVertexBuffer() { + VkBufferCreateInfo bufferInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .size = sizeof(vertices[0]) * vertices.size(), + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL + }; + if (vkCreateBuffer(device, &bufferInfo, NULL, &vertexBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } +} +// END c ---- The buffer should be available for use in rendering commands until the end of @@ -83,9 +117,17 @@ The buffer should be available for use in rendering commands until the end of The buffer has been created, but it doesn't have any memory assigned to it yet. The first step of allocating memory for the buffer is to query its memory requirements using the aptly named `vkGetBufferMemoryRequirements` function. -[,c++] +[source,multilang,c++,c] +.Memory requirements query ---- +// START c++ vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); +// END c++ + +// START c +VkMemoryRequirements memRequirements; +vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); +// END c ---- The `VkMemoryRequirements` struct has three fields: @@ -99,18 +141,35 @@ Each type of memory varies in terms of allowed operations and performance charac We need to combine the requirements of the buffer and our own application requirements to find the right type of memory to use. Let's create a new function `findMemoryType` for this purpose. -[,c++] +[source,multilang,c++,c] +.findMemoryType signature ---- +// START c++ uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { } +// END c++ + +// START c +uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + +} +// END c ---- First we need to query info about the available types of memory using `vkGetPhysicalDeviceMemoryProperties`. -[,c++] +[source,multilang,c++,c] +.Device memory properties query ---- +// START c++ vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice->getMemoryProperties(); +// END c++ + +// START c +VkPhysicalDeviceMemoryProperties memProperties; +vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); +// END c ---- The `VkPhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes` and `memoryHeaps`. @@ -120,8 +179,20 @@ Right now we'll only concern ourselves with the type of memory and not the heap Let's first find a memory type that is suitable for the buffer itself: -[,c++] +[source,multilang,c++,c] +.Find any suitable memory type ---- +// START c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i))) { + return i; + } +} + +throw std::runtime_error("failed to find suitable memory type!"); +// END c++ + +// START c for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if ((typeFilter & (1 << i))) { return i; @@ -129,6 +200,7 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { } throw std::runtime_error("failed to find suitable memory type!"); +// END c ---- The `typeFilter` parameter will be used to specify the bit field of memory types that are suitable. @@ -144,13 +216,24 @@ We'll see why when we map the memory. We can now modify the loop to also check for the support of this property: -[,c++] +[source,multilang,c++,c] +.Memory type with required properties ---- +// START c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } +} +// END c++ + +// START c for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { return i; } } +// END c ---- We may have more than one desirable property, so we should check if the result of the bitwise AND is not just non-zero, but equal to the desired properties bit field. @@ -160,29 +243,61 @@ If there is a memory type suitable for the buffer that also has all the properti We now have a way to determine the right memory type, so we can actually allocate the memory by filling in the `VkMemoryAllocateInfo` structure. -[,c++] +[source,multilang,c++,c] +.Memory allocation info ---- +// START c++ vk::MemoryAllocateInfo memoryAllocateInfo( memRequirements.size, findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) ); +// END c++ + +// START c +VkMemoryAllocateInfo memoryAllocateInfo = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) +}; +// END c ---- Memory allocation is now as simple as specifying the size and type, both of which are derived from the memory requirements of the vertex buffer and the desired property. Create a class member to store the handle to the memory and allocate it with `vkAllocateMemory`. -[,c++] +[source,multilang,c++,c] +.Allocate device memory for vertex buffer ---- +// START c++ vk::raii::Buffer vertexBuffer = nullptr; vk::raii::DeviceMemory vertexBufferMemory = nullptr; ... vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); +// END c++ + +// START c +VkDeviceMemory vertexBufferMemory = VK_NULL_HANDLE; + +... + +if (vkAllocateMemory(device, &memoryAllocateInfo, NULL, &vertexBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate vertex buffer memory!"); +} +// END c ---- If memory allocation was successful, then we can now associate this memory with the buffer using `vkBindBufferMemory`: -[,c++] +[source,multilang,c++,c] +.Bind buffer to memory ---- +// START c++ vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); +// END c++ + +// START c +vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); +// END c ---- The first three parameters are self-explanatory, and the fourth parameter is the offset within the region of memory. @@ -194,19 +309,41 @@ If the offset is non-zero, then it is required to be divisible by `memRequiremen It is now time to copy the vertex data to the buffer. This is done by https://en.wikipedia.org/wiki/Memory-mapped_I/O[mapping the buffer memory] into CPU accessible memory with `vkMapMemory`. -[,c++] +[source,multilang,c++,c] +.Map vertex buffer memory ---- +// START c++ void* data = vertexBufferMemory.mapMemory(0, bufferInfo.size); +// END c++ + +// START c +void* data = NULL; +if (vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data) != VK_SUCCESS) { + throw std::runtime_error("failed to map vertex buffer memory!"); +} +// END c ---- This function allows us to access a region of the specified memory resource defined by an offset and size. The offset and size here are `0` and `bufferInfo.size`, respectively. -[,c++] +[source,multilang,c++,c] +.Copy and unmap ---- +// START c++ void* data = vertexBufferMemory.mapMemory(0, bufferInfo.size); memcpy(data, vertices.data(), bufferInfo.size); vertexBufferMemory.unmapMemory(); +// END c++ + +// START c +void* data = NULL; +if (vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data) != VK_SUCCESS) { + throw std::runtime_error("failed to map vertex buffer memory!"); +} +memcpy(data, vertices.data(), bufferInfo.size); +vkUnmapMemory(device, vertexBufferMemory); +// END c ---- You can now simply `memcpy` the vertex data to the mapped memory and unmap it again using `vkUnmapMemory`. diff --git a/en/07_Depth_buffering.adoc b/en/07_Depth_buffering.adoc index a2fd710c..0626e77f 100644 --- a/en/07_Depth_buffering.adoc +++ b/en/07_Depth_buffering.adoc @@ -127,11 +127,20 @@ The difference is that the swap chain will not automatically create depth images We only need a single depth image, because only one draw operation is running at once. The depth image will again require the trifecta of resources: image, memory and image view. -[,c++] +[source,multilang,c++,c] +.Depth image resources ---- +// START c++ vk::raii::Image depthImage = nullptr; vk::raii::DeviceMemory depthImageMemory = nullptr; vk::raii::ImageView depthImageView = nullptr; +// END c++ + +// START c +VkImage depthImage = VK_NULL_HANDLE; +VkDeviceMemory depthImageMemory = VK_NULL_HANDLE; +VkImageView depthImageView = VK_NULL_HANDLE; +// END c ---- Create a new function `createDepthResources` to set up these resources: @@ -172,21 +181,40 @@ We'll look at this in a future chapter. We could simply go for the `VK_FORMAT_D32_SFLOAT` format, because support for it is extremely common (see the hardware database), but it's nice to add some extra flexibility to our application where possible. We're going to write a function `findSupportedFormat` that takes a list of candidate formats in order from most desirable to least desirable, and checks which is the first one that is supported: -[,c++] +[source,multilang,c++,c] +.findSupportedFormat signature ---- +// START c++ vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { } +// END c++ + +// START c +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + +} +// END c ---- The support of a format depends on the tiling mode and usage, so we must also include these as parameters. The support of a format can be queried using the `vkGetPhysicalDeviceFormatProperties` function: -[,c++] +[source,multilang,c++,c] +.Query format properties ---- +// START c++ for (const auto format : candidates) { vk::FormatProperties props = physicalDevice.getFormatProperties(format); } +// END c++ + +// START c +for (const auto format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); +} +// END c ---- The `VkFormatProperties` struct contains three fields: @@ -197,20 +225,34 @@ The `VkFormatProperties` struct contains three fields: Only the first two are relevant here, and the one we check depends on the `tiling` parameter of the function: -[,c++] +[source,multilang,c++,c] +.Check tiling features ---- +// START c++ if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { return format; } if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { return format; } +// END c++ + +// START c +if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; +} +if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; +} +// END c ---- If none of the candidate formats support the desired usage, then we can either return a special value or simply throw an exception: -[,c++] +[source,multilang,c++,c] +.findSupportedFormat implementation ---- +// START c++ vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { for (const auto format : candidates) { vk::FormatProperties props = physicalDevice.getFormatProperties(format); @@ -225,19 +267,51 @@ vk::Format findSupportedFormat(const std::vector& candidates, vk::Im throw std::runtime_error("failed to find supported format!"); } +// END c++ + +// START c +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (const auto format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); +} +// END c ---- We'll use this function now to create a `findDepthFormat` helper function to select a format with a depth component that supports usage as depth attachment: -[,c++] +[source,multilang,c++,c] +.findDepthFormat helper ---- -VkFormat findDepthFormat() { +// START c++ +vk::Format findDepthFormat() { return findSupportedFormat( {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment ); } +// END c++ + +// START c +VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); +} +// END c ---- Make sure to use the `VK_FORMAT_FEATURE_` flag instead of `VK_IMAGE_USAGE_` in this case. @@ -245,11 +319,20 @@ All of these candidate formats contain a depth component, but the latter two als We won't be using that yet, but we do need to take that into account when performing layout transitions on images with these formats. Add a simple helper function that tells us if the chosen depth format contains a stencil component: -[,c++] +[source,multilang,c++,c] +.hasStencilComponent helper ---- +// START c++ bool hasStencilComponent(vk::Format format) { return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; } +// END c++ + +// START c +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} +// END c ---- Call the function to find a depth format from `createDepthResources`: diff --git a/en/10_Multisampling.adoc b/en/10_Multisampling.adoc index 1aa0c685..9f818c1d 100644 --- a/en/10_Multisampling.adoc +++ b/en/10_Multisampling.adoc @@ -33,11 +33,20 @@ Let's start off by determining how many samples our hardware can use. Most modern GPUs support at least eight samples, but this number is not guaranteed to be the same everywhere. We'll keep track of it by adding a new class member: -[,c++] +[source,multilang,c++,c] +.MSAA sample count member ---- +// START c++ ... vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; ... +// END c++ + +// START c +... +VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; +... +// END c ---- By default, we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. @@ -46,8 +55,10 @@ We're using a depth buffer, so we have to take into account the sample count for The highest sample count that both support (and) will be the maximum we can support. Add a function that will fetch this information for us: -[,c++] +[source,multilang,c++,c] +.getMaxUsableSampleCount helper ---- +// START c++ vk::SampleCountFlagBits getMaxUsableSampleCount() { vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice->getProperties(); @@ -61,6 +72,24 @@ vk::SampleCountFlagBits getMaxUsableSampleCount() { return vk::SampleCountFlagBits::e1; } +// END c++ + +// START c +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSampleCountFlags counts = properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; +} +// END c ---- We will now use this function to set the `msaaSamples` variable during the physical device selection process. @@ -90,13 +119,24 @@ This is why we have to create an additional render target and modify our current We only need one render target since only one drawing operation is active at a time, just like with the depth buffer. Add the following class members: -[,c++] +[source,multilang,c++,c] +.Color image resources ---- +// START c++ ... vk::raii::Image colorImage = nullptr; vk::raii::DeviceMemory colorImageMemory = nullptr; vk::raii::ImageView colorImageView = nullptr; ... +// END c++ + +// START c +... +VkImage colorImage = VK_NULL_HANDLE; +VkDeviceMemory colorImageMemory = VK_NULL_HANDLE; +VkImageView colorImageView = VK_NULL_HANDLE; +... +// END c ---- This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the image creation process. diff --git a/en/14_Android.adoc b/en/14_Android.adoc index e5527d1e..9b9477bc 100644 --- a/en/14_Android.adoc +++ b/en/14_Android.adoc @@ -476,22 +476,34 @@ One of the key platform-specific differences in our Vulkan implementation is how Here's how we create a Vulkan surface on Android: -[source,cpp] +[source,multilang,c++,c] +.Creating the Vulkan Surface on Android ---- +// START c++ void createSurface() { - VkSurfaceKHR _surface; - VkResult result = VK_SUCCESS; + // RAII approach using Vulkan-Hpp + vk::AndroidSurfaceCreateInfoKHR createInfo{ + .window = androidApp->window + }; + surface = vk::raii::SurfaceKHR(instance, createInfo); +} +// END c++ + +// START c +void createSurface() { + VkSurfaceKHR _surface = VK_NULL_HANDLE; + + VkAndroidSurfaceCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .pNext = NULL, + .flags = 0, + .window = androidApp->window + }; - // Create Android surface - result = vkCreateAndroidSurfaceKHR( + VkResult result = vkCreateAndroidSurfaceKHR( *instance, - &(VkAndroidSurfaceCreateInfoKHR{ - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .window = androidApp->window - }), - nullptr, + &createInfo, + NULL, &_surface ); @@ -501,6 +513,7 @@ void createSurface() { surface = vk::raii::SurfaceKHR(instance, _surface); } +// END c ---- === Handling Android Events