glnext

pip install glnext

Objects

class Instance
Represents a single Vulkan instance.
class Surface
Represents a Vulkan compatible window surface and an image source.
The sole purpose of this object is to allow swapping the image source with Surface.image.
Presentation won’t take place until Instance.present() is called.
class Task
Represents a collection of Vulkan objects and operations.
class Framebuffer
Represents an entire Render Pass and Framebuffer.
Graphics Piplines are created from this object.
Compute Pipelines created from this object can be used to post process the framebuffer output.
They run before the final layout transition takes place.
class RenderPipeline
Represents a single Graphics Pipeline and a single draw call per Framebuffer layer.
RenderPipelines are automatically executed in their order of creation when Task.run() is called.
class ComputePipeline
Represents a single Compute Pipeline and a single dispatch call.
ComputePipeline are automatically executed in their order of creation when Task.run() is called.
class Group
Represents a context helper for grouping read and write operations and task runs.
The command buffer is in recording state during the group is active.
On __exit__ the command buffer is executed and the output memory views are filled with data.
class Buffer
Represents a device local buffer objects.
class Image
Represents a device local image objects.
class Memory
Represents a Vulkan memory allocation.
This class is for advanced use only.

Documentation

Instance objects

glnext.instance(physical_device: int = 0, application_name: str = None, application_version: int = 0, engine_name: str = None, engine_version: int = 0, backend: str = None, surface: bool = False, layers: list = None, cache: bytes = None, debug: bool = False)Instance
Instance.surface(window: tuple, image: Image)Surface
Parameters
  • window (tuple) – The window handle in the (hinstance, hwnd) format for windows and (display, window) format for linux.

  • image (Image) – The source image.

Instance.buffer(type: str, size: int, readable: bool = False, writable: bool = True, memory: Memory = None)Buffer
Instance.image(size: tuple, format: str = '4p', levels: int = 1, layers: int = 1, mode: str = 'output', memory: Memory = None)Image
Parameters

format (str) – formats

Instance.task()Task
Instance.group(buffer: int)Group
Parameters

buffer (int) – The staging buffer size. The staging buffer must large enough to support all the reads and writes within the buffer. The size of the staging buffer is usually the expected maximum number of transfered bytes within the group. The staging buffer consumes the host memory.

Prevent submitting small command buffers for each read and write call.
Wrap multiple operations with this context helper.
The write operations copy immediately into the staging buffer.
The read and write operations complete only on __exit__.
All the operations within a group respect ordering.

More on this at How glnext groups work?

Instance.present()
For every surface the source image content is blitted to the acquired swapchain image.
The invalid swapchains silently destroy for the closed windows.
The user is not responsible for cleaning up after window recreation, the resources are automatically freed.
This call may be blocking until vsync.

More on this at How glnext present works?

Instance.cache()bytes
Returns the pipeline cache content.
The pipeline cache is always empty if the instance was created with cache=None.
Initialize the Instance with cache=data to enable the pipeline cache.

Reasoning behind the cache parameter and the cache function:

There are two best practices to follow:

  1. Have a single pipeline cache.

    When having a pipeline cache the user is expected to provide its content when creating the instance.
    This can be done with the following code:
    my_pipeline_cache_data = b''
    if os.path.exists('my_pipeline_cache.cache'):
        my_pipeline_cache_data = open('my_pipeline_cache.cache', 'rb').read()
    
    instance = glnext.instance(cache=my_pipeline_cache_data)
    
    # ... the program goes here
    
    open('my_pipeline_cache.cache', 'wb').write(instance.cache())
    
    In this example the cache parameter is always bytes even on the first run.
    There are better ways to load and store binary data. This example is just a short working example.
  2. Have no caching at all.

    To disable the pipeline cache entirely set the cache to None.
    An Instance created with cache=None will not generate cache on pipeline creation and the Instance.cache() will fail with an error.

Surface objects

Surface.image: Image
A surface source image can be replaced by setting the Surface.image attribute to a different image.
The new image must have the same size and format as the original image.
Setting the image is a cheap operation.

Task objects

Task.framebuffer(size: tuple, format: str = '4p', samples: int = 4, levels: int = 1, layers: int = 1, depth: bool = True, compute: bool = False, mode: str = 'output', memory: Memory = None)Framebuffer
Task.compute(compute_shader: bytes, compute_count: tuple, bindings: list, memory: Memory = None)ComputePipeline
Task.run()
Executes all RenderPipeline and ComputePipeline objects derived from this objects.
This call may be blocking until all the operations finish.

Framebuffer objects

Framebuffer.render(vertex_shader, fragment_shader, task_shader, mesh_shader, vertex_format, instance_format, vertex_count, instance_count, index_count, indirect_count, max_draw_count, vertex_buffer, instance_buffer, index_buffer, indirect_buffer, count_buffer, vertex_buffer_offset, instance_buffer_offset, index_buffer_offset, indirect_buffer_offset, count_buffer_offset, topology, restart_index, short_index, depth_test, depth_write, bindings, memory)RenderPipeline
Framebuffer.compute(compute_shader: bytes, compute_count: tuple, bindings: list, memory: Memory = None)ComputePipeline
Framebuffer.update(clear_values: bytes, clear_depth: float, **kwargs)

RenderPipeline objects

RenderPipeline.update(vertex_count: int, instance_count: int, index_count: int, indirect_count: int, **kwargs)

ComputePipeline objects

ComputePipeline.update(compute_count: tuple, **kwargs)

Group objects

group = instance.group(buffer=1024)    # Create a group with a staging buffer of 1024 bytes
                                       #
with group:                            # Begin Command Buffer
    buffer1.write(data1)               # Copy data 1 into the staging buffer
    task1.run()                        # Copy task 1 into the command buffer
    task2.run()                        # Copy task 2 into the command buffer
    buffer2.read()                     # Copy buffer 2 into the staging buffer
                                       # End Command Buffer
data2 = bytes(group.output[0])         # Copy from the staging buffer to a Python variable
Group.__enter__()
Start recording into the main command buffer.
The following methods will have no immediate effect and will be postponed until __exit__:
Group.__exit__()
Submit the recorded commands.

Buffer objects

Buffer.read()bytes
Buffer.write(data: bytes)

Image objects

Image.read()bytes
Image.write(data: bytes)

Memory objects

Diagrams

Object Hierarchy

On the diagram the [A] -> [B] represents “an instance of B can be created from an instance of A”.

_images/diagram_5.png _images/diagram_4.png

Task Execution

_images/diagram_6.png
Task execution respects the creation order of the Frambuffers and the Compute Pipelines.
On the diagram above the yellow ComputePipeline is created from the task.
The yellow Compute Pipeline executes after the first Framebuffer finishes.
The green Compute Pipeline and the Render Pipelines are part of the Framebuffers.
They respect their creation order within the Framebuffer.
On the task level they respect the Framebuffer creation order.

Framebuffer execution

_images/diagram_1.png
The Render Pipelines of the Framebuffer execute within the render pass.
The render pass renders directly into the output image if samples = 1.
For multisampling the resolve step is responsible for the output image content.
A Framebuffer created with compute = True can have Compute Pipelines attached to it. The Compute Pipelines of the Framebuffer are considered to be the postprocessing step.
The Compute Pipelines of the Framebuffer execute after the render pass and resolve step but before the final layout transition.
If the Framebuffer was created with compute = True the output image will transition into shader storage optimal first. After the Compute Pipelines execute the image is transitioned to respect the Framebuffer mode.

Framebuffer Memory

_images/diagram_2.png _images/diagram_3.png
Multisampeld Framebuffers have additional images as the render target.
This library uses VK_KHR_dedicated_allocation for the multisampled images of the Framebuffer.
There is not dedicated allocation at the moment for any other part of the code.

Formats

Format

Vulkan Format

1f

VK_FORMAT_R32_SFLOAT

2f

VK_FORMAT_R32G32_SFLOAT

3f

VK_FORMAT_R32G32B32_SFLOAT

4f

VK_FORMAT_R32G32B32A32_SFLOAT

1h

VK_FORMAT_R16_SFLOAT

2h

VK_FORMAT_R16G16_SFLOAT

3h

VK_FORMAT_R16G16B16_SFLOAT

4h

VK_FORMAT_R16G16B16A16_SFLOAT

1i

VK_FORMAT_R32_SINT

2i

VK_FORMAT_R32G32_SINT

3i

VK_FORMAT_R32G32B32_SINT

4i

VK_FORMAT_R32G32B32A32_SINT

1u

VK_FORMAT_R32_UINT

2u

VK_FORMAT_R32G32_UINT

3u

VK_FORMAT_R32G32B32_UINT

4u

VK_FORMAT_R32G32B32A32_UINT

1b

VK_FORMAT_R8_UINT

2b

VK_FORMAT_R8G8_UINT

3b

VK_FORMAT_R8G8B8_UINT

4b

VK_FORMAT_R8G8B8A8_UINT

1p

VK_FORMAT_R8_UNORM

2p

VK_FORMAT_R8G8_UNORM

3p

VK_FORMAT_R8G8B8_UNORM

4p

VK_FORMAT_R8G8B8A8_UNORM

1s

VK_FORMAT_R8_SRGB

2s

VK_FORMAT_R8G8_SRGB

3s

VK_FORMAT_R8G8B8_SRGB

4s

VK_FORMAT_R8G8B8A8_SRGB

1x

VK_FORMAT_UNDEFINED

2x

VK_FORMAT_UNDEFINED

3x

VK_FORMAT_UNDEFINED

4x

VK_FORMAT_UNDEFINED

8x

VK_FORMAT_UNDEFINED

12x

VK_FORMAT_UNDEFINED

16x

VK_FORMAT_UNDEFINED

Image Formats

'4p'     # 4 pixels
'4b'     # 4 byes
'3f'     # 3 floats
'4h'     # 4 half floats
'3h'     # 3 half floats, but it is unlikely to be supported (size = 6)
'3p'     # 3 pixels, but it is unlikely to be supported (size = 3)
'2f 2f'  # multiple attachments are not supported for images
'4x'     # padding is not supported for images

Warning

Do not use Image formats with size not divisible by four. Those may be supported on you platform, but not on others.

Framebuffer Formats

'4p'        # 4 pixels
'3f'        # 3 floats
'4h 1f'     # 4 half floats and one float
'3h 2b'     # 3 half floats and 2 bytes, but it is unlikely to be supported (size = 6 + 2)
'2f 2f'     # 2 floats and 2 floats
'2f 4x 2f'  # padding is not supported for framebuffers

Note

Formats are separated with a single space.
For each format an Image is created.
All the rules that apply on the Image formats apply per Image in the Framebuffer.

Vertex Formats

'4b'        # 4 bytes
'3f'        # 3 floats
'3h 2b'     # 3 half floats and 2 bytes
'2f 2f'     # 2 floats and 2 floats
'2f 4x 2f'  # 2 floats, 4 bytes padding and 2 floats
'2f 1b'     # 2 floats and 1 byte

Matrix Vertex Input

To bind matrix vertex attributes use multiple vertex formats as they were separate attributes.
#version 450

in layout (location = 0) in vec3 position;
in layout (location = 1) in mat3 rotation;
in layout (location = 4) in vec3 scale;
In the example above mat3 rotation consumes 3 attribute locations.
Its vertex format can be defined as 3f 3f 3f.

Examples and Patterns

Indices and tables