1. Introduction
This document describes the V-EZ Vulkan wrapper library, what it is, what it abstracts away from the Vulkan API, and how to use it. Comparisons to Vulkan are made with the assumption the reader has a moderate understanding of the Khronos specification. In many sections of this documentation, details of functions calls are omitted since the behavior is nearly identical to Vulkan. The latest specification for the Vulkan API can be found here.
1.1. What is V-EZ?
V-EZ is designed to be a C based light-weight layer around Vulkan which maintains mostly identical semantics but abstracts away the lower level complexities. The motivation behind this design is to accelerate adoption of Vulkan among software vendors outside of the gaming industry, who desire modern graphics API features without all of the low level responsibilities.
1.2. What is Abstracted From Vulkan?
The following is a short list of low level Vulkan API features and responsibilities which are abstracted in V-EZ and no longer a concern of the application.
-
Memory management
-
Swapchain management
-
Render Passes
-
Pipeline permutations
-
Pipeline layouts
-
Pipeline barriers
-
Descriptor pools
-
Descriptor sets
-
Descriptor set layouts
-
Image layouts
-
GLSL compilation
Below is a diagram of the Vulkan API objects and their interactions.
The next image is a diagram of V-EZ. Notice the number of native Vulkan objects which are abstracted and whose responsibility is removed from the application.
1.3. Performance Comparisons
V-EZ incurs only slight overhead compared to native Vulkan API calls. However in most cases the performance overhead is negligible. Existing best practices with respect to Vulkan are still applicable with V-EZ. These include, but are not limited to:
-
Batching queue submissions
-
Multi-threaded command buffer recording
-
Reusing command buffers
-
Minimizing pipeline bindings
-
Minimizing resource bindings
-
Batching draw calls
1.4. Native Vulkan Calls
Most object handles created by V-EZ are the native Vulkan objects. These can be used in native Vulkan. There are only a few exceptions, which are prefixed with vez
rather than vk
. These object handles may only be passed to V-EZ.
-
vezSwapchain
-
vezCommandBuffer
-
vezPipeline
-
vezFramebuffer
Any native Vulkan API functions that do not exist within V-EZ may be used with Vulkan object handles returned by V-EZ.
2. Initialization
This section describes how an application initializes V-EZ, enumerates devices attached to the system and creates one or more handles to those devices for use.
2.1. Instances
The first step to using V-EZ is to create a VkInstance
object by calling vezCreateInstance
with appropriate parameter values set in VezInstanceCreateInfo
and VezApplicationInfo
. These structures allow an application to pass information about itself to V-EZ. Below is a simple example call.
1VezApplicationInfo appInfo = {};
2appInfo.pApplicationName = "MyApplication";
3appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
4appInfo.pEngineName = "MyEngine";
5appinfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
6
7VezInstanceCreateInfo createInfo = {};
8createInfo.pApplicationInfo = &appInfo;
9
10VkInstance instance = VK_NULL_HANDLE;
11VkResult result = vezCreateInstance(&createInfo, &instance);
As with Vulkan, instance layers and extensions can be enabled by passing the relevant string names to VezInstanceCreateInfo::ppEnabledLayerNames
and VezInstanceCreateInfo::ppEnabledExtensionNames
. For example, to enable the LunarG standard validation layer, the above code would be modified to be the following below.
1std::array<const char*> enabledLayers = { "VK_LAYER_LUNARG_standard_validation" };
2
3VezInstanceCreateInfo createInfo = {};
4createInfo.pApplicationInfo = &appInfo;
5createInfo.enabledLayerCount = static_cast<uint32_t>(enabledLayers.size());
6createInfo.ppEnabledLayerNames = enabledLayers.data();
The vezEnumerateInstanceExtensionProperties
and vezEnumerateInstanceLayerProperties
may be used to enumerate available instance extensions and layers. The behavior is identical to native Vulkan. For more information about vkCreateInstance and instance layers and extensions, see the Vulkan spec.
A VkInstance
object is destroyed by calling VezDestroyInstance
when an application terminates.
1vezDestroyInstance(instance);
2.2. Surfaces
V-EZ does not abstract or wrap creation of surfaces in Vulkan. Applications must use the platform specific WSI Vulkan extensions to create the surface. See the Vulkan spec for details.
2.3. Physical Devices
To retrieve a list of physical devices in the system, call vezEnumeratePhysicalDevices
, which behaves identically to its counterpart in Vulkan. Physical device properties and features can be retrieved via vezGetPhysicalDeviceProperties
and vezGetPhysicalDeviceFeatures
. The following code snippet demonstrates enumerating all available physical devices in a system and their properties and features.
1uint32_t physicalDeviceCount;
2vezEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr);
3
4std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
5vezEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data());
6
7for (auto physicalDevice : physicalDevices)
8{
9 VkPhysicalDeviceProperties properties;
10 vezGetPhysicalDeviceProperties(physicalDevice, &properties);
11
12 VkPhysicalDeviceFeatures features;
13 vezGetPhysicalDeviceFeatures(physicalDevice, &features);
14}
2.3.1. Extensions Support
All available Vulkan extensions for a physical device may be enumerated by calling vezEnumerateDeviceExtensionProperties
. Each VkExtensionProperties
entry in the output array will contain the string name of the supported extension. If an application needs to quickly check for the existence of a specific extension, it can call vezGetPhysicalDeviceExtensionSupport
.
2.3.2. Layers Support
As with physical device specific extensions, layers can be enumerated with vezEnumerateDeviceLayerProperties
.
2.4. Devices
Device objects represent logical connections to physical devices (see the Vulkan spec). An application must create separate device object handles for each physical device it will use. vezCreateDevice
is used to create a logical connection to a physical device.
An application may specify a list of device level extensions to enable (see the Vulkan spec) by populating vezCreateDeviceInfo::ppEnabledExtensionNames
. Device level layers can be enabled by populating vezCreateDeviceInfo::ppEnabledLayerNames
. The code snippet below demonstrates creating a device with the VK_KHR_swapchain
extension enabled.
1std::array<const char*, 1> enabledExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
2
3VezDeviceCreateInfo deviceCreateInfo = {};
4deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(enabledExtensions.size());
5deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
6
7VkDevice device = VK_NULL_HANDLE;
8VkResult result = vezCreateDevice(physicalDevice, &deviceCreateInfo, &device);
2.5. Swapchains and Present Support
To determine if a physical device supports a particular surface format, call vezGetPhysicalDeviceSurfaceFormats
. This will retrieve an array of VkFormat
and VkColorSpace
values which can be used to check for required compatibility, for example HDR10. In some cases a surface attached to a window handle may not support being presented to, or may only support present with a specific queue family index (see Queues). This can be determined by calling vezGetPhysicalDevicePresentSupport
.
After enumerating supported image formats and color spaces for a given physical device, an application can create one or more swapchains in V-EZ by calling vezCreateSwapchain
with appropriate parameters for VezSwapchainCreateInfo
. In contrast to Vulkan, V-EZ manages most of the responsibilities of an application’s swapchain.
1VezSwapchainCreateInfo swapchainCreateInfo = {};
2swapchainCreateInfo.surface = surface;
3swapchainCreateInfo.format = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
4
5VezSwapchain swapchain = VK_NULL_HANDLE;
6auto result = vezCreateSwapchain(device, &swapchainCreateInfo, &swapchain);
If the format specified in VezSwapchainCreateInfo
was not supported, V-EZ will choose a default one. An application may query the final image format by calling vezGetSwapchainSurfaceFormat
.
2.6. Putting It All Together
Below is a coding listing demonstrating each preceeding section.
1#include <iostream>
2#define GLFW_INCLUDE_NONE
3#define GLFW_EXPOSE_NATIVE_WIN32
4#include <GLFW/glfw3.h>
5#include <GLFW/glfw3native.h>
6#include <VEZ.h>
7
8GLFWwindow* window = nullptr;
9VkInstance instance = VK_NULL_HANDLE;
10VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
11VkSurfaceKHR surface = VK_NULL_HANDLE;
12VkDevice device = VK_NULL_HANDLE;
13VezSwapchain swapchain = VK_NULL_HANDLE;
14
15bool InitWindow()
16{
17 glfwInit();
18 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
19 window = glfwCreateWindow(800, 600, "MyApplication", nullptr, nullptr);
20 if (!window)
21 return false;
22
23 return true;
24}
25
26bool InitVulkanEZ()
27{
28 // Create the V-EZ instance.
29 VezApplicationInfo appInfo = {};
30 appInfo.pApplicationName = "MyApplication";
31 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
32 appInfo.pEngineName = "MyEngine";
33 appinfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
34
35 VezInstanceCreateInfo instanceCreateInfo = {};
36 instanceCreateInfo.pApplicationInfo = &appInfo;
37
38 VkResult result = vezCreateInstance(&instanceCreateInfo, &instance);
39 if (result != VK_SUCCESS)
40 return false;
41
42 // Create a surface to render to.
43 result = glfwCreateWindowSurface(instance, window, nullptr, &surface);
44 if (result != VK_SUCCESS)
45 return false;
46
47 // Enumerate and select the first discrete GPU physical device.
48 uint32_t physicalDeviceCount;
49 vezEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr);
50
51 std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
52 vezEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data());
53
54 for (auto pd : physicalDevices)
55 {
56 VkPhysicalDeviceProperties properties;
57 vezGetPhysicalDeviceProperties(pd, &properties);
58 if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
59 {
60 physicalDevice = pd;
61 break;
62 }
63 }
64
65 if (physicalDevice == VK_NULL_HANDLE)
66 return false;
67
68 // Create a surface.
69 VezSurfaceCreateInfo createInfo = {};
70 createInfo.hinstance = GetModuleHandle(nullptr);
71 createInfo.hwnd = glfwGetWin32Window(window);
72 result = vezCreateSurface(instance, &createInfo, &surface);
73 if (result != VK_SUCCESS)
74 return false;
75
76 // Create a logical device connection to the physical device.
77 VezDeviceCreateInfo deviceCreateInfo = {};
78 deviceCreateInfo.enabledExtensionCount = 0;
79 deviceCreateInfo.ppEnabledExtensionNames = nullptr;
80 result = vezCreateDevice(physicalDevice, &deviceCreateInfo, &device);
81 if (result != VK_SUCCESS)
82 return false;
83
84 // Create the swapchain.
85 VezSwapchainCreateInfo swapchainCreateInfo = {};
86 swapchainCreateInfo.surface = surface;
87 swapchainCreateInfo.format = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
88 result = vezCreateSwapchain(device, &swapchainCreateInfo, &swapchain);
89 if (result != VK_SUCCESS)
90 return false;
91
92 return true;
93}
94
95int main(int argc, char** argv)
96{
97 if (!InitGLFW())
98 {
99 std::cout << "Failed to create GLFW window!\n";
100 return -1;
101 }
102
103 if (!InitVulkanEZ())
104 {
105 std::cout << "Failed to initialize V-EZ!\n";
106 return -1;
107 }
108
109 vezDestroyDevice(device);
110 vezDestroyInstance(instance);
111 return 0;
112}
3. Queues
3.1. Queue Families
As discussed in the Vulkan spec, each physical device may have one or more queue families. A physical device’s queue families can be queried with vezGetPhysicalDeviceQueueFamilyProperties
. The returned VkQueueFamilyProperties
array contains the following fields:
-
queueFlags
indicating the capabilities of the queues in the queue family -
queueCount
a count of queues in this queue family
In V-EZ there is no need to explicitly create or request the different queue families. By default, all queue families and the maximum counts are created and accessible to the application. An application can obtain VkQueue
handles to each queue family by calling vezGetDeviceQueue
with the desired queueFamilyIndex
and queueIndex
, which must be less than queueCount
obtained in VkQueueFamilyProperties
. Utility functions are also provided to explicitly request the graphics, compute or transfer queue families.
1void vezGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue);
1void vezGetDeviceGraphicsQueue(VkDevice device, uint32_t queueIndex, VkQueue* pQueue);
2void vezGetDeviceComputeQueue(VkDevice device, uint32_t queueIndex, VkQueue* pQueue);
3void vezGetDeviceTransferQueue(VkDevice device, uint32_t queueIndex, VkQueue* pQueue);
VkQueue
handles do not need to be explicitly destroyed by the application.
3.2. Queue Submission
Queue submission behavior and syntax in V-EZ is nearly identical to Vulkan. An application fills in a VezSubmitInfo
structure as described in the Vulkan spec. Please see 4.3.5 Queue Submission for detailed information on this topic.
The primary differences are with respect to semaphores and fences. In V-EZ, semaphores and fences are never explicitly created by an application. Instead an application requests them to be created when calling VezQueueSubmit
by passing valid, uninitialized, object handles to VezSubmitInfo::pSignalSemaphores
and the final pFence
parameter of VezQueueSubmit
. V-EZ will allocate the objects and store them in these handles. An application is responsible for calling vezDestroyFence
for any VkFence
object handle returned by V-EZ. As in Vulkan, an application must ensure any queue submission has completed and the VkFence
object has been signaled before deletion.
For semaphores returned by V-EZ, an application may only pass them to proceeding calls to VezQueueSubmit
as wait semaphores. V-EZ will ensure the object handles are properly destroyed after the queue submission completes. External semaphores created by native Vulkan may be passed to V-EZ via VezSubmitInfo::pWaitSemaphores
.
1VkSemaphore semaphore = VK_NULL_HANDLE;
2VkFence fence = VK_NULL_HANDLE;
3
4VezSubmitInfo submitInfo = {};
5submitInfo.commandBufferCount = 1;
6submitInfo.pCommandBuffers = &commandBuffer;
7submitInfo.signalSemaphoreCount = 1;
8submitInfo.pSignalSemaphores = &semaphore;
9VkResult result = vezQueueSubmit(queue, 1, &submitInfo, &fence);
10
11// Pass semaphore to another vkQueueSubmit call as a wait semaphore.
12
13// Wait on fence to complete.
14vezWaitForFences(device, 1, &fence, VK_TRUE, ~0ULL);
15vezDestroyFence(device, fence);
More on semaphores and fences will be discussed in Synchronization.
3.3. Queue Presentation
V-EZ alleviates most of the responsibility for managing the Vulkan swapchain from the application. The only operation an application must perform to display a rendered image to a window is call vezQueuePresent
with the VezPresentInfo
parameter correctly set. The semantics of VezPresentInfo
differ quite a bit from Vulkan’s VkPresentInfoKHR
. Namely, in V-EZ an application presents a VkImage
object handle rather than specifying the image index of the Vulkan swapchain. Furthermore, V-EZ allows an application to specify signal semaphores in VezPresentInfo
which can be used to determine when the VkImage
object handle is no longer in use.
More than one swapchain may be presented in a single call. The pImages
array must contains the same number of entries as pSwapchains
.
1typedef struct VezPresentInfo {
2 const void* pNext;
3 uint32_t waitSemaphoreCount;
4 const VkSemaphore* pWaitSemaphores;
5 uint32_t swapchainCount;
6 const VezSwapchain* pSwapchains;
7 const VkImage* pImages;
8 uint32_t signalSemaphoreCount;
9 VkSemaphore* pSignalSemaphores;
10 VkResult* pResults;
11} VkPresentInfo;
The code listing below demonstrates presenting an image the application has rendered into, specifying a single wait semaphore from a previous queue submission and retrieving a signal semaphore.
1VkSemaphore signalSemaphore = VK_NULL_HANDLE;
2
3VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
4VezPresentInfo presentInfo = {};
5presentInfo.waitSemaphoreCount = 1;
6presentInfo.pWaitSemaphores = &semaphore;
7presentInfo.pWaitDstStageMask = &waitDstStageMask;
8presentInfo.swapchainCount = 1;
9presentInfo.pSwapchains = &swapchain;
10presentInfo.pImages = &srcImage;
11presentInfo.signalSemaphoreCount = 1;
12presentInfo.pSignalSemaphores = &signalSemaphore;
13VkResult result = vezQueuePresent(graphicsQueue, &presentInfo);
14
15// Wait on the signal semaphore before using renderedImage again.
16// Ex: pass the semaphore to VezSubmitInfo::pWaitSemaphores.
Note
|
Any images passed to VezPresentInfo::pImages must be created with the VK_IMAGE_USAGE_TRANSFER_SRC_BIT usage flag bit. See Resource Creation for details on image creation and usage flags.
|
3.3.1. Multisampled Images
If any entry in VezPresentInfo::pImages
is multisampled, no additional steps need to be taken before calling vezQueuePresent. V-EZ will automatically resolve the image as its presented to the target window.
4. Synchronization
In contrast to Vulkan, the application’s responsibilities for resource access synchronization is partially alleviated by V-EZ. Namely, Pipeline Barriers and Memory Barriers are no longer explicitly required by the application, but handled by V-EZ. Additionally, fences and semaphores are not explicitly created by the application but requested when calling VkQueueSubmit
or vkQueuePresent
as previously described in Queue Submission.
4.1. Fences
As described in the Vulkan spec, fences are a synchronization primitive that can be used between a queue and the host. In Vulkan, a fence is either signaled, after the execution of a queue submission, or unsignaled after being reset. In V-EZ, fences are never reset. Once a fence is requested, its status may be queried via vezGetFenceStatus
or waited on with vezWaitForFences
. An application must call vezDestroyFence
when the object is no longer used.
1// Submit a command buffer to a queue and request a fence object for the submission.
2VkFence fence = VK_NULL_HANDLE;
3
4VezSubmitInfo submitInfo = {};
5submitInfo.commandBufferCount = 1;
6submitInfo.pCommandBuffers = &commandBuffer;
7vezQueueSubmit(queue, 1, &submitInfo, &fence);
8
9// Wait until the queue submission has finished executing.
10vezWaitForFences(device, 1, &fence, VK_TRUE, ~0);
11
12// Destroy the fence.
13vezDestroyFence(device, fence);
4.2. Semaphores
Semaphores are a synchronization primitive used to insert dependencies between queue submissions within the same queue or between different queues. Like fences, semaphores can be signaled or unsignaled. For a more detailed explanation of semaphores, see the Vulkan spec.
An application may obtain semaphore objects when calling vkQueueSubmit
or vkQueuePresent
. In both cases, the semaphores are set to be signaled when either operation completes and may be passed as wait semaphores to a proceeding queue submission or present operation. In V-EZ, semaphores are never explicitly destroyed by the application, rather once they are passed as wait semaphores to a proceeding queue operation, the application no longer maintains ownership. See the previous code listing in Queue Presentation.
The only exception to this behavior is if a semaphore created by VEZ is then passed as a wait semaphore to a native Vulkan queue submission. In this case, the application must call vezDestroySemaphore once the native Vulkan queue submission completes and the semaphore has been waited on.
4.3. Events
Events in V-EZ have identical behavior and operation as in Vulkan. See the Vulkan spec for more information.
4.4. Wait Idle Operations
Wait idle operations in V-EZ have identical behavior and operation as in Vulkan. See the Vulkan spec for more information.
5. Pipelines
Pipeline creation and management in V-EZ has been significantly simplified compared to Vulkan. When creating a pipeline in Vulkan, the following graphics state and object handles must be passed to vkCreateGraphicsPipeline
.
-
Vertex input state
-
Input assembly state
-
Viewport state
-
Rasterization state
-
Multisample state
-
Depth stencil state
-
Color blend state
-
Pipeline layout
-
Render passed
-
Subpass index
V-EZ alleviates the need to specify any of this information at creation time, thus allowing an application the flexibility to use the same VezPipeline
object with different graphics state permutations, vertex attribute bindings and render passes.
5.1. Shader Modules
Shader modules represent the different shader stages of a pipeline. In Vulkan shader modules can only be created from SPIR-V binaries. However V-EZ allows shader module creation from both SPIR-V and GLSL source. To create a shader module, vezCreateShaderModule
is called with VezShaderModuleCreateInfo
specifying the stage and GLSL source or SPIR_V binary. The behavior and syntax remain largely the same as Vulkan, with the exception of added GLSL support. The code listing below shows a shader module being created from GLSL source.
1std::string glslSource = ReadFile("shader.vert");
2
3VezShaderModuleCreateInfo createInfo = {};
4createInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
5createInfo.codeSize = static_cast<uint32_t>(glslSource.size());
6createInfo.pGLSLSource = glslSource.c_str();
7createInfo.pEntryPoint = "main";
8
9VkShaderModule shaderModule = VK_NULL_HANDLE;
10VkResult result = vezCreateShaderModule(device, &createInfo, &shaderModule);
The VezShaderModuleCreateInfo::pEntryPoint
field is only required when creating a shader module from GLSL source. For more information on shader modules, please see the Vulkan spec.
5.2. Graphics Pipelines
To create a graphics pipeline, call vezCreateGraphicsPipeline
with an array of VezPipelineShaderStageCreateInfo
entries, one for each shader stage the pipeline will use. The code listing below assumes vertex and fragment shader modules have already been created.
1std::array<VezPipelineShaderStageCreateInfo, 2> stages = { { {}, {} } };
2stages[0] = {};
3stages[0].module = vertexStageShaderModule;
4stages[0].pEntryPoint = nullptr;
5stages[1] = {};
6stages[1].module = fragmentStageShaderModule;
7stages[1].pEntryPoint = nullptr;
8
9VezGraphicsPipelineCreateInfo createInfo = {};
10createInfo.stageCount = static_cast<uint32_t>(stages.size());
11createInfo.pStages = stages.data();
12
13VezPipeline pipeline = VK_NULL_HANDLE;
14VkResult result = vezCreateGraphicsPipeline(device, &createInfo, &pipeline);
The VezPipelineShaderStageCreateInfo::pEntryPoint
allows an application to re-specify alternate entry points for a shader module. This is useful in the case where a single shader module contains multiple entry points for a given shader stage and used between more than one pipeline.
5.3. Compute Pipelines
Compute pipelines are created with a single VezPipelineShaderStageCreateInfo
object and a call to vezCreateComputePipeline
.
5.4. Specialization Constants
Specialization constants in V-EZ work the same as they do in Vulkan. An application sets VezPipelineShaderStageCreateInfo::pSpecializationInfo
accordingly. For more information on specialization constants, please see the Vulkan spec.
5.5. Push Constants
Push constants are a "high speed path to modify constant data in pipelines that is expected to out perform memory-backed resource updates". For more information on push constants, please see the Vulkan spec.
V-EZ simplifies the use of push constants by only requiring an application call vezCmdPushConstants
to update the memory, requiring only an offset, size and pointer to host data to update the push constants memory with. A valid pipeline must be bound before calling vezCmdPushConstants
.
1void vezCmdPushConstants(VezCommandBuffer commandBuffer, uint32_t offset, uint32_t size, const void* pValues);
In the case where multiple shader stages within a pipeline define push constants at different byte offsets, an application may query these offsets and sizes with V-EZ’s pipeline reflection described in the next section.
5.6. Pipeline Reflection
Unlike Vulkan, V-EZ allows full pipeline shader stage reflection if the shader stages were created from GLSL source, or the SPIR-V binary source contains the relevant meta-data. To enumerate all pipeline resources call vezEnumeratePipelineResources
, which returns an array of VkPipelineResource
entries.
1typedef struct VezMemberInfo
2{
3 VezBaseType baseType;
4 uint32_t offset;
5 uint32_t size;
6 uint32_t vecSize;
7 uint32_t arraySize;
8 char name[VK_MAX_DESCRIPTION_SIZE];
9 const VezMemberInfo* pNext;
10 const VezMemberInfo* pMembers;
11} VezMemberInfo;
12
13typedef struct VezPipelineResource {
14 VkShaderStageFlags stages;
15 VezPipelineResourceType resourceType;
16 VezBaseType baseType;
17 VkAccessFlags access;
18 uint32_t set;
19 uint32_t binding;
20 uint32_t location;
21 uint32_t inputAttachmentIndex;
22 uint32_t vecSize;
23 uint32_t arraySize;
24 uint32_t offset;
25 uint32_t size;
26 char name[VK_MAX_DESCRIPTION_SIZE];
27 const VezMemberInfo* pMembers;
28} VezPipelineResource;
The stages field is a bitmask of all shader stages the resource is used in. For example, a the same uniform buffer binding might be bound and accessed in both the vertex and fragment shader stages.
The resourceType field specifies one of the following values:
1typedef enum VezPipelineResourceType {
2 VEZ_PIPELINE_RESOURCE_TYPE_INPUT = 0,
3 VEZ_PIPELINE_RESOURCE_TYPE_OUTPUT = 1,
4 VEZ_PIPELINE_RESOURCE_TYPE_SAMPLER = 2,
5 VEZ_PIPELINE_RESOURCE_TYPE_COMBINED_IMAGE_SAMPLER = 3,
6 VEZ_PIPELINE_RESOURCE_TYPE_SAMPLED_IMAGE = 4,
7 VEZ_PIPELINE_RESOURCE_TYPE_STORAGE_IMAGE = 5,
8 VEZ_PIPELINE_RESOURCE_TYPE_UNIFORM_TEXEL_BUFFER = 6,
9 VEZ_PIPELINE_RESOURCE_TYPE_STORAGE_TEXEL_BUFFER = 7,
10 VEZ_PIPELINE_RESOURCE_TYPE_UNIFORM_BUFFER = 8,
11 VEZ_PIPELINE_RESOURCE_TYPE_STORAGE_BUFFER = 9,
12 VEZ_PIPELINE_RESOURCE_TYPE_ATOMIC_COUNTER = 10,
13 VEZ_PIPELINE_RESOURCE_TYPE_PUSH_CONSTANT_BUFFER = 11,
14} VezPipelineResourceType;
The baseType field specifies the data type. For GLSL vec4, baseType would be VEZ_PIPELINE_RESOURCE_BASE_TYPE_FLOAT for example.
1typedef enum VezBaseType {
2 VEZ_BASE_TYPE_BOOL = 0,
3 VEZ_BASE_TYPE_CHAR = 1,
4 VEZ_BASE_TYPE_INT = 2,
5 VEZ_BASE_TYPE_UINT = 3,
6 VEZ_BASE_TYPE_UINT64 = 4,
7 VEZ_BASE_TYPE_HALF = 5,
8 VEZ_BASE_TYPE_FLOAT = 6,
9 VEZ_BASE_TYPE_DOUBLE = 7,
10 VEZ_BASE_TYPE_STRUCT = 8,
11} VezBaseType;
The access field specifies whether the resource has read and/or write access.
The set and binding fields specify the set number and binding point in the shader as declared in the GLSL source or SPIR-V binary. For additional information on this, please see the GLSL Guide at the end of this document.
For shader stage inputs and outputs, the location field specifies the location binding. Examples include fragment stage output, inputs and outputs between shader stages, etc.
For renderpass subpasses, the inputAttachmentIndex specifies the index the attachment is bound to.
The vecSize field specifies the number of components. If the shader resource is vec3, vecSize will equal 3. For scalar pipeline resources, vecSize will be 1.
The 'arraySize' field specifies the number of elements in the pipeline resource’s array declaration. For example, in the code listing below, the arraySize would be 10.
1layout (set=0, binding=0) uniform sampler2D textures[10];
The 'offset' and 'size' specify the byte offset and byte size of a resource when declared inside of a struct.
If an application knows the name of the pipeline resource to query, vkGetPipelineResource
may be used.
For uniform and shader storage buffer objects, the pMembers field forms a multi-level linked list of members of these structured bindings. See the PipelineReflection sample for how to iterate over these.
5.7. Pipeline Binding
Graphics pipelines must be bound between vezCmdBeginRenderPass
and vezCmdEndRenderPass
calls. Compute pipelines must be bound outside of a render pass. To bind a pipeline, call vezCmdBindPipeline
.
6. Vertex Input Formats
In Vulkan the expected vertex input state must be specified at graphics pipeline creation time. The downside to this is that an application must manage different permutations of the same pipeline if it’s used with more than one vertex input format. The vertex input format consists of an array of vertex binding descriptions, via VkVertexInputBindingDescription
, and vertex attribute descriptions, via VkVertexInputAttributeDescription
.
V-EZ alleviates this requirement, as graphics pipeline creation only requires an array of shader modules (Graphics Pipelines). Instead, the desired vertex input format is set during command buffer recording by calling vezCmdSetVertexInputFormat
.
1void vezCmdSetVertexInputFormat(VkCommandBuffer commandBuffer, VezVertexInputFormat format);
6.1. Creation
An application now only needs to manage vertex input formats at the mesh or object level. V-EZ moves the specification of the VkVertexInputBindingDescription
and VkVertexInputAttributeDescription
arrays to the VkVertexInputFormat
object creation. In the code listing below, a vertex input format is created specifying a single binding point and two vertex attributes.
1// All vertex attributes bound using a single buffer binding point.
2VkVertexInputBindingDescription bindingDescription = {};
3bindingDescription.binding = 0;
4bindingDescription.stride = sizeof(float) * 5;
5bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
6
7// XYZ position and XY texture coordinate attributes declared.
8std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions;
9attributeDescriptions[0].location = 0;
10attributeDescriptions[0].binding = 0;
11attributeDescriptions[0].offset = 0;
12attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
13attributeDescriptions[1].location = 1;
14attributeDescriptions[1].binding = 0;
15attributeDescriptions[1].offset = sizeof(float) * 3;
16attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
17
18VezVertexInputFormatCreateInfo createInfo = {};
19createInfo.vertexBindingDescriptionCount = 1;
20createInfo.pVertexBindingDescriptions = &bindingDescription;
21createInfo.vertexAttributeDescriptionCount = static_cast<uin32_t>(attributeDescriptions.size());
22createInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
23
24VezVertexInputFormat vertexInputFormat = VK_NULL_HANDLE;
25VkResult result = vezCreateVertexInputFormat(device, &createInfo, &vertexInputFormat);
The rules and semantics of specifying VkVertexInputBindingDescription
and VkVertexInputAttributeDescription
follow the same rules as specified in the Vulkan spec.
6.2. Destruction
When an application no longer requires a VezVertexInputFormat
it should be destroyed by calling vezDestroyVertexInputFormat
.
7. Resource Creation
Like Vulkan, V-EZ supports two low-level resource types: buffers and images. However V-EZ does not require an application to manage the underlying memory, which is completely abstracted away. All the application sees are VkBuffer
and VkImage
objects. For more information on resource creation and memory, see the Vulkan spec.
Since memory management is hidden from the application, V-EZ exposes VezMemoryFlags
when creating buffers and images. These flags allow an application to specify how the memory will be used and where the it should reside.
1typedef enum VezMemoryFlagsBits {
2 VEZ_MEMORY_GPU_ONLY = 0x00000000,
3 VEZ_MEMORY_CPU_ONLY = 0x00000001,
4 VEZ_MEMORY_CPU_TO_GPU = 0x00000002,
5 VEZ_MEMORY_GPU_TO_CPU = 0x00000004,
6 VEZ_MEMORY_DEDICATED_ALLOCATION = 0x00000008,
7} VezMemoryFlagsBits;
VEZ_MEMORY_GPU_ONLY specifies a buffer or image memory backing should reside in a device’s local memory only.
VEZ_MEMORY_CPU_ONLY specifies a buffer or image memory backing should reside in host memory.
VEZ_MEMORY_CPU_TO_GPU specifies a buffer or image memory backing should be optimized for data transfers from the host to the device.
VEZ_MEMORY_GPU_TO_CPU specifies a buffer or image memory backing should be optimized for data transfers from the device to the host.
VEZ_MEMORY_DEDICATED_ALLOCATION specifies that the buffer or image should use a unique memory block.
V-EZ’s internal memory management sub-allocates memory blocks by default. If an application prefers that a particular buffer or image have its own dedicated memory block, VEZ_MEMORY_DEDICATED_ALLOCATION can be used.
7.1. Buffers
Buffers are represented by VkBuffer
handles and created by calling vkCreateBuffer
. The code listing below demonstrates creating a buffer to be used for staging, or data transfers optimized for host to device.
1VezBufferCreateInfo createInfo = {};
2createInfo.size = (32 << 20ULL) // 32 megabytes
3createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
4
5VkBuffer buffer = VK_NULL_HANDLE;
6VkResult result = vezCreateBuffer(device, VEZMEMORY_CPU_TO_GPU, &createInfo, &buffer);
The application must always specify the intended usage for a buffer using the VezBufferUsageFlagBits
enumeration values. In the code listing above, the buffer is only used as a source for data transfers.
Like Vulkan, V-EZ allows an application to specify an array of queue family indices the buffer will be used with. If an application sets queueFamilyIndexCount
to 0, V-EZ defaults the buffer to being accessible to all queue families.
When a VkBuffer
handle is no longer used by an application, it should be destroyed with vezDestroyBuffer
.
7.2. Buffer Views
Creating buffer views from buffers has nearly identical syntax to Vulkan. An application calls vezCreateBufferView
with appropriate values for fields in VezBufferViewCreateInfo
. Buffer views are destroyed by calling vezDestroyBufferView
. For more information on buffer views, see the Vulkan spec.
7.3. Images
Images are represented by VkImage
handles and created by calling vezCreateImage
. Like buffers, VezMemoryFlags
is passed to vezCreateImage
allowing an application to specify how the memory will be used and where the it should reside. The syntax and behavior of vezCreateImage
and VezImageCreateInfo
in V-EZ are nearly identical to Vulkan, see the Vulkan spec for more information.
As with buffers, if the queueFamilyIndexCount in VezImageCreateInfo
is set to 0, then V-EZ assumes the image will be used with all available queue families.
7.4. Image Views
See the Vulkan spec for more information on image views. The behavior and syntax in V-EZ is nearly identical to Vulkan.
7.5. Framebuffers
In Vulkan, an application must specify an array of VkImageView
attachments, a compatible Render Pass, and the dimensions when creating a framebuffer. V-EZ simplifies this to only requiring the array of attachments and the dimensions. Render passes are never explicitly created by applications V-EZ (see Render Passes), and therefore are not required when creating a framebuffer.
The coding listing below demonstrates creating a simple framebuffer from a color and depth image. Note that the usage parameter for the color image has the VK_IMAGE_USAGE_TRANSFER_SRC_BIT set. This is required for any image presented to a window when calling vezQueuePresent
.
1// Create the color image.
2VezImageCreateInfo imageCreateInfo = {};
3imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
4imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
5imageCreateInfo.extent = { width, height, 1 };
6imageCreateInfo.mipLevels = 1;
7imageCreateInfo.arrayLayers = 1;
8imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
9imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
10imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
11
12VkImage colorImage = VK_NULL_HANDLE;
13vezCreateImage(device, VEZ_MEMORY_GPU_ONLY, &imageCreateInfo, &colorImage);
14
15// Create the image view.
16VezImageViewCreateInfo imageViewCreateInfo = {};
17imageViewCreateInfo.image = colorImage;
18imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
19imageViewCreateInfo.format = imageCreateInfo.format;
20imageViewCreateInfo.subresourceRange.layerCount = 1;
21imageViewCreateInfo.subresourceRange.levelCount = 1;
22
23VkImageView colorImageView = VK_NULL_HANDLE;
24vezCreateImageView(device, &imageViewCreateInfo, &colorImageView);
25
26// Create the depth image attachment.
27imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
28imageCreateInfo.format = VK_FORMAT_D32_SFLOAT;
29imageCreateInfo.extent = { width, height, 1 };
30imageCreateInfo.mipLevels = 1;
31imageCreateInfo.arrayLayers = 1;
32imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
33imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
34imageCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
35
36VkImage depthImage = VK_NULL_HANDLE;
37vezCreateImage(device, VEZ_MEMORY_GPU_ONLY, &imageCreateInfo, &depthImage);
38
39// Create the image view.
40imageViewCreateInfo.image = depthImage;
41imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
42imageViewCreateInfo.format = imageCreateInfo.format;
43imageViewCreateInfo.subresourceRange.layerCount = 1;
44imageViewCreateInfo.subresourceRange.levelCount = 1;
45
46VkImageView depthImageView = VK_NULL_HANDLE;
47vezCreateImageView(device, &imageViewCreateInfo, &depthImageView);
48
49// Create the framebuffer.
50std::array<VkImageView, 2> attachments = { colorImageView, depthImageView };
51VezFramebufferCreateInfo framebufferCreateInfo = {};
52framebufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
53framebufferCreateInfo.pAttachments = attachments.data();
54framebufferCreateInfo.width = width;
55framebufferCreateInfo.height = height;
56framebufferCreateInfo.depth = 1;
57VezFramebuffer framebuffer = VK_NULL_HANDLE;
58vezCreateFramebuffer(device, &framebufferCreateInfo, &framebuffer);
Framebuffers are destroyed by calling vezDestroyFramebuffer
.
7.5.1. Multisampled Framebuffers
An application may create a multisampled framebuffer by setting VezImageCreateInfo::samples
to the appropriate value when creating the color and depth stencil attachments. Then the multisample state block must be set appropriately to enable multisampled rendering (see Graphics State).
7.5.2. Framebuffers With No Attachments
As described in the Vulkan specification, framebuffers with no attachments but valid dimensions may still be created. To create a framebuffer with no attachments in V-EZ, simply set VezFramebufferCreateInfo::pAttachments
to null and VezFramebufferCreateInfo::attachmentCount
to 0. Then set appropriate values for width, height, depth and samples.
8. Samplers
Sampler creation and usage in V-EZ is identical to Vulkan. For more information, see the Vulkan spec.
9. Command Buffers
The primary differences between Vulkan and V-EZ with respect to command buffers are the removal of command pools and secondary command buffers in V-EZ. An application need no longer manage individual command pools across threads. Only VkCommandBuffer
handles are created by an applications in V-EZ.
A secondary difference is with respect to pipeline barriers. As previously stated in Synchronization, V-EZ does not expose pipeline barriers. Within a command buffer, and between command buffer submissions, pipeline barriers are inserted automatically. An application is no longer responsible for managing this level of synchronization.
Other aspects of command buffers, such as state persistence within a command buffer, is identical to Vulkan. Existing Vulkan command buffer commands are also present, along with additional ones (see Resource Binding).
9.1. Allocation and Management
To create a command buffer an application calls vezAllocateCommandBuffers
. The intended queue the command buffer will be used on must be specified.
1VkQueue graphicsQueue = VK_NULL_HANDLE;
2vezGetDeviceGraphicsQueue(device, 0, &graphicsQueue);
3
4VezCommandBufferAllocateInfo allocInfo = {};
5allocInfo.queue = graphicsQueue;
6allocInfo.commandBufferCount = 1;
7VezCommandBuffer commandBuffer = VK_NULL_HANDLE;
8VkResult result = vezAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
A command buffer must be destroyed by calling vezFreeCommandBuffers
.
9.2. Recording
An application may begin recording commands to a VkCommandBuffer
handle by calling vezBeginCommandBuffer
. The only required parameter is VkCommandBufferUsageFlags
which gives hints to the driver about how it will be used. See the Vulkan spec for more details. Unlike native Vulkan, V-EZ’s equivalent command buffer functions do not require the VkCommandBuffer handle be passed in. Rather, all vezCmd* functions are associated with the VkCommandBuffer passed to vezBeginCommandBuffer within the same application thread. Commands recorded across threads must indepdently call vezBeginCommandBuffer. An application ends recording by calling vezEndCommandBuffer
.
Like Vulkan, an application must wait for a previously submitted VkCommandBuffer
object handle to not be in use before re-recording. Applications should track queue submissions with fences and query the fence status, or wait, before re-recording commands. See fences under Synchronization.
9.3. Graphics State
As described in Pipelines, V-EZ removes all graphics state specification from pipeline creation. In V-EZ, graphics state is set dynamically while recording a command buffer. Furthermore, all available dynamic states from Vulkan are enabled by default and their corresponding command buffer functions made available in V-EZ. States are not required to be set within a render pass. The following is a list of states available to be set.
1typedef struct VezInputAssemblyState {
2 VkPrimitiveTopology topology;
3 VkBool32 primitiveRestartEnable;
4} VezInputAssemblyState;
5
6typedef struct VezRasterizationState {
7 VkBool32 depthClampEnable;
8 VkBool32 rasterizerDiscardEnable;
9 VkPolygonMode polygonMode;
10 VkCullModeFlags cullMode;
11 VkFrontFace frontFace;
12 VkBool32 depthBiasEnable;
13 float depthBiasConstantFactor;
14 float depthBiasClamp;
15 float depthBiasSlopeFactor;
16} VezRasterizationState;
17
18typedef struct VezMultisampleState {
19 VkSampleCountFlagBits rasterizationSamples;
20 VkBool32 sampleShadingEnable;
21 float minSampleShading;
22 const VkSampleMask* pSampleMask;
23 VkBool32 alphaToCoverageEnable;
24 VkBool32 alphaToOneEnable;
25} VezMultisampleStateCreateInfo;
26
27typedef struct VezStencilOpState {
28 VkStencilOp failOp;
29 VkStencilOp passOp;
30 VkStencilOp depthFailOp;
31 VkCompareOp compareOp;
32} VezStencilOpState;
33
34typedef struct VezDepthStencilState {
35 VkBool32 depthTestEnable;
36 VkBool32 depthWriteEnable;
37 VkCompareOp depthCompareOp;
38 VkBool32 depthBoundsTestEnable;
39 VkBool32 stencilTestEnable;
40 VkStencilOpState front;
41 VkStencilOpState back;
42} VezDepthStencilState;
43
44typedef struct VezColorBlendAttachmentState {
45 VkBool32 blendEnable;
46 VkBlendFactor srcColorBlendFactor;
47 VkBlendFactor dstColorBlendFactor;
48 VkBlendOp colorBlendOp;
49 VkBlendFactor srcAlphaBlendFactor;
50 VkBlendFactor dstAlphaBlendFactor;
51 VkBlendOp alphaBlendOp;
52 VkColorComponentFlags colorWriteMask;
53} VezColorBlendAttachmentState;
54
55typedef struct VezColorBlendState {
56 VkBool32 logicOpEnable;
57 VkLogicOp logicOp;
58 uint32_t attachmentCount;
59 const VkPipelineColorBlendAttachmentState* pAttachments;
60} VezColorBlendState;
An application is not required to set these, as V-EZ sets default values when an application calls vezBeginCommandBuffer
. The following tables list the default values for each state.
Field name | Default value |
---|---|
topology |
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST |
primitiveRestartEnable |
VK_FALSE |
Field name | Default value |
---|---|
depthClampEnable |
VK_FALSE |
rasterizerDiscardEnable |
VK_FALSE |
polygonMode |
VK_POLYGON_MODE_FILL |
cullMode |
VK_CULL_MODE_NONE |
frontFace |
VK_FRONT_FACE_COUNTER_CLOCKWISE |
depthBiasEnable |
VK_FALSE |
depthBiasConstantFactor |
0.0 |
depthBiasClamp |
1.0 |
depthBiasSlopeFactor |
1.0 |
Field name | Default value |
---|---|
rasterizationSamples |
VK_SAMPLE_COUNT_1_BIT |
sampleShadingEnable |
VK_FALSE |
minSampleShading |
1.0 |
pSampleMask |
NULL |
alphaToCoverageEnable |
VK_FALSE |
alphaToOneEnable |
VK_FALSE |
Field name | Default value |
---|---|
depthTestEnable |
VK_FALSE |
depthWriteEnable |
VK_TRUE |
depthCompareOp |
VK_COMPARE_OP_LESS_OR_EQUAL |
depthBoundsTestEnable |
VK_FALSE |
stencilTestEnable |
VK_FALSE |
front |
N/A |
back |
N/A |
Field name | Default value |
---|---|
logicOpEnable |
VK_FALSE |
logicOp |
VK_LOGIC_OP_SET |
attachmentCount |
0 |
pAttachments |
NULL |
All state blocks are set together, therefore default values must still be set when the application only needs to set a single field. For example, when enabling backface culling, the polygonMode should still be set. The code listing below demonstrates setting setting the viewport, expected primitive topology and enabling backface culling and depth testing.
1vezBeginCommandBuffer(commandBuffer, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
2
3VkViewport viewport = { 0.0f, 0.0f, width, height, 0.0f, 1.0f };
4vezCmdSetViewport(0, 1, &viewport);
5vezCmdSetViewportState(1);
6
7VezInputAssemblyState inputAssemblyState = {};
8inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
9inputAssemblyState.primitiveRestartEnable = VK_FALSE;
10vezCmdSetInputAssemblyState(&inputAssemblyState);
11
12VezRasterizationState rasterizationState = {};
13rasterizationState.polygonMode = VK_POLGYON_MODE_FILL;
14rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
15rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
16vezCmdSetRasterizationState(&rasterizationState);
17
18VezDepthStencilState depthStencilState = {};
19depthStencilState.depthTestEnable = VK_TRUE;
20depthStencilState.depthWriteEnable = VK_FALSE;
21depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
22vezCmdSetDepthStencilState(&depthStencilState);
23
24// Draw commands
25
26vezEndCommandBuffer();
9.4. Resource Binding
In Vulkan descriptor sets are required for binding resources to different bindings for use in a pipeline. The complexities of descriptor set layouts, descriptor pools and updating descriptor set objects has been abstracted away in V-EZ. Instead a simplified interface of explicitly binding buffers, bufferViews, and images to set and binding indices during command buffer recording is exposed. These bindings are persistent only with a command buffer, but maintain persistence across pipeline bindings.
Each binding function for different resource types requires the set number, binding, and array element index. The following functions are available for binding each resource type.
void vezCmdBindBuffer(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32_t set, uint32_t binding, uint32_t arrayElement)
void vezCmdBindBufferView(VkBufferView bufferView, uint32_t set, uint32_t binding, uint32_t arrayElement)
void vezCmdBindImageView(VkImageView imageView, VkSampler sampler, uint32_t set, uint32_t binding, uint32_t arrayElement);
void vezCmdBindSampler(VkSampler sampler, uint32_t set, uint32_t binding, uint32_t arrayElement);
Note
|
The preprocessor macro VK_WHOLE_SIZE may be passed to the range parameter of vezCmdBindBuffer when an application desires to bind the entire buffer and not a sub range.
|
vkCmdBindImageView
allows an application to specify a sampler alongside the image view. When sampler is VK_NULL_HANDLE, the binding represents a sampled image or texture2D in GLSL. When sample is not VK_NULL_HANDLE, it represents a combined image sampler or 'sampler2D' in GLSL. See 13.1.3 Sampled Image in the Vulkan spec for more details.
10. Render Passes
Render passes in Vulkan are used to encapsulate all draw commands. No draw command may occur outside of a render pass. Render passes allow an application to define a sequence of dependent stages within an application’s rendering pipeline that should be synchronized and may access the same framebuffer attachments. Desktop applications may not benefit as much from this feature as mobile applications, however use of render passes is still encouraged as AMD’s driver can make specific optimizations under many circumstances.
Render passes are greatly simplified in V-EZ. Applications are no longer required to:
-
Explicitly create render pass objects
-
Manage render pass compatibility with pipelines and framebuffers
-
Define attachment image layouts and transitions
-
Define input attachments
-
Define all subpasses up front
-
Define subpass dependencies
These tasks are handled automatically by V-EZ, and in some cases inferred from the SPIR-V of bound pipeline shader stages. To learn more about render passes and their use cases, please see the Vulkan spec.
10.1. Begin A Render Pass
To begin a render pass, an application may call vkCmdBeginRenderPass
. The VezRenderPassBeginInfo
structure requires a target framebuffer to be specified, an array of VezAttachmentReference
structures which define load operations, store operations and clear values. The code listing below demonstrates beginning a render pass with two attachments, namely a single color attachment and a depth stencil attachment. Both attachments are cleared and any values written by the fragment shader stage stored.
1// Clear the framebuffer's color and depth attachments (set clear color to red).
2// Always store the fragment stage results.
3std::array<VezAttachmentReference, 2> attachmentReferences = {};
4attachmentReferences[0].clearValue.color = { 0.3f, 0.3f, 0.3f, 0.0f };
5attachmentReferences[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
6attachmentReferences[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
7attachmentReferences[1].clearValue.depthStencil.depth = 1.0f;
8attachmentReferences[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
9attachmentReferences[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
10
11// Begin a render pass.
12VezRenderPassBeginInfo beginInfo = {};
13beginInfo.framebuffer = framebuffer;
14beginInfo.attachmentCount = static_cast<uint32_t>(attachmentReferences.size());
15beginInfo.pAttachments = attachmentReferences.data();
16vezCmdBeginRenderPass(commandBuffer, &beginInfo);
10.2. Next Subpass
By default, calling vkCmdBeginRenderPass starts the first subpass. To transition to a proceeding one, an application may call vezCmdNextSubpass
. No parameters are required except the command buffer the call is being recorded to.
As previously stated, V-EZ determines which framebuffer attachments are written to within each subpass based on the pipeline objects that are bound and draw calls made.
10.3. End A Render Pass
To end a render pass, call vezCmdEndRenderPass
.
10.4. Input Attachments
Vulkan allows framebuffer attachments to be used as inputs or outputs within a render pass. One subpass may write to a color attachment while a proceeding subpass may read from it. V-EZ infers this information from the bound pipeline shader stages, specifically the GLSL subpassInput
uniform type (see 13.1.11. Input Attachment in the Vulkan spec for more details. In the code snippet below, the first subpass outputs to two attachments for color and surface normals.
1#version 450
2#extension GL_ARB_separate_shader_objects : enable
3
4layout(location = 0) in VS_OUT
5{
6 vec3 worldNormal;
7} ps_in;
8
9layout(location = 0) out vec4 albedo;
10layout(location = 1) out vec4 normals;
11
12void main()
13{
14 albedo = ...
15 normals = ps_in.worldNormal;
16}
In the next subpass, the framebuffer attachment storing the normals, index 1, is bound as an input attachment within the shader.
1#version 450
2#extension GL_ARB_separate_shader_objects : enable
3
4layout(input_attachment_index = 1, set = 0, binding = 0) uniform subpassInput normals;
5layout(location = 0) out vec4 finalColor;
6
7void main()
8{
9 vec4 normal = subpassLoad(normals);
10 finalColor = ...
11}
The input_attachment_index
value used in the shader should match the index of the attachment when the framebuffer was created. In V-EZ these must be absolute indices. The same applies for the location
index for output attachments.
11. Updating Memory
Since V-EZ does not explicitly expose underlying memory handles as in Vulkan, applications update buffer and image data using the VkBuffer
and VkImage
handles directly.
11.1. Map and Unmap
V-EZ exposes map and unmap operations for VkBuffer
handles only. An application must ensure that the target VkBuffer
handle was created with the correct VezMemoryFlags
. The VEZ_MEMORY_GPU_ONLY is the only enumeration value which cannot be mapped.
To map and unmap a buffer, an application calls VezMapBuffer
and vezUnmapBuffer
. Care must be taken to call vezFlushMappedBufferRanges
to guarantee writes by the host are made available to the device. The behavior remains nearly indentical to Vulkan’s vkMapMemory
function. See the Vulkan spec for more details.
11.2. Utility Functions
V-EZ simplifies host to device data transfers by providing two utility functions. Both are synchronous function calls on the host and block the calling thread until completion.
1VkResult vezBufferSubData(VkDevice device, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, const void* pData);
2
3typedef struct VezImageSubDataInfo {
4 uint32_t dataRowLength;
5 uint32_t dataImageHeight;
6 VezImageSubresourceLayers imageSubresource;
7 VkOffset3D imageOffset;
8 VkExtent3D imageExtent;
9} VezImageSubDataInfo;
10
11VkResult vezImageSubData(VkDevice device, VkImage image, const VezImageSubDataInfo* pSubDataInfo, const void* pData);
12. GLSL Guide
TODO