core

package module
v2.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2023 License: MIT Imports: 6 Imported by: 46

README

vkngwrapper/core/v2

Go Reference

go get github.com/vkngwrapper/core/v2

Vkngwrapper (proununced "Viking Wrapper") is a handwritten cgo wrapper for the Vulkan graphics and compute API. The goal is to produce fast, easy-to-use, low-go-allocation, and idiomatic Go code to communicate with your graphics card and enable games and other graphical applications. Vkngwrapper currently supports core versions 1.0-1.2, as well as many extensions via the https://github.com/vkngwrapper/extensions repository.

Under the hood, Vkngwrapper uses https://github.com/cannibalvox/cgoparam to avoid calling C.Malloc and C.Free while still avoiding the cost of a deep cgocheck on Go memory. This allows you to save precious nanoseconds (or sometimes microseconds!) on your cgo overhead.

Vkngwrapper is also heavily-tested. The marshalling and unmarshalling layer has high test coverage, giving the core library 84.5% test coverage and the extensions library 87.9% test coverage. While this coverage is not perfect, Vulkan has an extremely large API surface, and these tests ensure that there is no obviously-busted functionality. Additionally, the entire API is mockable (and pre-generated gomocks are provided), allowing you to test your own code with ease.

Lastly, vkngwrapper has a solid and still-growing base of examples, built from Go ports of existing Vulkan examples. Several key samples from https://github.com/LunarG/VulkanSamples have are included in our example repository, as well as a full port of the Vulkan tutorial, which can be followed step by step at https://github.com/vkngwrapper/vulkan-tutorial

For more information about our future roadmap, see the org page.

Getting Started

Before building any Vulkan application, you will need to install the Vulkan SDK for your operating system. Additionally, if you intend to use SDL2 to create windows, as in vkngwrapper's examples, it may be necessary to download SDL2 using your local package manager. For more information, see go-sdl2 requirements.

The first step to using vkngwrapper is to create a Loader. While we offer the option to create a Loader from a ProcAddr provided by a windowing system (such as SDL2), the easiest way is to build a loader from the system's local Vulkan library:

loader, err := core.CreateSystemLoader()
if err != nil {
 return err 
}

Once you have a Loader, you can use that Loader to create an Instance, the Instance to create a PhysicalDevice, and the PhysicalDevice to create a Device.

instanceOptions := core1_0.InstanceCreateInfo{
    ApplicationName:    "My Vulkan App",
    ApplicationVersion: common.CreateVersion(1, 0, 0),
    EngineName:         "No Engine",
    EngineVersion:      common.CreateVersion(1, 0, 0),
    APIVersion:         common.Vulkan1_0,
}

instance, _, err := loader.CreateInstance(nil, instanceOptions)
if err != nil {
	return err 
}

physicalDevices, _, err := instance.EnumeratePhysicalDevices()
if err != nil {
    return err
}

// The real logic is more complicated than this
queueFamilies := physicalDevices[0].QueueFamilyProperties()
queueIndex := -1

for index, queueFamily := range queueFamilies {
	if (queueFamily.QueueFlags & core1_0.QueueGraphics) != 0 {
        graphicsIndex = index 		
    }
}

deviceOptions := core1_0.DeviceCreateInfo{
	QueueCreateInfos: []core1_0.DeviceQueueCreateInfo{
	    {
		    QueueFamilyIndex: 	graphicsIndex,
			QueuePriorities: []float32{1.0},
        },
    },
}

device, _, err := physicalDevices[0].CreateDevice(nil, deviceOptions)
if err != nil {
	return err 
}

Then, the world is your oyster! Be sure to destory these (and all other) Vulkan objects when you are finished with them. To learn more about how to use vkngwrapper effectively, check out the examples repository and to learn more about how to use Vulkan effectively, check out the Vulkan tutorial and the excellent Vulkan Discord!

Principals of vkngwrapper

While vkngwrapper labors to follow the Vulkan specification fairly closely, there are some unusual qualities that one should be aware of when working with the library.

Objects, Not Handles

Vulkan represents all persistent structures using object handles, opaque pointers that are passed to and from Vulkan to indicate a particular Vulkan object. vkngwrapper wraps these handles with a Go object, and exposed Vulkan commands in an object-oriented fashion. For instance, the Vulkan command vkCreateBuffer accepts a Device handle (VkDevice), and returns a Buffer handle (VkBuffer). By contrast, Device.CreateBuffer is located on a Device object and returns a Buffer object.

One of the principals of vkngwrapper is that two Vulkan objects of the same type with the same handle should compare as true. As a result, vkngwrapper utilizes an internal cache of Vulkan objects to ensure that the same object is returned if it is retrieved multiple times.

physicalDevices1, _, err := instance.EnumeratePhysicalDevices()
if err != nil {
    return err
}

physicalDevices2, _, err := instance.EnumeratePhysicalDevices()
if err != nil {
return err
}

// this returns true (provided EnumeratePhysicalDevices returns devices in the same order... which isn't actually
// guaranteed, but still)
return physicalDevices1[0] == physicalDevices2[0] 
Use Idiomatic Types

When representing integer numbers, most types in vkngwrapper are simply int, while the underlying Vulkan type may be uint64, int32, etc. The only exception is when a type represents a bitmask. Likewise, while a duration in Vulkan might be represented by an integer counting nanoseconds, vkngwrapper tends to use time.Duration. This library endeavors to use go-friendly types unless doing so would result in a degradation of quality or performance for a substantial number of users.

Namespace By Availability

All types, methods, and constants in vkngwrapper (both here in the core library, as well as the extensions library) are packaged under the Vulkan version or extension that makes them available for use. For instance, SamplerYcbcrConversion objects were introduced in the VK_KHR_sampler_ycbcr_conversion extension, and then later promoted to core 1.1. As a result, the SamplerYcbcrConversion interface is available via khr_sampler_ycbcr_conversion.SamplerYcbcrConversion and core1_1.SamplerYcbcrConversion.

All symbols that are available in the C Vulkan headers are namespaced in this manner, with the exception of driver.AllocationCallbacks which is special for silly package interdependency and cgo reasons. Arguments that accept *driver.AllocationCallbacks can usually be left nil, but if you would like to receive callbacks when Vulkan makes internal allocations and deallocations, do the following:

  1. Create a common.AllocationCallbackOptions object with the callback methods you would like to be executed, and optionally, a UserData object to be passed to all callbacks.
  2. Use driver.CreateAllocationCallbacks to create a driver.AllocationCallbacks object, which can be passed to Create, Destroy, and Free methods.

While driver.AllocationCallbacks objects are immutable, common.AllocationCallbackOptions structures are not. They can be modified and then used to create another driver.AllocationCallbacks object with different behaviors. driver.AllocationCallbacks objects need to be destroyed like any other Vulkan object when you are done with them.

Advertise Version Support

All Vulkan objects in vkngwrapper have an APIVersion method which returns the highest Vulkan core version the object supports. Generally speaking, the Loader will support whatever version is available via the .dll/.so/etc. the Loader was created from, the Instance will support whatever version you requested when creating it, if lower than the Loader version, the PhysicalDevice will support whatever version your hardware supports, if lower than the Instance version, and all other objects will inherit their version from the PhysicalDevice they exist on.

It is helpful to be able to request information about Vulkan support from any Vulkan object, but the easiest way to check for core version support is with promotion.

Promote to Add Functionality

All Vulkan versions from 1.1 upward provide promoted versions of Vulkan objects introduced in previous core versions. As an example, consider the CommandBuffer. core1_0.CommandBuffer introduces 58 Vulkan commands and has several utility methods. core1_1.CommandBuffer extends core1_0.CommandBuffer and adds 2 additional Vulkan commands introduced in core 1.1. core1_2.CommandBuffer extends core1_1.CommandBuffer and adds 5 more commands. In environments where you are making use of core 1.1 functionality, you may find it easier to work with core1_1.CommandBuffer.

You may use core1_1.PromoteCommandBuffer or core1_1.PromoteCommandBufferSlice to convert any CommandBuffer objects into a core1_1.CommandBuffer. If the CommandBuffer passed to a promote method does not support core 1.1, a promoted version will not be included in the results. core1_1.PromoteCommandBuffer will return nil, and core1_1.PromoteCommandBufferSlice will not include the underversioned CommandBuffer in the returned slice. The same methods exist in core1_2 which will return promoted core 1.2 CommandBuffer objects, and will exist in every version after that.

Recall in the Objects, Not Handles section that objects will only compare to true if they are the same type, even if they share the same handle. core1_1.PromoteCommandBuffer will always return an object of an underlying core 1.1 type, even if a CommandBuffer from a higher version was passed in. However, if you are uncertain which version an object is from and don't want to perform a version promotion (they aren't free!), you may prefer to compare the handles returned from CommandBuffer.Handle or other Handle methods on other objects.

Chain Options and OutData

Vulkan has the capability to allow existing structure and method behavior to be extended by chaining structures using a pNext field added to most Vulkan structures. This field is represented in vkngwrapper using the NextOptions and NextOutData embedded structures.

Take a look at this example:

_, err := device.BindBufferMemory2([]core1_1.BindBufferMemoryInfo{
    {
        Buffer:       buffer,
        Memory:       memory,
        MemoryOffset: 1,

        NextOptions: common.NextOptions{
            core1_1.BindBufferMemoryDeviceGroupInfo{
                DeviceIndices: []int{1, 2, 7},
            },
        },
    },
})

By chaining core1_1.BindBufferMemoryDeviceGroupInfo onto core1_1.BindBufferMemoryInfo, additional behavior related to Device groups can be applied to an existing method. BindBufferMemoryDeviceGroupInfo also has a NextOptions embedded struct, so further behavior can be chained to that structure as well.

Broadly speaking, any structure that passes data into a Vulkan command embeds NextOptions and is passed in by value. Any structure that retrieves data from a Vulkan command embeds NextOutData and is passed in as a pointer. Chaining Options allows you to pass in additional parameter data to a command and change the behavior of a command. Chaining OutData allows you to request additional data from a command, which will be populated into the chained OutData.

While Vulkan has specific Options types that are intended to go together (and more can be learned as you understand Vulkan more deeply), from a syntactical point of view, any structure with NextOptions can be chained onto any other structure with NextOptions. Likewise, any structure with NextOutData can be chained onto any other structure with NextOutData.

Some structures (mainly Features structures) have both NextOptions and NextOutData. When they are being used to pass data into Vulkan (such as in core1_0.PhysicalDevice.CreateDevice, when it is specifying which features to activate), you must use NextOptions to chain further structures. When they are being used to retrieve data from Vulkan (such as in core1_1.InstanceScopedPhysicalDevice.Features2, when it is retrieving feature support from the device), you must use NextOutData to chain further structures.

Chained structures in the wrong field will be ignored.

Separate PhysicalDevice Functionality Into Instance And Device Scope

All Vulkan extensions fall into one of two categories: instance extensions, and device extensions. When an extension is promoted to a core version, an unusual state can come about. In rare cases, a user's system may support a higher core version than specific devices on that system (for example, if a user has multiple devices). In this case, Instance objects on that system can support the higher functionality, but Device objects cannot.

For example, if a user has a physical device that supports core 1.2 and another that only supports core 1.1, when working with the core 1.1 device, core 1.2 functionality will still be available, but only for the functionality that was promoted from instance extensions, nto the functionality that was promoted from device extensions.

But what of PhysicalDevice objects? The PhysicalDevice is the only Vulkan object that may have its functionality expanded in both instance and device extensions. In this case, the higher-versioned instance extension functionality is available, and the higher-versioned device extension functionality is not.

As a result, beginning with core 1.1, PhysicalDevice objects are split into InstanceScopedPhysicalDevice, which contains promoted instance extension functionality, and PhysicalDevice, which contains promoted device extension functionality, and a method to return an InstanceScopedPhysicalDevice of the same version. core1_0.PhysicalObject objects can be promoted directly to InstanceScopedPhysicalDevice objects, as well.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Loader

type Loader interface {
	// Driver is the Vulkan wrapper driver used by this Loader
	Driver() driver.Driver
	// APIVersion is the maximum Vulkan API version supported by this Loader.
	APIVersion() common.APIVersion

	// AvailableExtensions returns all of the instance extensions available on this Loader,
	// in the form of a map of extension name to ExtensionProperties
	//
	// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkEnumerateInstanceExtensionProperties.html
	AvailableExtensions() (map[string]*core1_0.ExtensionProperties, common.VkResult, error)
	// AvailableExtensionsForLayer returns all of the layer extensions available on this Loader
	// for the requested layer, in the form of a map of extension name to ExtensionProperties
	//
	// layerName - a string naming the layer to retrieve extensions from
	//
	// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkEnumerateInstanceExtensionProperties.html
	AvailableExtensionsForLayer(layerName string) (map[string]*core1_0.ExtensionProperties, common.VkResult, error)
	// AvailableLayers returns all of the layers available on this Loader, in the form of a
	// map of layer name to LayerProperties
	//
	// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkEnumerateInstanceLayerProperties.html
	AvailableLayers() (map[string]*core1_0.LayerProperties, common.VkResult, error)

	// CreateInstance creates a new Vulkan Instance
	//
	// allocationCallbacks - controls host memory allocation
	//
	// options - Controls creation of the Instance
	//
	// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCreateInstance.html
	CreateInstance(allocationCallbacks *driver.AllocationCallbacks, options core1_0.InstanceCreateInfo) (core1_0.Instance, common.VkResult, error)
}

Loader is the root object of vkng - all usage begins by creating a Loader and then using the Loader to create a Vulkan instance with Loader.CreateInstance

type VulkanLoader

type VulkanLoader struct {
	// contains filtered or unexported fields
}

func CreateLoaderFromDriver

func CreateLoaderFromDriver(driver driver.Driver) (*VulkanLoader, error)

CreateLoaderFromDriver generates a Loader from a driver.Driver object- this is usually used in tests to build a Loader from mock drivers

func CreateLoaderFromProcAddr

func CreateLoaderFromProcAddr(addr unsafe.Pointer) (*VulkanLoader, error)

CreateLoaderFromProcAddr generates a Loader from a ProcAddr provided by another library, such as the address provided by sdl2's SDL_Vulkan_GetVkInstanceProcAddr

func CreateSystemLoader

func CreateSystemLoader() (*VulkanLoader, error)

CreateSystemLoader generates a Loader from a vulkan-1.dll/so located on the local file system

func NewLoader

func NewLoader(
	driver driver.Driver,
) *VulkanLoader

func (*VulkanLoader) APIVersion

func (l *VulkanLoader) APIVersion() common.APIVersion

func (*VulkanLoader) AvailableExtensions

func (l *VulkanLoader) AvailableExtensions() (map[string]*core1_0.ExtensionProperties, common.VkResult, error)

func (*VulkanLoader) AvailableExtensionsForLayer

func (l *VulkanLoader) AvailableExtensionsForLayer(layerName string) (map[string]*core1_0.ExtensionProperties, common.VkResult, error)

func (*VulkanLoader) AvailableLayers

func (l *VulkanLoader) AvailableLayers() (map[string]*core1_0.LayerProperties, common.VkResult, error)

func (*VulkanLoader) CreateInstance

func (l *VulkanLoader) CreateInstance(allocationCallbacks *driver.AllocationCallbacks, options core1_0.InstanceCreateInfo) (core1_0.Instance, common.VkResult, error)

func (*VulkanLoader) Driver

func (l *VulkanLoader) Driver() driver.Driver

Directories

Path Synopsis
mocks
Package mock_driver is a generated GoMock package.
Package mock_driver is a generated GoMock package.
internal
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL