vkcpp -...

Post on 30-Sep-2020

1 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

April 4-7, 2016 | Silicon Valley

Markus Tavenrath

Senior Developer Technology Engineer

mtavenrath@nvidia.com

4/4/2016

VKCPP

2

INTRODUCTION

Senior Dev Tech Software Engineer - Professional Visualization

Joined NVIDIA 8 years ago to work on middleware

Goal: Make GPU programming easy and efficient

Working with CAD ISVs to optimizing their graphics pipelines

Working with driver team on Vulkan and OpenGL performance

Who am I?

3

INTRODUCTIONWhat is Vulkan?

4/4/2016

Clean, modern and consistent C-API without cruft

Low CPU overhead

Scales well with multiple threads

Provides ‚low level‘ control over GPU

Moves a lot of responsibility from driver to developer

Developer essentialy writes part of the driver

4

HELLO VULKAN

4/4/2016

Simple HelloVulkan is ~750 lines of code

So much to do, hard to start

Easy to make errors

Even harder to find those errors

Is there a way to simplify Vulkan usage?

Two projects started

VKCPP (low level C++ API)

NVK* (high level C++ API)

5

VKCPPhttp://github.com/nvpro-pipeline/vkcpp

VKCPP is a port of the Vulkan API for C++11

Simplifies logic where possible without changing concepts/behaviour

Generated from the official Vulkan spec file, vk.xml

Header only with inline functions to minimize additional cost

Open source project contains generated header and generator

4/4/2016

6

VKCPP GOALSReduce code size & risk of errors

Initialization ‚vertical‘

-> reduced clearness due to #lines on screen

Potential issues

Struct/sType enums mismatch

No type safety for enums and flags

Risk of uninitialized fields

VkApplicationInfo appInfo = {};

appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;

appInfo.pNext = NULL;

appInfo.pApplicationName = appName;

appInfo.applicationVersion = 1;

appInfo.pEngineName = engineName;

appInfo.engineVersion = 1;

appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 5);

VkInstanceCreateInfo instInfo = {};

instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;

instInfo.pNext = NULL;

instInfo.flags = 0;

instInfo.pApplicationInfo = &appInfo;

instInfo.enabledLayerCount = layerNames.size();

instInfo.ppEnabledLayerNames = layerNames.data()

instInfo.enabledExtensionCount = extensionNames.size();

instInfo.ppEnabledExtensionNames = extensionNames.data();

VkResult res = vkCreateInstance(&instInfo, NULL, &info.inst);

assert(res == VK_SUCCESS);

7

VULKAN NAMESPACE

// Strip Vk prefix of all functions and structs

VkResult vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo,

const VkAllocationCallbacks* pAllocator,

VkInstance* pInstance);

// Introduce new vk namespace for all vkcpp symbols

namespace vk {

Result createInstance(const InstanceCreateInfo* pCreateInfo,

const AllocationCallbacks* pAllocator,

Instance* pInstance);

};

avoid symbol collisions

8

TYPE SAFETYEnums

namespace vk

{

// Use scoped enums for type safety

enum class ImageType

{

e1D = VK_IMAGE_TYPE_1D,

e2D = VK_IMAGE_TYPE_2D,

e3D = VK_IMAGE_TYPE_3D

};

}

Strip VK_ prefix + enum name

Use upper camel case of enum type as name

‘e‘ + name

prefix is required only for numbers, used everywhere for consistency reasons

9

TYPE SAFETYFlags

4/4/2016

// Introduce class for typesafe flags

template <typename BitType, typename MaskType = VkFlags>

class Flags {

...

};

// BitType is scoped enum

enum class QueueFlagBits {

eGraphics = VK_QUEUE_GRAPHICS_BIT,

eCompute = VK_QUEUE_COMPUTE_BIT,

eTransfer = VK_QUEUE_TRANSFER_BIT,

eSparseBinding = VK_QUEUE_SPARSE_BINDING_BIT

};

// Define typesafe flags

typedef Flags<QueueFlagBits, VkQueueFlags> QueueFlags;

10

TYPE SAFETYFlags

template <typename BitType, typename MaskType = VkFlags>

class Flags {

public:

Flags(); // No flags set. Use QueueFlags() or {} as value

Flags(BitType bit); // QueueFlags qf(QueueFlagBits::eGraphics)

Flags<BitType> & operator|=(Flags<BitType> const& rhs); // qf |= QueueFlagBits::eCompute;

Flags<BitType> & operator&=(Flags<BitType> const& rhs); // qf &= QueueFlagBits::eGraphics;

Flags<BitType> & operator^=(Flags<BitType> const& rhs); // qf ^= QueueFlagBits::eGraphics;

Flags<BitType> operator|(Flags<BitType> const& rhs) const; // qf = QueueFlagBits::eCompute | QueueFlagBits::eGraphics

Flags<BitType> operator&(Flags<BitType> const& rhs) const; // qf = qf & QueueFlagBits::eGraphics

Flags<BitType> operator^(Flags<BitType> const& rhs) const; // qf = qf ^ QueueFlagBits::eGraphics

explicit operator bool() const; // if (qf) Is any bit set?

bool operator!() const; // if (!qf) Is no bit set?

bool operator==(Flags<BitType> const& rhs) const; // if (qt == QueueFlagBits::eCompute)

bool operator!=(Flags<BitType> const& rhs) const; // if (qt != QueueFlagBits::eCompute)

explicit operator MaskType() const; // VkFlags flags = static_cast<VkFlags>(qf);

};

11

INITIALIZATIONCreateInfos and Structs

4/4/2016

class EventCreateInfo

{

public:

// All constructors initialize sType/pNext

EventCreateInfo(); // Initialize all fields with default values (0 currently)

EventCreateInfo(EventCreateFlags flags); // Create with all parameters specified

EventCreateInfo(VkEventCreateInfo const & rhs); // Construct from native Vulkan type

// get parameter object

const EventCreateFlags& flags() const;

EventCreateFlags& flags(); // get parameter from non-cost object

// set parameter

EventCreateInfo& flags(EventCreateFlags flags);

...

operator const VkEventCreateInfo&() const; // cast operator to native vulkan type

};

12

RESULTSCODE SIZE & TYPE SAFETY

vk::ApplicationInfo appInfo(appName, 1,

engineName, 1,

VK_MAKE_VERSION(1,0,5));

vk::InstanceCreateInfo i({}, &appInfo,

layerNames.size(), layerNames.data(),

extNames.size(), extNames.data());

vk::Instance instance;

vk::Result res = vk::createInstance(&i, nullptr, &instance);

assert(res == vk::Result::eSuccess);

VkApplicationInfo appInfo = {};

appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;

appInfo.pNext = NULL;

appInfo.pApplicationName = appName;

appInfo.applicationVersion = 1;

appInfo.pEngineName = engineName;

appInfo.engineVersion = 1;

appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 5);

VkInstanceCreateInfo i = {};

i.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;

i.pNext = NULL;

i.flags = 0;

i.pApplicationInfo = &appInfo;

i.enabledLayerCount = layerNames.size();

i.ppEnabledLayerNames = layerNames.data()

i.enabledExtensionCount = extNames.size();

i.ppEnabledExtensionNames = extNames.data();

VkInstance instance;

VkResult res = vkCreateInstance(&i, NULL, &instance);

assert(res == VK_SUCCESS);

13

RESULTSCODE SIZE & TYPE SAFETY

4/4/2016

vk::ApplicationInfo appInfo(appName, 1,

engineName, 1,

VK_MAKE_VERSION(1,0,5));

vk::InstanceCreateInfo i({}, &appInfo,

layerNames.size(), layerNames.data(),

extNames.size(), extNames.data());

vk::Instance instance;

vk::Result res = vk::createInstance(&i, nullptr, &instance);

assert(res == vk::Result::eSuccess);

std::vector<std::string>?

Bad idea

vk::InstanceCreateInfo i({}, &appInfo,

{“layer_xyz”},

extNames.size(), extNames.data());

Lifetime of struct != Lifetime of temporaryTemporary array will be destroyed after

constructor

CreateInfos would have to copy data

14

DESIGNATED INITIALIZER LIST

4/4/2016

VkApplicationInfo appInfo =

{

.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,

.pNext = NULL,

.pApplicationName = appName,

.applicationVersion = 1,

.pEngineName = engineName,

.engineVersion = 1,

.apiVersion = VK_MAKE_VERSION(1,0,5)

};

vk::ApplicationInfo appInfo = vk::ApplicationInfo()

.pApplicationName(appName)

.applicationVersion(1)

.pEngineName(engineName)

.engineVersion(1)

.apiVersion(VK_MAKE_VERSION(1,0,5));

Designated initializer list not part of C++11

Names explicit, but no guarantee to set all fields

15

C++ CODING STYLEHANDLES

4/4/2016

VkCommandBuffer cmd = ...

VkRect2D scissor;

scissor.offset.x = 0;

scissor.offset.y = 0;

scissor.extent.width = width;

scissor.extent.height = height;

vkCmdSetScissor(cmd, 0, 1, &scissor);

Convert C-Style OOto

C++ Style OO

class CommandBuffer{

// conversion from/to native handleCommandBuffer(VkCommandBuffer commandBuffer);CommandBuffer& operator=(VkCommandBuffer

commandBuffer);operator VkCommandBuffer() const;

// boolean tests if handle is validexplicit operator bool() const;bool operator!() const;

// functionsvoid setScissor(uint32_t firstScissor,

uint32_t scissorCount,const Rect2D* pScissors) const;

};vk::CommandBuffer cmd;

cmd.setScissor(0, 1, &scissor);

16

C++ CODING STYLETEMPORARY STRUCTS AND EXCEPTIONS

4/4/2016

class Device {

Result createFence(const FenceCreateInfo * createInfo, AllocationCallbacks const * allocator, Fence * fence) const;

};

Change from pointer to referenceallows passing temporaries

class Device {

Fence createFence(const FenceCreateInfo & createInfo, Optional<AllocationCallbacks const> const & allocator) const;

};

17

C++ CODING STYLETEMPORARY STRUCTS AND EXCEPTIONS

4/4/2016

class Device {

Result createFence(const FenceCreateInfo * createInfo, AllocationCallbacks const * allocator, Fence * fence) const;

};

AllocationCallbacks are optional and might be nullOptional<Allocationcallbacks> accepts nullptr as input

class Device {

Fence createFence(const FenceCreateInfo & createInfo, Optional<AllocationCallbacks const> const & allocator) const;

};

18

C++ CODING STYLEOptional References and Return Values

4/4/2016

class Device {

Result createFence(const FenceCreateInfo * createInfo, const AllocationCallbacks * allocator, Fence * fence) const;

};

Fence is now a return valueThe C++ version of the function throws a std::system-error if result is not a success code

class Device {

Fence createFence(const FenceCreateInfo & createInfo, Optional<AllocationCallbacks const> const & allocator) const;

};

19

C++ CODING STYLERESULTS

4/4/2016

vk::Fence fence;

vk::FenceCreateInfo ci;

vk::Result result = device.createFence(&ci, nullptr, &fence);

assert(result == vk::Result::eSuccess);

try {

vk::Fence fence = device.createFence({}, nullptr);

} catch (std::system_error e) {...}

20

C++ CODING STYLERESULTS

4/4/2016

vk::Fence fence;

vk::FenceCreateInfo ci;

vk::Result result = device.createFence(&ci, nullptr, &fence);

assert(result == vk::Result::eSuccess);

vk::Fence fence = device.createFence({}, nullptr);

21

C++ CODING STYLEARRAYS

4/4/2016

VkCommandBuffer cmd = ...

VkRect2D scissor;

scissor.offset.x = 0;

scissor.offset.y = 0;

scissor.extent.width = width;

scissor.extent.height = height;

cmd.setScissor(0, 1, &scissor);

class CommandBuffer{

// additional functions for arrays

void setScissor(uint32_t firstScissor,

std::vector<Rect2D> const & scissors)

const;

};

VkCommandBuffer cmd = ...

cmd.setScissor(0, { { {0u,0u}, {width, height} } });

Offset2D Extent2D

Rect2D

std::vector<Rect2D>

Count and real data size always correctLifetime of temporary is function call

22

C++ CODING STYLEDynamically sized queries

vk::PhysicalDevice pd = ...;

std::vector<vk::QueueFamilyProperties> properties;

uint32_t queueCount;

pd.getQueueFamilyProperties(&queueCount, nullptr);

properties.resize(queueCount);

pd.getQueueFamilyProperties(&queueCount, properties.data());

Define variables

Query size

Resize std::vector

Query data

vk::PhysicalDevice pd = ...;

std::vector<vk::QueueFamilyProperties> properties = pd.getQueueFamilyProperties();

23

C++ CODING STYLEDynamically sized queries

std::vector<vk::ExtensionProperties> properties = enumerateInstanceExtensionProperties(layer);

vk::Result res;

std::vector<vk::ExtensionProperties> properties;

uint32_t count;

char *layer = ...;

do {

res = vk::enumerateInstanceExtensionProperties(layer, &count, nullptr);

if (res) throw error;

properties.resize(count);

res = vk::enumerateInstanceExtensionProperties(layer, &count, properties.data());

} while (res == vk::Result::eIncomplete);

24

C++ CODING STYLEupdateBuffer

class CommandBuffer {

void updateBuffer(Buffer dstBuffer, DeviceSize dstOffset,

DeviceSize dataSize, const uint32_t* pData) const;

};

cmd.updateBuffer(buffer, 0,

static_cast<DeviceSize>(data.size() * sizeof(*data.data())),

reinterpret_cast<const uint32_t*>(data.data()));

Might work or might not work if sizeof(*data.data()) is not a multiple of 4

Call is not that beautiful

dataSize in bytes data type is uint32_t -> dataSize must be multiple of 4

25

C++ CODING STYLEupdateBuffer

4/4/2016

class CommandBuffer {

template <typename T>

void updateBuffer(Buffer dstBuffer, DeviceSize dstOffset,

std::vector<T> const & data) const;

};

cmd.updateBuffer(buffer, 0, data); static_assert to ensure sizeof(T) is a multiple of 4-> compile time failure instead of runtime failure

26

UTILITY FUNCTIONSto_string

For debugging purposes it‘s useful to convert enums or flags to strings

std::string to_string(FooEnum value) for enums

to_string(vk::Result::eSuccess)-> “Success”

std::string to_string(BarFlags value) for flags

to_string(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute)-> “Graphics | Compute”

27

VKCPP

VKCPP is ‘raw vulkan‘ using C++11 features

Increases type safety and construction safety at compile time

Also provides syntactic sugar

Classes for handles

Tempoaries for parameters

std::vector for arrays

Utility functions (to_string)

Good for experts, doesn‘t simplify Vulkan workflow

Conclusion

28

NVKMotivation

Vulkan starters just want to render a triangle

NVK will provide utility functions for routine work

Resource Tracking CPU and GPU, lifetime can be hard to handle

Memory suballocation, everyone will have to do it

Utility functions, i.e. setup framebuffer

Project at early stages, Hello Vulkan already ported ;-)

4/4/2016

29

NVKHierarchy

4/4/2016

Instance

DeviceMemory … Image

PhysicalDevice

Device

std::shared_ptrto parent

Queue

Resources

Destroy DeviceDestroy Image

vkDestroyImage(m_device, …);

30

NVKHierarchy

4/4/2016

Instance

DeviceMemory … Image

PhysicalDevice

Device

std::weak_ptr

std::shared_ptr

Queue

Resources

PhysicalDevice and Queue exist

No need for destruction

Reference to parent required

Weak_ptr allows identity check

31

NVKHierarchy

Implemented RAII layer with shared_ptrs

Lifetime tracking

Exception safety

CreateInfos replaced by create function with parameters

std::shared_ptr<nvk::Instance> instance = nvk::Instance::create(“nvk", 0, ...);

4/4/2016

32

NVKMemory Suballocation

4/4/2016

It‘s strongly recommended to do suballocations in Vulkan

Make nvk::DeviceMemory suballocation aware

class DeviceMemory {vk::DeviceMemory deviceMemory;vk::DeviceSize offset;...

};

Made API using DeviceMemory suballocation aware

nvk::bindImageMemory(deviceMemory, image, offset) gets

vk::bindImageMemory(deviceMemory, image, offset + deviceMemory.offset)

33

NVKMemory Suballocation

4/4/2016

Introduce Heap interface

class Heap {

virtual std::shared_ptr<nvk::DeviceMemory>

allocate(vk::MemoryRequirements const & requirements) = 0;

};

Interface allows multiple strategies for small/large or fixed size allocations

34

NVKMemory Suballocation

There‘s no free in the interface

class Heap {

virtual std::shared_ptr<nvk::DeviceMemory>

allocate(vk::MemoryRequirements const & requirements) = 0;

};

The Heap implementation will return a class derived from nvk::DeviceMemory

Derived class keeps reference to Heap

Destructor knows how to free the memory

4/4/2016

35

NVKResource Tracking GPU

4/4/2016

Bind

Buffer

Bind

BufferDraw

Bind

Desc…

CommandBuffer Queue

Submit

GPU

Process

Set of resources

keep alive as long as it is use by CmdBuffer or GPU

36

NVKResource Tracking GPU

4/4/2016

class ResourceTracker {

// track resources, increase refcount

void track(std::shared_ptr<vk::Buffer> const & buffer);

// Give Fence to tracker which can be checked if resources are still being used

void addFence(std::shared_ptr<vk::Fence> const & fence);

// check if any tracked resource used on GPU,

bool isUsed() const;

// decrease refcount of resources if not used anymore

void releaseUnused();

};

Introduce ResourceTracker Interface

37

NVKResource Tracking GPU

Might be costly to track all used resources

Put each resource once in list results in small list, but slow insertion

Put each resource in list on each usage results large list, but fast insertion

Use ResourceTracker as template interface for CommandBuffer

Build up list once and keep it over multiple frames

In debug mode check if all resources are tracked

Should be nearly free in this mode4/4/2016

38

NVK + VKCPPResults

4/4/2016

VKCPP for the ninja provides modern C+11 API for

Reduced code complexity

Improved compile time error checking

NVK provides

RAII styles gives exception safety

Refcounting provides resource tracking on CPU

Resource tracker provides resource tracking on GPU

Hello Vulkan in NVK needs only about 200 lines of code

April 4-7, 2016 | Silicon Valley

THANK YOU

Get it now: https://github.com/nvpro-pipeline/vkcpp

top related