Skip to content

Instantly share code, notes, and snippets.

@omnp
Created August 3, 2023 19:04
Show Gist options
  • Save omnp/6ac3385e2b3f6cab987d84e6477e636a to your computer and use it in GitHub Desktop.
Save omnp/6ac3385e2b3f6cab987d84e6477e636a to your computer and use it in GitHub Desktop.
Example showing how to create a minimal GTK 4 app with a main window and on it a Wayland subsurface fed by Vulkan.
# Example showing how to create a minimal GTK 4 app with a main window and on it a Wayland subsurface fed by Vulkan.
# The FPS counter printed on the console should show about 60 fps on Gnome/Mutter.
# One may need to install vulkan (the python package) from source.
# Maybe same for pywayland. Mileage may vary.
# I happen to have two discrete GPUs installed in the test computer, one AMD, one NVIDIA, and Intel integrated to top.
# The Radeon is the main one running Gnome/Mutter (after some /usr/share/glvnd/egl_vendor.d/ symlinking happened, that is)
# "Works for me" on the Radeon, when started from Gnome Terminal as python example4.py
# Playing around with switcherooctl style env variables trying to get the Nvidia card to render vulkan while being composited to
# the main window does not work though.
# A similar error message can be found here:
# https://github.com/NVIDIA/egl-wayland/issues/41
# Don't know if it's related.
# There are some additional comments in the code below (from myself and the original comments by the author of the vulkan example).
# Get GTK 4
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
import ctypes
from cffi import FFI
ffi = FFI()
# These functions can't be accessed from the language bindings directly.
ffi.cdef("""
void * gdk_wayland_surface_get_wl_surface (void * surface);
void * gdk_wayland_display_get_wl_display (void * display);
void * gdk_wayland_display_get_wl_compositor (void * display);
""")
# GTK 4, again
gtk = ffi.dlopen('libgtk-4.so.1')
# https://github.com/realitix/vulkan
import vulkan
# https://github.com/flacjacket/pywayland
import pywayland.client
import pywayland.protocol.wayland
import os
import sys
import time
def on_activate(app):
X=100
Y=100
WIDTH = 400
HEIGHT = 400
win = Gtk.ApplicationWindow(application=app)
win.set_default_size(600, 600)
win.set_title("Example 4")
win.present()
win.set_size_request(600, 600)
win.realize()
native = win.get_native()
print(native)
display = native.get_display()
print(display)
print(display.get_name())
surface = native.get_surface()
print(surface)
# Some pointer magic. Accessing the internals.
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object,)
wl_surface = gtk.gdk_wayland_surface_get_wl_surface(ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(surface.__gpointer__, None)))
print(wl_surface)
wl_display = gtk.gdk_wayland_display_get_wl_display(ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(display.__gpointer__, None)))
print(wl_display)
wl_compositor = gtk.gdk_wayland_display_get_wl_compositor(ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(display.__gpointer__, None)))
print(wl_compositor)
# We have a display!
d = pywayland.client.Display()
d._ptr = wl_display
# No need to d.connect() here (one should not)
compositor = None
subcompositor = None
def handler(registry, id_, interface, version):
nonlocal compositor, subcompositor
if interface == 'wl_compositor':
compositor = registry.bind(id_, pywayland.protocol.wayland.WlCompositor, version)
elif interface == 'wl_subcompositor':
subcompositor = registry.bind(id_, pywayland.protocol.wayland.WlSubcompositor, version)
registry = d.get_registry()
registry.dispatcher['global'] = handler
d.dispatch()
d.roundtrip()
assert(compositor)
assert(subcompositor)
s = compositor.create_surface()
r = compositor.create_region()
r.add(0, 0, WIDTH, HEIGHT)
s.set_opaque_region(r)
t = pywayland.protocol.wayland.WlSurface()
t._ptr = wl_surface
# The order is surface, parent.
subsurface = subcompositor.get_subsurface(s, t)
subsurface.set_position(X, Y)
subsurface.set_sync()
# vulkan starts here
# The vulkan setup and rendering part is adapted from here:
# https://github.com/realitix/vulkan/blob/master/example/contribs/example_wayland.py
# I've changed some of the print calls to use .format() and such.
appinfo = vulkan.VkApplicationInfo(
sType=vulkan.VK_STRUCTURE_TYPE_APPLICATION_INFO,
pApplicationName="Hello Triangle",
applicationVersion=vulkan.VK_MAKE_VERSION(1, 0, 0),
pEngineName="No Engine",
engineVersion=vulkan.VK_MAKE_VERSION(1, 0, 0),
apiVersion=vulkan.VK_API_VERSION_1_0)
extensions = vulkan.vkEnumerateInstanceExtensionProperties(None)
extensions = [e.extensionName for e in extensions]
print("available extensions:", *extensions)
layers = vulkan.vkEnumerateInstanceLayerProperties()
layers = [l.layerName for l in layers]
print("available layers:", *layers)
layers = []#'VK_LAYER_LUNARG_standard_validation'] # I don't have this one.
extensions = ['VK_KHR_surface', 'VK_EXT_debug_report', 'VK_KHR_wayland_surface']
createinfo = vulkan.VkInstanceCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
flags=0,
pApplicationInfo=appinfo,
enabledExtensionCount=len(extensions),
ppEnabledExtensionNames=extensions,
enabledLayerCount=len(layers),
ppEnabledLayerNames=layers)
instance = vulkan.vkCreateInstance(createinfo, None)
vkDestroySurfaceKHR = vulkan.vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR")
vkCreateWaylandSurfaceKHR = vulkan.vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR")
surface_create = vulkan.VkWaylandSurfaceCreateInfoKHR(
sType=vulkan.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
display=ffi.cast('void *', wl_display),
surface=ffi.cast('void *', s._ptr),
flags=0)
surface_vk_wayland = vkCreateWaylandSurfaceKHR(instance, surface_create, None)
physical_devices = vulkan.vkEnumeratePhysicalDevices(instance)
physical_devices_features = {physical_device: vulkan.vkGetPhysicalDeviceFeatures(physical_device) for physical_device in physical_devices}
physical_devices_properties = {physical_device: vulkan.vkGetPhysicalDeviceProperties(physical_device) for physical_device in physical_devices}
physical_device = physical_devices[0]
print("available devices:", *(p.deviceName for p in physical_devices_properties.values()))
print("selected device:", physical_devices_properties[physical_device].deviceName)
vkGetPhysicalDeviceSurfaceSupportKHR = vulkan.vkGetInstanceProcAddr(instance, 'vkGetPhysicalDeviceSurfaceSupportKHR')
queue_families = vulkan.vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice=physical_device)
print("{} available queue families".format(len(queue_families)))
queue_family_graphic_index = -1
queue_family_present_index = -1
for i, queue_family in enumerate(queue_families):
support_present = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice=physical_device,
queueFamilyIndex=i,
surface=surface_vk_wayland)
if (queue_family.queueCount > 0 and queue_family.queueFlags & vulkan.VK_QUEUE_GRAPHICS_BIT):
queue_family_graphic_index = i
queue_family_present_index = i
break
print("index of selected queue families, graphics: {}, presentation: {}".format(queue_family_graphic_index, queue_family_present_index))
extensions = vulkan.vkEnumerateDeviceExtensionProperties(physicalDevice=physical_device, pLayerName=None)
extensions = [e.extensionName for e in extensions]
print("available device extensions:", *extensions)
extensions = [vulkan.VK_KHR_SWAPCHAIN_EXTENSION_NAME]
queues_create = [vulkan.VkDeviceQueueCreateInfo(sType=vulkan.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
queueFamilyIndex=i,
queueCount=1,
pQueuePriorities=[1],
flags=0)
for i in {queue_family_graphic_index,
queue_family_present_index}]
device_create = vulkan.VkDeviceCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
pQueueCreateInfos=queues_create,
queueCreateInfoCount=len(queues_create),
pEnabledFeatures=physical_devices_features[physical_device],
flags=0,
enabledLayerCount=len(layers),
ppEnabledLayerNames=layers,
enabledExtensionCount=len(extensions),
ppEnabledExtensionNames=extensions)
logical_device = vulkan.vkCreateDevice(physical_device, device_create, None)
graphic_queue = vulkan.vkGetDeviceQueue(
device=logical_device,
queueFamilyIndex=queue_family_graphic_index,
queueIndex=0)
presentation_queue = vulkan.vkGetDeviceQueue(
device=logical_device,
queueFamilyIndex=queue_family_present_index,
queueIndex=0)
print("Logical device and graphic queue successfully created")
# ----------
# Create swapchain
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = vulkan.vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR")
vkGetPhysicalDeviceSurfaceFormatsKHR = vulkan.vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR")
vkGetPhysicalDeviceSurfacePresentModesKHR = vulkan.vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR")
surface_capabilities = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice=physical_device, surface=surface_vk_wayland)
surface_formats = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice=physical_device, surface=surface_vk_wayland)
surface_present_modes = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice=physical_device, surface=surface_vk_wayland)
if not surface_formats or not surface_present_modes:
raise Exception('No available swapchain')
def get_surface_format(formats):
for f in formats:
if f.format == vulkan.VK_FORMAT_UNDEFINED:
return f
if (f.format == vulkan.VK_FORMAT_B8G8R8A8_UNORM and
f.colorSpace == vulkan.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR):
return f
return formats[0]
def get_surface_present_mode(present_modes):
for p in present_modes:
if p == vulkan.VK_PRESENT_MODE_MAILBOX_KHR:
return p
return vulkan.VK_PRESENT_MODE_FIFO_KHR
def get_swap_extent(capabilities):
#uint32_max = 4294967295
#if capabilities.currentExtent.width != uint32_max:
# return vulkan.VkExtent2D(width=capabilities.currentExtent.width,
# height=capabilities.currentExtent.height)
#
width = max(
capabilities.minImageExtent.width,
min(capabilities.maxImageExtent.width, WIDTH))
height = max(
capabilities.minImageExtent.height,
min(capabilities.maxImageExtent.height, HEIGHT))
actualExtent = vulkan.VkExtent2D(width=width, height=height);
return actualExtent
surface_format = get_surface_format(surface_formats)
present_mode = get_surface_present_mode(surface_present_modes)
extent = get_swap_extent(surface_capabilities)
imageCount = surface_capabilities.minImageCount + 1;
if surface_capabilities.maxImageCount > 0 and imageCount > surface_capabilities.maxImageCount:
imageCount = surface_capabilities.maxImageCount
print('selected format: {}'.format(surface_format.format))
print('{} available swapchain present modes'.format(len(surface_present_modes)))
imageSharingMode = vulkan.VK_SHARING_MODE_EXCLUSIVE
queueFamilyIndexCount = 0
pQueueFamilyIndices = None
if queue_family_graphic_index != queue_family_present_index:
imageSharingMode = vulkan.VK_SHARING_MODE_CONCURRENT
queueFamilyIndexCount = 2
pQueueFamilyIndices = [queue_family_graphic_index, queue_family_present_index]
vkCreateSwapchainKHR = vulkan.vkGetInstanceProcAddr(instance, 'vkCreateSwapchainKHR')
vkDestroySwapchainKHR = vulkan.vkGetInstanceProcAddr(instance, 'vkDestroySwapchainKHR')
vkGetSwapchainImagesKHR = vulkan.vkGetInstanceProcAddr(instance, 'vkGetSwapchainImagesKHR')
swapchain_create = vulkan.VkSwapchainCreateInfoKHR(
sType=vulkan.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
flags=0,
surface=surface_vk_wayland,
minImageCount=imageCount,
imageFormat=surface_format.format,
imageColorSpace=surface_format.colorSpace,
imageExtent=extent,
imageArrayLayers=1,
imageUsage=vulkan.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
imageSharingMode=imageSharingMode,
queueFamilyIndexCount=queueFamilyIndexCount,
pQueueFamilyIndices=pQueueFamilyIndices,
compositeAlpha=vulkan.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
presentMode=present_mode,
clipped=vulkan.VK_TRUE,
oldSwapchain=None,
preTransform=surface_capabilities.currentTransform)
swapchain = vkCreateSwapchainKHR(logical_device, swapchain_create, None)
swapchain_images = vkGetSwapchainImagesKHR(logical_device, swapchain)
# Create image view for each image in swapchain
image_views = []
for image in swapchain_images:
subresourceRange = vulkan.VkImageSubresourceRange(
aspectMask=vulkan.VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel=0,
levelCount=1,
baseArrayLayer=0,
layerCount=1)
components = vulkan.VkComponentMapping(
r=vulkan.VK_COMPONENT_SWIZZLE_IDENTITY,
g=vulkan.VK_COMPONENT_SWIZZLE_IDENTITY,
b=vulkan.VK_COMPONENT_SWIZZLE_IDENTITY,
a=vulkan.VK_COMPONENT_SWIZZLE_IDENTITY)
imageview_create = vulkan.VkImageViewCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
image=image,
flags=0,
viewType=vulkan.VK_IMAGE_VIEW_TYPE_2D,
format=surface_format.format,
components=components,
subresourceRange=subresourceRange)
image_views.append(vulkan.vkCreateImageView(logical_device, imageview_create, None))
print("{} image views created".format(len(image_views)))
# Load spirv shader
path = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(path, "vert.spv"), 'rb') as f:
vert_shader_spirv = f.read()
with open(os.path.join(path, "frag.spv"), 'rb') as f:
frag_shader_spirv = f.read()
# Added note: get the spirv files from here:
# https://github.com/realitix/vulkan/tree/master/example/contribs
# Create shader
vert_shader_create = vulkan.VkShaderModuleCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
flags=0,
codeSize=len(vert_shader_spirv),
pCode=vert_shader_spirv)
vert_shader_module = vulkan.vkCreateShaderModule(logical_device, vert_shader_create, None)
frag_shader_create = vulkan.VkShaderModuleCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
flags=0,
codeSize=len(frag_shader_spirv),
pCode=frag_shader_spirv)
frag_shader_module = vulkan.vkCreateShaderModule(logical_device, frag_shader_create, None)
# Create shader stage
vert_stage_create = vulkan.VkPipelineShaderStageCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
stage=vulkan.VK_SHADER_STAGE_VERTEX_BIT,
module=vert_shader_module,
flags=0,
pSpecializationInfo=None,
pName='main')
frag_stage_create = vulkan.VkPipelineShaderStageCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
stage=vulkan.VK_SHADER_STAGE_FRAGMENT_BIT,
module=frag_shader_module,
flags=0,
pSpecializationInfo=None,
pName='main')
# Create render pass
color_attachment = vulkan.VkAttachmentDescription(
flags=0,
format=surface_format.format,
samples=vulkan.VK_SAMPLE_COUNT_1_BIT,
loadOp=vulkan.VK_ATTACHMENT_LOAD_OP_CLEAR,
storeOp=vulkan.VK_ATTACHMENT_STORE_OP_STORE,
stencilLoadOp=vulkan.VK_ATTACHMENT_LOAD_OP_DONT_CARE,
stencilStoreOp=vulkan.VK_ATTACHMENT_STORE_OP_DONT_CARE,
initialLayout=vulkan.VK_IMAGE_LAYOUT_UNDEFINED,
finalLayout=vulkan.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
color_attachment_reference = vulkan.VkAttachmentReference(
attachment=0,
layout=vulkan.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
sub_pass = vulkan.VkSubpassDescription(
flags=0,
pipelineBindPoint=vulkan.VK_PIPELINE_BIND_POINT_GRAPHICS,
inputAttachmentCount=0,
pInputAttachments=None,
pResolveAttachments=None,
pDepthStencilAttachment=None,
preserveAttachmentCount=0,
pPreserveAttachments=None,
colorAttachmentCount=1,
pColorAttachments=[color_attachment_reference])
dependency = vulkan.VkSubpassDependency(
dependencyFlags=0,
srcSubpass=vulkan.VK_SUBPASS_EXTERNAL,
dstSubpass=0,
srcStageMask=vulkan.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
srcAccessMask=0,
dstStageMask=vulkan.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
dstAccessMask=vulkan.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | vulkan.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)
render_pass_create = vulkan.VkRenderPassCreateInfo(
flags=0,
sType=vulkan.VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
attachmentCount=1,
pAttachments=[color_attachment],
subpassCount=1,
pSubpasses=[sub_pass],
dependencyCount=1,
pDependencies=[dependency])
render_pass = vulkan.vkCreateRenderPass(logical_device, render_pass_create, None)
# Create graphic pipeline
vertex_input_create = vulkan.VkPipelineVertexInputStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
flags=0,
vertexBindingDescriptionCount=0,
pVertexBindingDescriptions=None,
vertexAttributeDescriptionCount=0,
pVertexAttributeDescriptions=None)
input_assembly_create = vulkan.VkPipelineInputAssemblyStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
flags=0,
topology=vulkan.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
primitiveRestartEnable=vulkan.VK_FALSE)
viewport = vulkan.VkViewport(
x=0, y=0, width=float(extent.width), height=float(extent.height),
minDepth=0., maxDepth=1.)
scissor_offset = vulkan.VkOffset2D(x=0, y=0)
scissor = vulkan.VkRect2D(offset=scissor_offset, extent=extent)
viewport_state_create = vulkan.VkPipelineViewportStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
flags=0,
viewportCount=1,
pViewports=[viewport],
scissorCount=1,
pScissors=[scissor])
rasterizer_create = vulkan.VkPipelineRasterizationStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
flags=0,
depthClampEnable=vulkan.VK_FALSE,
rasterizerDiscardEnable=vulkan.VK_FALSE,
polygonMode=vulkan.VK_POLYGON_MODE_FILL,
lineWidth=1,
cullMode=vulkan.VK_CULL_MODE_BACK_BIT,
frontFace=vulkan.VK_FRONT_FACE_CLOCKWISE,
depthBiasEnable=vulkan.VK_FALSE,
depthBiasConstantFactor=0.,
depthBiasClamp=0.,
depthBiasSlopeFactor=0.)
multisample_create = vulkan.VkPipelineMultisampleStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
flags=0,
sampleShadingEnable=vulkan.VK_FALSE,
rasterizationSamples=vulkan.VK_SAMPLE_COUNT_1_BIT,
minSampleShading=1,
pSampleMask=None,
alphaToCoverageEnable=vulkan.VK_FALSE,
alphaToOneEnable=vulkan.VK_FALSE)
color_blend_attachment = vulkan.VkPipelineColorBlendAttachmentState(
colorWriteMask=vulkan.VK_COLOR_COMPONENT_R_BIT | vulkan.VK_COLOR_COMPONENT_G_BIT | vulkan.VK_COLOR_COMPONENT_B_BIT | vulkan.VK_COLOR_COMPONENT_A_BIT,
blendEnable=vulkan.VK_FALSE,
srcColorBlendFactor=vulkan.VK_BLEND_FACTOR_ONE,
dstColorBlendFactor=vulkan.VK_BLEND_FACTOR_ZERO,
colorBlendOp=vulkan.VK_BLEND_OP_ADD,
srcAlphaBlendFactor=vulkan.VK_BLEND_FACTOR_ONE,
dstAlphaBlendFactor=vulkan.VK_BLEND_FACTOR_ZERO,
alphaBlendOp=vulkan.VK_BLEND_OP_ADD)
color_blend_create = vulkan.VkPipelineColorBlendStateCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
flags=0,
logicOpEnable=vulkan.VK_FALSE,
logicOp=vulkan.VK_LOGIC_OP_COPY,
attachmentCount=1,
pAttachments=[color_blend_attachment],
blendConstants=[0, 0, 0, 0])
push_constant_ranges = vulkan.VkPushConstantRange(
stageFlags=0,
offset=0,
size=0)
pipeline_layout_create = vulkan.VkPipelineLayoutCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
flags=0,
setLayoutCount=0,
pSetLayouts=None,
pushConstantRangeCount=0,
pPushConstantRanges=[push_constant_ranges])
pipeline_layout = vulkan.vkCreatePipelineLayout(logical_device, pipeline_layout_create, None)
# Finally create graphic pipeline
pipeline_create = vulkan.VkGraphicsPipelineCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
flags=0,
stageCount=2,
pStages=[vert_stage_create, frag_stage_create],
pVertexInputState=vertex_input_create,
pInputAssemblyState=input_assembly_create,
pTessellationState=None,
pViewportState=viewport_state_create,
pRasterizationState=rasterizer_create,
pMultisampleState=multisample_create,
pDepthStencilState=None,
pColorBlendState=color_blend_create,
pDynamicState=None,
layout=pipeline_layout,
renderPass=render_pass,
subpass=0,
basePipelineHandle=None,
basePipelineIndex=-1)
pipeline = vulkan.vkCreateGraphicsPipelines(logical_device, None, 1, [pipeline_create], None)
# Framebuffers creation
framebuffers = []
for image in image_views:
attachments = [image]
framebuffer_create = vulkan.VkFramebufferCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
flags=0,
renderPass=render_pass,
attachmentCount=len(attachments),
pAttachments=attachments,
width=extent.width,
height=extent.height,
layers=1)
framebuffers.append(
vulkan.vkCreateFramebuffer(logical_device, framebuffer_create, None))
# Create command pools
command_pool_create = vulkan.VkCommandPoolCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
queueFamilyIndex=queue_family_graphic_index,
flags=0)
command_pool = vulkan.vkCreateCommandPool(logical_device, command_pool_create, None)
# Create command buffers
command_buffers_create = vulkan.VkCommandBufferAllocateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
commandPool=command_pool,
level=vulkan.VK_COMMAND_BUFFER_LEVEL_PRIMARY,
commandBufferCount=len(framebuffers))
command_buffers = vulkan.vkAllocateCommandBuffers(logical_device, command_buffers_create)
# Record command buffer
for i, command_buffer in enumerate(command_buffers):
command_buffer_begin_create = vulkan.VkCommandBufferBeginInfo(
sType=vulkan.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
flags=vulkan.VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
pInheritanceInfo=None)
vulkan.vkBeginCommandBuffer(command_buffer, command_buffer_begin_create)
# Create render pass
render_area = vulkan.VkRect2D(offset=vulkan.VkOffset2D(x=0, y=0),
extent=extent)
color = vulkan.VkClearColorValue(float32=[0, 1, 0, 1])
clear_value = vulkan.VkClearValue(color=color)
render_pass_begin_create = vulkan.VkRenderPassBeginInfo(
sType=vulkan.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
renderPass=render_pass,
framebuffer=framebuffers[i],
renderArea=render_area,
clearValueCount=1,
pClearValues=[clear_value])
vulkan.vkCmdBeginRenderPass(command_buffer, render_pass_begin_create, vulkan.VK_SUBPASS_CONTENTS_INLINE)
# Bind pipeline
vulkan.vkCmdBindPipeline(command_buffer, vulkan.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline[0])
vulkan.vkCmdSetViewport(command_buffer, 0, 1, [viewport])
# Draw
vulkan.vkCmdDraw(command_buffer, 3, 1, 0, 0)
# End
vulkan.vkCmdEndRenderPass(command_buffer)
vulkan.vkEndCommandBuffer(command_buffer)
# Create semaphore
semaphore_create = vulkan.VkSemaphoreCreateInfo(
sType=vulkan.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
flags=0)
semaphore_image_available = vulkan.vkCreateSemaphore(logical_device, semaphore_create, None)
semaphore_render_finished = vulkan.vkCreateSemaphore(logical_device, semaphore_create, None)
vkAcquireNextImageKHR = vulkan.vkGetInstanceProcAddr(instance, "vkAcquireNextImageKHR")
vkQueuePresentKHR = vulkan.vkGetInstanceProcAddr(instance, "vkQueuePresentKHR")
wait_semaphores = [semaphore_image_available]
wait_stages = [vulkan.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT]
signal_semaphores = [semaphore_render_finished]
submit_create = vulkan.VkSubmitInfo(
sType=vulkan.VK_STRUCTURE_TYPE_SUBMIT_INFO,
waitSemaphoreCount=len(wait_semaphores),
pWaitSemaphores=wait_semaphores,
pWaitDstStageMask=wait_stages,
commandBufferCount=1,
pCommandBuffers=[command_buffers[0]],
signalSemaphoreCount=len(signal_semaphores),
pSignalSemaphores=signal_semaphores)
present_create = vulkan.VkPresentInfoKHR(
sType=vulkan.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
waitSemaphoreCount=1,
pWaitSemaphores=signal_semaphores,
swapchainCount=1,
pSwapchains=[swapchain],
pImageIndices=[0],
pResults=None)
# optimization to avoid creating a new array each time
submit_list = vulkan.ffi.new('VkSubmitInfo[1]', [submit_create])
# Note: above we are calling new from *vulkan.ffi*. Lots of ffi's around in this file. Count them.
def draw_frame():
try:
image_index = vkAcquireNextImageKHR(logical_device, swapchain, vulkan.UINT64_MAX, semaphore_image_available, None)
except vulkan.VkNotReady:
print('not ready')
return
submit_create.pCommandBuffers[0] = command_buffers[image_index]
vulkan.vkQueueSubmit(graphic_queue, 1, submit_list, None)
present_create.pImageIndices[0] = image_index
vkQueuePresentKHR(presentation_queue, present_create)
# Moved the clean up stuff to be done here in this callback.
def on_close_request(x):
# ----------
# Clean everything
vulkan.vkDestroySemaphore(logical_device, semaphore_image_available, None)
vulkan.vkDestroySemaphore(logical_device, semaphore_render_finished, None)
vulkan.vkDestroyCommandPool(logical_device, command_pool, None)
for f in framebuffers:
vulkan.vkDestroyFramebuffer(logical_device, f, None)
vulkan.vkDestroyPipeline(logical_device, pipeline[0], None)
vulkan.vkDestroyPipelineLayout(logical_device, pipeline_layout, None)
vulkan.vkDestroyRenderPass(logical_device, render_pass, None)
vulkan.vkDestroyShaderModule(logical_device, frag_shader_module, None)
vulkan.vkDestroyShaderModule(logical_device, vert_shader_module, None)
for i in image_views:
vulkan.vkDestroyImageView(logical_device, i, None)
vkDestroySwapchainKHR(logical_device, swapchain, None)
vulkan.vkDestroyDevice(logical_device, None)
vkDestroySurfaceKHR(instance, surface_vk_wayland, None)
#vulkan.vkDestroyDebugReportCallbackEXT(instance, callback, None)
vulkan.vkDestroyInstance(instance, None)
win.connect('close-request', on_close_request)
# Main loop
if sys.version_info >= (3, 3):
clock = time.perf_counter
else:
clock = time.clock
running = True
last_time = clock() * 1000
fps = 0
# I modified the main loop into a draw callback.
# Still printing fps though, but instead of 30000 it's 60!
def draw(*args):
nonlocal running, last_time, clock, fps
if running:
fps += 1
if clock() * 1000 - last_time >= 1000:
last_time = clock() * 1000
print("FPS: %s" % fps)
fps = 0
draw_frame()
return True
return False
win.add_tick_callback(draw)
# Here we create our application and run it!
app = Gtk.Application(application_id='com.github.omnp.Example4')
app.connect('activate', on_activate)
app.run(None)
@omnp
Copy link
Author

omnp commented Aug 3, 2023

Screenshot image!
screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment