Vulkan based Ab4d.SharpEngine on Raspberry Pi 4 with a touch screen
by Rok Mandeljc; February 2022 (updated January 2023)
The improved hardware capabilities of the Raspberry Pi 4 and recent advances of the open-source V3DV Vulkan driver for Raspberry Pi make it possible to run Vulkan applications on your Pi. As shown in this post, this requires relatively little effort when using the latest version of the Raspberry Pi OS.
This post comes in two parts: the first part assumes that your Raspberry Pi 4 is connected to a standard external PC monitor, and describes the standard steps required to set up Vulkan, dotnet, and to get SharpEngine demo running. In the second part, an attached LCD screen (a Raspberry Pi add-on) is used instead. The post deals with caveats and issues that come with a particular LCD screen, although the information might be applicable to wider range of Raspberry Pi LCD screens out in the wild.
Part 1: using an external PC monitor
Depending on your monitor and luck, things might work out of the box. If not, you might need to fiddle with HDMI settings on the Pi to get the display working. But overall, using a PC monitor should work with the default v3d
display driver for Raspberry Pi, which is what we need for working 3D Vulkan acceleration.
Install the OS
Instead of the “classic” 32-bit Raspbian OS, we will be using 64-bit variant that has just recently become official. The instructions should largely remain unchanged for 32-bit Raspbian OS, but given that Vulkan works only with Raspberry Pi 4, using 64-bit OS should be preferable to 32-bit in most cases (and comes with substantial performance improvements).
Go to https://www.raspberrypi.com/software/operating-systems to download the image, and choose “Raspberry Pi OS with desktop” under “Raspberry Pi OS (64-bit)”. Note that the first entry is regular 32-bit “Rapsberry Pi OS”, and the one that we are interested in is the second one.
Direct link for the latest version at the time of writing (January 2023) is here.
Download the image and write it to an SD card, and you are ready!
Install vulkan drivers
Even recent (relative to the time of writing) resources on installing Vulkan on Raspberry Pi (e.g., this one) suggest to install Mesa from scratch, either using PiKISS or by building it manually.
In both cases, the latest version of mesa
branch 20.3
is built locally and installed over the system-provided version of mesa. However, looking at the commit history in that branch, it seems that at the time of writing, the last commits were for making the official 20.3.5
release.
And debian
bullseye
already comes with packaged mesa 20.3.5
.
So the only thing required for enabling Vulkan on the system installed from the 2022-01-28-raspios-bullseye-arm64
image is to install the mesa-vulkan-drivers
package:
sudo apt-get update
sudo apt-get install mesa-vulkan-drivers vulkan-tools
You can also install Vulkan validation layers, which come handy when debugging Vulkan programs:
sudo apt-get install vulkan-validationlayers
Test the vulkan setup
At this point, the vulkan
should be ready to use, with V3D 4.2
device being available in addition to the llvmpipe
software renderer:
pi@avalon:~ $ vulkaninfo
WARNING: v3dv is neither a complete nor a conformant Vulkan implementation. Testing use only.
WARNING: lavapipe is not a conformant vulkan implementation, testing use only.
==========
VULKANINFO
==========
Vulkan Instance Version: 1.2.162
Instance Extensions: count = 18
===============================
VK_EXT_acquire_xlib_display : extension revision 1
VK_EXT_debug_report : extension revision 9
VK_EXT_debug_utils : extension revision 2
VK_EXT_direct_mode_display : extension revision 1
VK_EXT_display_surface_counter : extension revision 1
VK_KHR_device_group_creation : extension revision 1
VK_KHR_display : extension revision 23
VK_KHR_external_fence_capabilities : extension revision 1
VK_KHR_external_memory_capabilities : extension revision 1
VK_KHR_external_semaphore_capabilities : extension revision 1
VK_KHR_get_display_properties2 : extension revision 1
VK_KHR_get_physical_device_properties2 : extension revision 1
VK_KHR_get_surface_capabilities2 : extension revision 1
VK_KHR_surface : extension revision 25
VK_KHR_surface_protected_capabilities : extension revision 1
VK_KHR_wayland_surface : extension revision 6
VK_KHR_xcb_surface : extension revision 6
VK_KHR_xlib_surface : extension revision 6
Layers: count = 3
=================
VK_LAYER_KHRONOS_validation (Khronos Validation Layer) Vulkan version 1.2.162, layer version 1:
Layer Extensions: count = 3
VK_EXT_debug_report : extension revision 9
VK_EXT_debug_utils : extension revision 1
VK_EXT_validation_features : extension revision 2
Devices: count = 2
GPU id = 0 (V3D 4.2)
Layer-Device Extensions: count = 3
VK_EXT_debug_marker : extension revision 4
VK_EXT_tooling_info : extension revision 1
VK_EXT_validation_cache : extension revision 1
GPU id = 1 (llvmpipe (LLVM 11.0.1, 128 bits))
Layer-Device Extensions: count = 3
VK_EXT_debug_marker : extension revision 4
VK_EXT_tooling_info : extension revision 1
VK_EXT_validation_cache : extension revision 1
VK_LAYER_MESA_device_select (Linux device selection layer) Vulkan version 1.2.73, layer version 1:
Layer Extensions: count = 0
Devices: count = 2
GPU id = 0 (V3D 4.2)
Layer-Device Extensions: count = 0
GPU id = 1 (llvmpipe (LLVM 11.0.1, 128 bits))
Layer-Device Extensions: count = 0
VK_LAYER_MESA_overlay (Mesa Overlay layer) Vulkan version 1.1.73, layer version 1:
Layer Extensions: count = 0
Devices: count = 2
GPU id = 0 (V3D 4.2)
Layer-Device Extensions: count = 0
GPU id = 1 (llvmpipe (LLVM 11.0.1, 128 bits))
Layer-Device Extensions: count = 0
Presentable Surfaces:
=====================
GPU id : 0 (V3D 4.2):
Surface types: count = 2
VK_KHR_xcb_surface
VK_KHR_xlib_surface
Formats: count = 2
SurfaceFormat[0]:
format = FORMAT_B8G8R8A8_SRGB
colorSpace = COLOR_SPACE_SRGB_NONLINEAR_KHR
SurfaceFormat[1]:
format = FORMAT_B8G8R8A8_UNORM
colorSpace = COLOR_SPACE_SRGB_NONLINEAR_KHR
Present Modes: count = 4
PRESENT_MODE_IMMEDIATE_KHR
PRESENT_MODE_MAILBOX_KHR
PRESENT_MODE_FIFO_KHR
PRESENT_MODE_FIFO_RELAXED_KHR
VkSurfaceCapabilitiesKHR:
-------------------------
minImageCount = 3
maxImageCount = 0
currentExtent:
width = 256
height = 256
minImageExtent:
width = 256
height = 256
maxImageExtent:
width = 256
height = 256
maxImageArrayLayers = 1
supportedTransforms: count = 1
SURFACE_TRANSFORM_IDENTITY_BIT_KHR
currentTransform = SURFACE_TRANSFORM_IDENTITY_BIT_KHR
supportedCompositeAlpha: count = 2
COMPOSITE_ALPHA_OPAQUE_BIT_KHR
COMPOSITE_ALPHA_INHERIT_BIT_KHR
supportedUsageFlags: count = 4
IMAGE_USAGE_TRANSFER_SRC_BIT
IMAGE_USAGE_TRANSFER_DST_BIT
IMAGE_USAGE_STORAGE_BIT
IMAGE_USAGE_COLOR_ATTACHMENT_BIT
VkSurfaceCapabilities2EXT:
--------------------------
supportedSurfaceCounters: count = 0
None
VkSurfaceProtectedCapabilitiesKHR:
----------------------------------
supportsProtected = false
GPU id : 1 (llvmpipe (LLVM 11.0.1, 128 bits)):
Surface types: count = 2
VK_KHR_xcb_surface
VK_KHR_xlib_surface
Formats: count = 2
SurfaceFormat[0]:
format = FORMAT_B8G8R8A8_SRGB
colorSpace = COLOR_SPACE_SRGB_NONLINEAR_KHR
SurfaceFormat[1]:
format = FORMAT_B8G8R8A8_UNORM
colorSpace = COLOR_SPACE_SRGB_NONLINEAR_KHR
Present Modes: count = 4
PRESENT_MODE_IMMEDIATE_KHR
PRESENT_MODE_MAILBOX_KHR
PRESENT_MODE_FIFO_KHR
PRESENT_MODE_FIFO_RELAXED_KHR
VkSurfaceCapabilitiesKHR:
-------------------------
minImageCount = 3
maxImageCount = 0
currentExtent:
width = 256
height = 256
minImageExtent:
width = 256
height = 256
maxImageExtent:
width = 256
height = 256
maxImageArrayLayers = 1
supportedTransforms: count = 1
SURFACE_TRANSFORM_IDENTITY_BIT_KHR
currentTransform = SURFACE_TRANSFORM_IDENTITY_BIT_KHR
supportedCompositeAlpha: count = 2
COMPOSITE_ALPHA_OPAQUE_BIT_KHR
COMPOSITE_ALPHA_INHERIT_BIT_KHR
supportedUsageFlags: count = 5
IMAGE_USAGE_TRANSFER_SRC_BIT
IMAGE_USAGE_TRANSFER_DST_BIT
IMAGE_USAGE_SAMPLED_BIT
IMAGE_USAGE_STORAGE_BIT
IMAGE_USAGE_COLOR_ATTACHMENT_BIT
VkSurfaceCapabilities2EXT:
--------------------------
supportedSurfaceCounters: count = 0
None
VkSurfaceProtectedCapabilitiesKHR:
----------------------------------
supportsProtected = false
Device Groups:
==============
Group 0:
Properties:
physicalDevices: count = 1
llvmpipe (LLVM 11.0.1, 128 bits) (ID: 0)
subsetAllocation = 0
Present Capabilities = Group does not support VK_KHR_device_group, skipping printing present capabilities
Group 1:
Properties:
physicalDevices: count = 1
V3D 4.2 (ID: 0)
subsetAllocation = 0
Present Capabilities = Group does not support VK_KHR_device_group, skipping printing present capabilities
Device Properties and Extensions:
=================================
GPU0:
VkPhysicalDeviceProperties:
---------------------------
apiVersion = 4194459 (1.0.155)
driverVersion = 83898373 (0x5003005)
vendorID = 0x14e4
deviceID = 0x002a
deviceType = PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
deviceName = V3D 4.2
VkPhysicalDeviceLimits:
-----------------------
maxImageDimension1D = 4096
maxImageDimension2D = 4096
maxImageDimension3D = 4096
maxImageDimensionCube = 4096
maxImageArrayLayers = 2048
maxTexelBufferElements = 268435456
maxUniformBufferRange = 134217728
maxStorageBufferRange = 134217728
maxPushConstantsSize = 128
maxMemoryAllocationCount = 431003
maxSamplerAllocationCount = 65536
bufferImageGranularity = 0x00000100
sparseAddressSpaceSize = 0x00000000
maxBoundDescriptorSets = 16
maxPerStageDescriptorSamplers = 16
maxPerStageDescriptorUniformBuffers = 12
maxPerStageDescriptorStorageBuffers = 12
maxPerStageDescriptorSampledImages = 16
maxPerStageDescriptorStorageImages = 4
maxPerStageDescriptorInputAttachments = 4
maxPerStageResources = 128
maxDescriptorSetSamplers = 96
maxDescriptorSetUniformBuffers = 72
maxDescriptorSetUniformBuffersDynamic = 8
maxDescriptorSetStorageBuffers = 72
maxDescriptorSetStorageBuffersDynamic = 36
maxDescriptorSetSampledImages = 96
maxDescriptorSetStorageImages = 24
maxDescriptorSetInputAttachments = 4
maxVertexInputAttributes = 16
maxVertexInputBindings = 16
maxVertexInputAttributeOffset = 4294967295
maxVertexInputBindingStride = 4294967295
maxVertexOutputComponents = 64
maxTessellationGenerationLevel = 0
maxTessellationPatchSize = 0
maxTessellationControlPerVertexInputComponents = 0
maxTessellationControlPerVertexOutputComponents = 0
maxTessellationControlPerPatchOutputComponents = 0
maxTessellationControlTotalOutputComponents = 0
maxTessellationEvaluationInputComponents = 0
maxTessellationEvaluationOutputComponents = 0
maxGeometryShaderInvocations = 0
maxGeometryInputComponents = 0
maxGeometryOutputComponents = 0
maxGeometryOutputVertices = 0
maxGeometryTotalOutputComponents = 0
maxFragmentInputComponents = 64
maxFragmentOutputAttachments = 4
maxFragmentDualSrcAttachments = 0
maxFragmentCombinedOutputResources = 20
maxComputeSharedMemorySize = 16384
maxComputeWorkGroupCount: count = 3
65535
65535
65535
maxComputeWorkGroupInvocations = 256
maxComputeWorkGroupSize: count = 3
256
256
256
subPixelPrecisionBits = 6
subTexelPrecisionBits = 8
mipmapPrecisionBits = 8
maxDrawIndexedIndexValue = 16777215
maxDrawIndirectCount = 2147483647
maxSamplerLodBias = 14
maxSamplerAnisotropy = 16
maxViewports = 1
maxViewportDimensions: count = 2
4096
4096
viewportBoundsRange: count = 2
-8192
8191
viewportSubPixelBits = 0
minMemoryMapAlignment = 4096
minTexelBufferOffsetAlignment = 0x00000100
minUniformBufferOffsetAlignment = 0x00000020
minStorageBufferOffsetAlignment = 0x00000020
minTexelOffset = -8
maxTexelOffset = 7
minTexelGatherOffset = -8
maxTexelGatherOffset = 7
minInterpolationOffset = -0.5
maxInterpolationOffset = 0.5
subPixelInterpolationOffsetBits = 6
maxFramebufferWidth = 4096
maxFramebufferHeight = 4096
maxFramebufferLayers = 256
framebufferColorSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferDepthSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferStencilSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferNoAttachmentsSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
maxColorAttachments = 4
sampledImageColorSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageIntegerSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageDepthSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageStencilSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
storageImageSampleCounts: count = 1
SAMPLE_COUNT_1_BIT
maxSampleMaskWords = 1
timestampComputeAndGraphics = true
timestampPeriod = 1
maxClipDistances = 8
maxCullDistances = 0
maxCombinedClipAndCullDistances = 8
discreteQueuePriorities = 2
pointSizeRange: count = 2
0
512
lineWidthRange: count = 2
1
32
pointSizeGranularity = 0
lineWidthGranularity = 0
strictLines = true
standardSampleLocations = false
optimalBufferCopyOffsetAlignment = 0x00000020
optimalBufferCopyRowPitchAlignment = 0x00000020
nonCoherentAtomSize = 0x00000100
VkPhysicalDeviceSparseProperties:
---------------------------------
residencyStandard2DBlockShape = false
residencyStandard2DMultisampleBlockShape = false
residencyStandard3DBlockShape = false
residencyAlignedMipSize = false
residencyNonResidentStrict = false
VkPhysicalDeviceIDPropertiesKHR:
--------------------------------
deviceUUID = e8c1c12d-2325-2af0-b0e8-66d7e87e7189
driverUUID = ed5483eb-9eb8-fe23-3b21-a3d24026d778
deviceNodeMask = 0
deviceLUIDValid = false
Device Extensions: count = 5
----------------------------
VK_EXT_external_memory_dma_buf : extension revision 1
VK_KHR_external_memory : extension revision 1
VK_KHR_external_memory_fd : extension revision 1
VK_KHR_maintenance1 : extension revision 2
VK_KHR_swapchain : extension revision 68
VkQueueFamilyProperties:
========================
queueProperties[0]:
-------------------
minImageTransferGranularity = (1,1,1)
queueCount = 1
queueFlags = QUEUE_GRAPHICS | QUEUE_COMPUTE | QUEUE_TRANSFER
timestampValidBits = 64
present support = true
VkPhysicalDeviceMemoryProperties:
=================================
memoryHeaps: count = 1
memoryHeaps[0]:
size = 1765388288 (0x6939b000) (1.64 GiB)
budget = 103079215104 (0x1800000000) (96.00 GiB)
usage = 103079215104 (0x1800000000) (96.00 GiB)
flags: count = 1
MEMORY_HEAP_DEVICE_LOCAL_BIT
memoryTypes: count = 1
memoryTypes[0]:
heapIndex = 0
propertyFlags = 0x0007: count = 3
MEMORY_PROPERTY_DEVICE_LOCAL_BIT
MEMORY_PROPERTY_HOST_VISIBLE_BIT
MEMORY_PROPERTY_HOST_COHERENT_BIT
usable for:
IMAGE_TILING_OPTIMAL:
color images
FORMAT_D16_UNORM
FORMAT_X8_D24_UNORM_PACK32
FORMAT_D32_SFLOAT
FORMAT_D24_UNORM_S8_UINT
(non-sparse)
IMAGE_TILING_LINEAR:
color images
(non-sparse)
VkPhysicalDeviceFeatures:
=========================
robustBufferAccess = true
fullDrawIndexUint32 = false
imageCubeArray = true
independentBlend = true
geometryShader = false
tessellationShader = false
sampleRateShading = true
dualSrcBlend = false
logicOp = true
multiDrawIndirect = false
drawIndirectFirstInstance = true
depthClamp = false
depthBiasClamp = false
fillModeNonSolid = true
depthBounds = false
wideLines = true
largePoints = true
alphaToOne = true
multiViewport = false
samplerAnisotropy = true
textureCompressionETC2 = true
textureCompressionASTC_LDR = false
textureCompressionBC = false
occlusionQueryPrecise = true
pipelineStatisticsQuery = false
vertexPipelineStoresAndAtomics = true
fragmentStoresAndAtomics = true
shaderTessellationAndGeometryPointSize = false
shaderImageGatherExtended = false
shaderStorageImageExtendedFormats = true
shaderStorageImageMultisample = false
shaderStorageImageReadWithoutFormat = false
shaderStorageImageWriteWithoutFormat = false
shaderUniformBufferArrayDynamicIndexing = false
shaderSampledImageArrayDynamicIndexing = false
shaderStorageBufferArrayDynamicIndexing = false
shaderStorageImageArrayDynamicIndexing = false
shaderClipDistance = true
shaderCullDistance = false
shaderFloat64 = false
shaderInt64 = false
shaderInt16 = false
shaderResourceResidency = false
shaderResourceMinLod = false
sparseBinding = false
sparseResidencyBuffer = false
sparseResidencyImage2D = false
sparseResidencyImage3D = false
sparseResidency2Samples = false
sparseResidency4Samples = false
sparseResidency8Samples = false
sparseResidency16Samples = false
sparseResidencyAliased = false
variableMultisampleRate = false
inheritedQueries = true
GPU1:
-----
VkPhysicalDeviceProperties:
---------------------------
apiVersion = 4194306 (1.0.2)
driverVersion = 1 (0x0001)
vendorID = 0x10005
deviceID = 0x0000
deviceType = PHYSICAL_DEVICE_TYPE_CPU
deviceName = llvmpipe (LLVM 11.0.1, 128 bits)
VkPhysicalDeviceLimits:
-----------------------
maxImageDimension1D = 16384
maxImageDimension2D = 16384
maxImageDimension3D = 4096
maxImageDimensionCube = 32768
maxImageArrayLayers = 2048
maxTexelBufferElements = 134217728
maxUniformBufferRange = 65536
maxStorageBufferRange = 134217728
maxPushConstantsSize = 128
maxMemoryAllocationCount = 4096
maxSamplerAllocationCount = 32768
bufferImageGranularity = 0x00000040
sparseAddressSpaceSize = 0x00000000
maxBoundDescriptorSets = 8
maxPerStageDescriptorSamplers = 32
maxPerStageDescriptorUniformBuffers = 16
maxPerStageDescriptorStorageBuffers = 16
maxPerStageDescriptorSampledImages = 128
maxPerStageDescriptorStorageImages = 128
maxPerStageDescriptorInputAttachments = 8
maxPerStageResources = 128
maxDescriptorSetSamplers = 32768
maxDescriptorSetUniformBuffers = 256
maxDescriptorSetUniformBuffersDynamic = 256
maxDescriptorSetStorageBuffers = 256
maxDescriptorSetStorageBuffersDynamic = 256
maxDescriptorSetSampledImages = 256
maxDescriptorSetStorageImages = 256
maxDescriptorSetInputAttachments = 256
maxVertexInputAttributes = 32
maxVertexInputBindings = 32
maxVertexInputAttributeOffset = 2047
maxVertexInputBindingStride = 2048
maxVertexOutputComponents = 128
maxTessellationGenerationLevel = 64
maxTessellationPatchSize = 32
maxTessellationControlPerVertexInputComponents = 128
maxTessellationControlPerVertexOutputComponents = 128
maxTessellationControlPerPatchOutputComponents = 128
maxTessellationControlTotalOutputComponents = 4096
maxTessellationEvaluationInputComponents = 128
maxTessellationEvaluationOutputComponents = 128
maxGeometryShaderInvocations = 32
maxGeometryInputComponents = 64
maxGeometryOutputComponents = 128
maxGeometryOutputVertices = 1024
maxGeometryTotalOutputComponents = 1024
maxFragmentInputComponents = 128
maxFragmentOutputAttachments = 8
maxFragmentDualSrcAttachments = 2
maxFragmentCombinedOutputResources = 8
maxComputeSharedMemorySize = 32768
maxComputeWorkGroupCount: count = 3
65535
65535
65535
maxComputeWorkGroupInvocations = 1024
maxComputeWorkGroupSize: count = 3
1024
1024
1024
subPixelPrecisionBits = 8
subTexelPrecisionBits = 8
mipmapPrecisionBits = 8
maxDrawIndexedIndexValue = 4294967295
maxDrawIndirectCount = 4294967295
maxSamplerLodBias = 16
maxSamplerAnisotropy = 16
maxViewports = 16
maxViewportDimensions: count = 2
16384
16384
viewportBoundsRange: count = 2
-32768
32768
viewportSubPixelBits = 0
minMemoryMapAlignment = 4096
minTexelBufferOffsetAlignment = 0x00000010
minUniformBufferOffsetAlignment = 0x00000010
minStorageBufferOffsetAlignment = 0x00000010
minTexelOffset = -32
maxTexelOffset = 31
minTexelGatherOffset = -32
maxTexelGatherOffset = 31
minInterpolationOffset = -2
maxInterpolationOffset = 2
subPixelInterpolationOffsetBits = 8
maxFramebufferWidth = 16384
maxFramebufferHeight = 16384
maxFramebufferLayers = 2048
framebufferColorSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferDepthSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferStencilSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
framebufferNoAttachmentsSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
maxColorAttachments = 8
sampledImageColorSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageIntegerSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageDepthSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
sampledImageStencilSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
storageImageSampleCounts: count = 2
SAMPLE_COUNT_1_BIT
SAMPLE_COUNT_4_BIT
maxSampleMaskWords = 1
timestampComputeAndGraphics = true
timestampPeriod = 1
maxClipDistances = 8
maxCullDistances = 8
maxCombinedClipAndCullDistances = 8
discreteQueuePriorities = 2
pointSizeRange: count = 2
0
255
lineWidthRange: count = 2
1
1
pointSizeGranularity = 0.125
lineWidthGranularity = 0
strictLines = false
standardSampleLocations = true
optimalBufferCopyOffsetAlignment = 0x00000080
optimalBufferCopyRowPitchAlignment = 0x00000080
nonCoherentAtomSize = 0x00000040
VkPhysicalDeviceSparseProperties:
---------------------------------
residencyStandard2DBlockShape = false
residencyStandard2DMultisampleBlockShape = false
residencyStandard3DBlockShape = false
residencyAlignedMipSize = false
residencyNonResidentStrict = false
VkPhysicalDeviceDriverPropertiesKHR:
------------------------------------
driverID = DRIVER_ID_MESA_LLVMPIPE
driverName = llvmpipe
driverInfo = Mesa 20.3.5 (LLVM 11.0.1)
conformanceVersion = 1.0.0.0
VkPhysicalDeviceIDPropertiesKHR:
--------------------------------
deviceUUID = 00000000-0000-0000-0000-000000000000
driverUUID = 00000000-0000-0000-0000-000000000000
deviceNodeMask = 0
deviceLUIDValid = false
Device Extensions: count = 14
-----------------------------
VK_EXT_external_memory_dma_buf : extension revision 1
VK_EXT_private_data : extension revision 1
VK_GOOGLE_decorate_string : extension revision 1
VK_GOOGLE_hlsl_functionality1 : extension revision 1
VK_KHR_bind_memory2 : extension revision 1
VK_KHR_dedicated_allocation : extension revision 1
VK_KHR_driver_properties : extension revision 1
VK_KHR_get_memory_requirements2 : extension revision 1
VK_KHR_incremental_present : extension revision 1
VK_KHR_maintenance1 : extension revision 1
VK_KHR_relaxed_block_layout : extension revision 1
VK_KHR_sampler_mirror_clamp_to_edge : extension revision 1
VK_KHR_storage_buffer_storage_class : extension revision 1
VK_KHR_swapchain : extension revision 68
VkQueueFamilyProperties:
========================
queueProperties[0]:
-------------------
minImageTransferGranularity = (1,1,1)
queueCount = 1
queueFlags = QUEUE_GRAPHICS | QUEUE_COMPUTE | QUEUE_TRANSFER
timestampValidBits = 64
present support = true
VkPhysicalDeviceMemoryProperties:
=================================
memoryHeaps: count = 1
memoryHeaps[0]:
size = 2147483648 (0x80000000) (2.00 GiB)
budget = 0 (0x00000000) (0.00 B)
usage = 0 (0x00000000) (0.00 B)
flags: count = 1
MEMORY_HEAP_DEVICE_LOCAL_BIT
memoryTypes: count = 1
memoryTypes[0]:
heapIndex = 0
propertyFlags = 0x000f: count = 4
MEMORY_PROPERTY_DEVICE_LOCAL_BIT
MEMORY_PROPERTY_HOST_VISIBLE_BIT
MEMORY_PROPERTY_HOST_COHERENT_BIT
MEMORY_PROPERTY_HOST_CACHED_BIT
usable for:
IMAGE_TILING_OPTIMAL:
color images
FORMAT_D16_UNORM
FORMAT_X8_D24_UNORM_PACK32
FORMAT_D32_SFLOAT
FORMAT_S8_UINT
FORMAT_D24_UNORM_S8_UINT
FORMAT_D32_SFLOAT_S8_UINT
(non-sparse)
IMAGE_TILING_LINEAR:
color images
(non-sparse)
VkPhysicalDeviceFeatures:
=========================
robustBufferAccess = true
fullDrawIndexUint32 = true
imageCubeArray = true
independentBlend = true
geometryShader = true
tessellationShader = true
sampleRateShading = true
dualSrcBlend = true
logicOp = true
multiDrawIndirect = true
drawIndirectFirstInstance = true
depthClamp = true
depthBiasClamp = true
fillModeNonSolid = true
depthBounds = false
wideLines = false
largePoints = true
alphaToOne = true
multiViewport = true
samplerAnisotropy = false
textureCompressionETC2 = false
textureCompressionASTC_LDR = false
textureCompressionBC = true
occlusionQueryPrecise = true
pipelineStatisticsQuery = true
vertexPipelineStoresAndAtomics = true
fragmentStoresAndAtomics = true
shaderTessellationAndGeometryPointSize = true
shaderImageGatherExtended = true
shaderStorageImageExtendedFormats = false
shaderStorageImageMultisample = true
shaderStorageImageReadWithoutFormat = false
shaderStorageImageWriteWithoutFormat = true
shaderUniformBufferArrayDynamicIndexing = false
shaderSampledImageArrayDynamicIndexing = false
shaderStorageBufferArrayDynamicIndexing = false
shaderStorageImageArrayDynamicIndexing = false
shaderClipDistance = true
shaderCullDistance = true
shaderFloat64 = true
shaderInt64 = true
shaderInt16 = true
shaderResourceResidency = false
shaderResourceMinLod = false
sparseBinding = false
sparseResidencyBuffer = false
sparseResidencyImage2D = false
sparseResidencyImage3D = false
sparseResidency2Samples = false
sparseResidency4Samples = false
sparseResidency8Samples = false
sparseResidency16Samples = false
sparseResidencyAliased = false
variableMultisampleRate = false
inheritedQueries = false
VkPhysicalDevicePrivateDataFeaturesEXT:
---------------------------------------
privateData = true
And running the vkcube
demo should also work:
pi@avalon:~ $ vkcube
Install and set up dotnet
Installing dotnet
on Raspberry Pi is a rather straightforward affair. The instructions here are loosely based on instructions from here.
Go to Microsoft’s .NET 6.0 download page, and download the latest SDK 6.0 release for arm64
.
Then, create a directory called dotnet
in your home directory, and unpack the SDK(s) into it, i.e.,
mkdir -p $HOME/dotnet
tar xvf dotnet-sdk-6.0.101-linux-arm64.tar.gz -C $HOME/dotnet/
Then open the .bashrc
file in your home directory using your favorite text editor (e.g., vi ~/.bashrc
) and add the following at the end:
# dotnet
export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet
export DOTNET_CLI_TELEMETRY_OPTOUT=1
This sets up the dotnet
path (and opts out from the CLI telemetry). The path becomes active on the next log-in, or immediately if you re-source the file (i.e., run . ~/.bashrc
from the shell).
After ensuring that dotnet
path is active (see above), you should see the installed SDK(s):
pi@avalon:~ $ dotnet --info
.NET SDK (reflecting any global.json):
Version: 6.0.101
Commit: ef49f6213a
Runtime Environment:
OS Name: debian
OS Version: 11
OS Platform: Linux
RID: debian.11-arm64
Base Path: /home/pi/dotnet/sdk/6.0.101/
Host (useful for support):
Version: 6.0.1
Commit: 3a25a7f1cc
.NET SDKs installed:
6.0.101 [/home/pi/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.1 [/home/pi/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.1 [/home/pi/dotnet/shared/Microsoft.NETCore.App]
To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download
and
pi@avalon:~ $ dotnet --list-sdks
5.0.404 [/home/pi/dotnet/sdk]
6.0.101 [/home/pi/dotnet/sdk]
You can now create and run a hello-world application:
pi@avalon:~ $ dotnet new console -o HelloWorld
The template "Console App" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on /home/pi/HelloWorld/HelloWorld.csproj...
Determining projects to restore...
Restored /home/pi/HelloWorld/HelloWorld.csproj (in 406 ms).
Restore succeeded.
pi@avalon:~ $ cd HelloWorld/
pi@avalon:~/HelloWorld $ dotnet run
Hello, World!
Run the Ab4d.SharpEngine demo
The Ab4d.SharpEngine demo uses Silk windowing library, which requires SDL2. In particular, it seems to look for libSDL2.so
file that is provided by the libsdl2-dev
package (which will pull in all other dependencies as well):
sudo apt-get install libsdl2-dev
You are now all set up to run the Ab4d.SharpEngine demo. Simply clone the code from GitHub Ab4d.SharpEngine.Samples:
pi@avalon:~ $ git clone https://github.com/ab4d/Ab4d.SharpEngine.Samples.git
Cloning into 'Ab4d.SharpEngine.Samples'...
remote: Enumerating objects: 317, done.
remote: Counting objects: 100% (317/317), done.
remote: Compressing objects: 100% (213/213), done.
remote: Total 317 (delta 103), reused 299 (delta 91), pack-reused 0
Receiving objects: 100% (317/317), 19.00 MiB | 3.22 MiB/s, done.
Resolving deltas: 100% (103/103), done.
The Ab4d.SharpEngine.Samples repository has multiple solutions, but two can also run on Raspberry Pi 4:
1) Ab4d.SharpEngine.Samples.CrossPlatform.sln
This solution uses SDL for creating windows and for processing user input.
NOTE:
When using Ab4d.SharpEngine v0.9.0-beta1, open the SharpEngineCrossPlatformSamplesRunner.cs and add the following into line 298:
engineCreateOptions.RequiredInstanceExtensionNames.Add("VK_KHR_get_physical_device_properties2");
To start this sample, first move to the CrossPlatform project directory …
pi@avalon:~ $ cd Ab4d.SharpEngine.Samples/Ab4d.SharpEngine.Samples.CrossPlatform/
pi@avalon:~/Ab4d.SharpEngine.Samples/Ab4d.SharpEngine.Samples.CrossPlatform $
… and run the program (and wait a while for it to compile):
pi@avalon:~/Ab4d.SharpEngine.Samples/Ab4d.SharpEngine.Samples.CrossPlatform $ dotnet run
2) Ab4d.SharpEngine.Samples.AvaloniaUI.sln
This solution uses Avalonia UI as cross-platform UI framework.
NOTE:
When using Ab4d.SharpEngine v0.9.0-beta1, open the MainWindow.axaml.cs and add the following into line 66:
SharpEngineSceneView.CreateOptions.RequiredInstanceExtensionNames.Add("VK_KHR_get_physical_device_properties2");
To start this sample, first move to its directory and run the sample by executing dotnet run
. The following is a screenshot of the sample application:
Part 2: using an attached LCD panel
In the first part of this post, we have seen how to set up Vulkan and dotnet on the latest, 64-bit version of the Raspberry Pi OS running on Raspberry Pi 4. In this part, we will explore the possibility of replacing an external PC monitor with an attached LCD panel, which makes for an attractive option for running 3D applications on stand-alone embedded devices.
The LCD screen
The particular LCD screen used in this post is Miuzei Raspberry Pi 4 Touchscreen, a 4-inch IPS touchscreen display with resolution of 800x480.
The first run
After assembling the display following the enclosed instructions, and turning on the Pi, we are greeted by a blue screen that flickers (changes between blue and black) several times as the Pi undergoes its boot process, until the user’s X session is (auto)started. At that point, the desktop is displayed, albeit in portrait mode instead of the landscape one.
This is actually expected and pointed out in the enclosed instructions, telling us that we need to install the touchscreen’s driver:
Driver installation
So, we install the driver - but not before creating a backup of the SD card!
The instructions boil down to cloning a public repository, and running a script from it:
sudo rm -rf LCD-show
git clone https://github.com/goodtft/LCD-show
chmod -R 755 LCD-show
cd LCD-show/
sudo ./MPI4008-show
After the script finishes its setup, it restarts the Pi. While the Pi is shutting down, we go black to blue-and-black flickering, but once it restarts, we get the display right off the bat.
The flicker during boot is gone; we can see the RPi splash screen, the boot progress, and finally, we get the X session in landscape orientation. Plus, the touch screen works.
Profit?
There’s only one problem - we’ve lost the Vulkan functionality. Trying to run vkcube
again, we are greeted by:
pi@avalon:~ $ vkcube
vulkan: No DRI3 support detected - required for presentation
Note: you can probably enable DRI3 in your Xorg config
Could not find both graphics and present queues
So what happened here?
Driver installation, analyzed
To understand the problem we are facing, we need to take a closer look at the installation script that we just ran - its source can be seen here.
A quick summary:
- create a backup of current system files (line 3)
- choose a boot config template, based on the OS (normal Raspbian vs Noobs) (line 6 - 11)
- append several parameters to the selected boot config template (line 12 - 24)
- replace
/boot/config.txt
with the generated one (line 25)
- create/replace
/usr/share/X11/xorg.conf.d/99-fbturbo.conf
with custom copy (line 35)
- copy touch-screen calibration settings to
/etc/X11/xorg.conf.d/99-calibration.conf
. Based on the name, this calibration corresponds to 270° screen rotation. (line 39)
- store the settings to local file, in case we re-run any of the scripts (line 40-41)
- on newer Raspbian versions, ensure
xserver-xorg-input-evdev
is installed (and if the hardcoded repository mirror is unreachable, fall back to local copy of the package) (lines 50-63)
- create a copy of (system’s)
/usr/share/X11/xorg.conf.d/10-evdev.conf
file to /usr/share/X11/xorg.conf.d/45-evdev.conf
- if we passed the screen orientation as parameter to script (we did not), apply settings for that rotation (line 74-78)
- reboot (line 81)
The crucial part for our Vulkan woes is not immediately obvious; but the boot config file template used by the script disables the VC3 V3D driver, which causes the system to fall back to the basic framebuffer driver:
[pi4]
# Enable DRM VC4 V3D driver on top of the dispmanx display stack
#dtoverlay=vc4-fkms-v3d
max_framebuffers=2
We can try re-enabling the driver by modifying the corresponding line in /boot/config.txt
back to its original value,
[pi4]
# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2
(Sidenote: the original RPi boot.config uses vc4-kms-v3d
instead of template’s older vc4-fkms-v3d
, and had those settings placed outside of the [pi4]
section. But this makes no difference, as we are on Pi 4.)
Re-enabling VC4 V3D driver and rebooting brings back blue-and-black flicker during boot, and portrait orientation in X session. So flicker-free display operation is predicated on the use of the framebuffer display driver (as apparently, V3D driver replaces that part of the firmware stack), and that precludes us from having a functional Vulkan driver.
But can we salvage the X session itself, so that we have landscape orientation, working touchscreen, and Vulkan acceleration?
Driver installation, revisited
While we could try to undo the relevant steps of the driver installation script, it is generally cleaner and more instructive to revert to the SD card backup, and start from a clean system.
First, clone the driver repository again, but do not run the installation script:
git clone https://github.com/goodtft/LCD-show
Now, we will modify the /boot/config.txt
files with the extra settings from the installation script - but without disabling V3D driver. Open /boot/config.txt
(as root) in your favorite text editor, and append the following lines at the end of the [all]
section, which should be at the end of the file, and empty:
[all]
# Options for Miuzei 4.0 inch display
# Taken from MPI4008-show script of goodtft/LCD-show
hdmi_force_hotplug=1
hdmi_force_edid_audio=1
dtparam=i2c_arm=on
dtparam=spi=on
enable_uart=1
display_rotate=3
max_usb_current=1
config_hdmi_boost=7
hdmi_group=2
hdmi_mode=1
hdmi_mode=87
hdmi_drive=2
hdmi_cvt=480 800 60 6 0 0 0
dtoverlay=ads7846,cs=1,penirq=25,penirq_pull=2,speed=50000,keep_vref_on=0,swapxy=0,pmax=255,xohms=150,xmin=200,xmax=3900,ymin=200,ymax=3900
Ensure that the xserver-xorg-input-evdev
package is installed:
sudo apt-get update
sudo apt-get install xserver-xorg-input-evdev
For touch screen to work, it seems we indeed need to make a copy of /usr/share/X11/xorg.conf.d/10-evdev.conf
file to /usr/share/X11/xorg.conf.d/45-evdev.conf
. This seems to imply that some settings are overwritten after the first file (10-evdev.conf
) is loaded, and we ensure they are re-instated by having the file re-loaded again (as 45-evdev.conf
).
Instead of copying the file, we can create a symbolic link:
sudo ln -s /usr/share/X11/xorg.conf.d/10-evdev.conf /usr/share/X11/xorg.conf.d/45-evdev.conf
Copy the touch screen calibration file (in our case for 270° rotation) to /etc/X11/xorg.conf.d/99-calibration.conf
.
If we restart the Pi now, we still end up with portrait-oriented display. To remedy that, we can rotate the display using xrandr
:
xrandr -o left
To make the rotation permanent, run arandr
utility as root:
sudo arandr
Set up the desired orientation (should already be set following the previous xrandr
command), and export the configuration by selecting Configure->Apply
. This utility writes a script called /usr/share/dispsetup.sh
that is used at X session startup to automatically rotate the display into the target position.
Profit!
After the reboot, you should end up with landscape-oriented display that is matched by the working touch screen. The boot sequence is still flickering, but we now have a Vulkan acceleration available in the X session: