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.

Vulkan API Objects

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.

V-EZ API

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.

VezInputAssemblyState: Default values
Field name Default value

topology

VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST

primitiveRestartEnable

VK_FALSE


VezRasterizationState: Default values
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


VezMultisampleState: Default values
Field name Default value

rasterizationSamples

VK_SAMPLE_COUNT_1_BIT

sampleShadingEnable

VK_FALSE

minSampleShading

1.0

pSampleMask

NULL

alphaToCoverageEnable

VK_FALSE

alphaToOneEnable

VK_FALSE


VezDepthStencilState: Default values
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


VezColorBlendState: Default values
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