
DRM leasing part three (vblank)
The last couple of weeks have been consumed by getting frame sequence
numbers and events handled within the leasing environment (and Vulkan)
correctly.
Vulkan EXT_display_control extension
This little extension provides the bits necessary for applications to
track the display of frames to the user.
VkResult
vkGetSwapchainCounterEXT(VkDevice device,
VkSwapchainKHR swapchain,
VkSurfaceCounterFlagBitsEXT counter,
uint64_t *pCounterValue);
This function just retrieves the current frame count from the display
associated with swapchain.
VkResult
vkRegisterDisplayEventEXT(VkDevice device,
VkDisplayKHR display,
const VkDisplayEventInfoEXT *pDisplayEventInfo,
const VkAllocationCallbacks *pAllocator,
VkFence *pFence);
This function creates a fence that will be signaled when the specified
event happens. Right now, the only event supported is when the first
pixel of the next display refresh cycle leaves the display engine for
the display. If you want something fancier (like two frames from now),
you get to do that on your own using this basic function.
drmWaitVBlank
drmWaitVBlank is the existing interface for all things sequence
related and has three modes (always nice to have one function do three
things, I think). It can:
- Query the current vblank number
- Block until a specified vblank number
- Queue an event to be delivered at a specific vblank number
This interface has a few issues:
- It has been kludged into supporting multiple CRTCs by taking bits
from the 'type' parameter to hold a 'pipe' number, which is the
index in the kernel into the array of CRTCs.
- It has a random selection of 'int' and 'long' datatypes in the
interface, making it need special helpers for 32-bit apps running
on a 64-bit kernel.
- Times are in microseconds, frame counts are 32 bits. Vulkan does
everything in nanoseconds and wants 64-bits of frame counts.
For leases, figuring out the index into the kernel list of crtcs is
pretty tricky -- our lease has a subset of those crtcs, so we can't
actually compute the global crtc index.
drmCrtcGetSequence
int drmCrtcGetSequence(int fd, uint32_t crtcId,
uint64_t *sequence, uint64_t *ns);
Here's a simple new function hand it a crtc ID and it provides the
current frame sequence number and the time when that frame started (in
nanoseconds).
drmCrtcQueueSequence
int drmCrtcQueueSequence(int fd, uint32_t crtcId,
uint32_t flags, uint64_t sequence,
uint64_t user_data);
struct drm_event_crtc_sequence
struct drm_event base;
__u64 user_data;
__u64 time_ns;
__u64 sequence;
;
This will cause a CRTC_SEQUENCE event to be delivered at the start of
the specified frame sequence. That event will include the frame when
the event was actually generated (in case it's late), along with the
time (in nanoseconds) when that frame was started. The event also
includes a 64-bit user_data value, which can be used to hold a pointer
to whatever data the application wants to see in the event handler.
The 'flags' argument contains a combination of:
#define DRM_CRTC_SEQUENCE_RELATIVE 0x00000001 /* sequence is relative to current */
#define DRM_CRTC_SEQUENCE_NEXT_ON_MISS 0x00000002 /* Use next sequence if we've missed */
#define DRM_CRTC_SEQUENCE_FIRST_PIXEL_OUT 0x00000004 /* Signal when first pixel is displayed */
These are similar to the values provided for the drmWaitVBlank
function, except I've added a selector for when the event should be
delivered to align with potential future additions to Vulkan. Right
now, the only time you can ask for is first-pixel-out, which says that
the event should correspond to the display of the first pixel on the
screen.
DRM events Vulkan fences
With the kernel able to deliver a suitable event at the next frame,
all the Vulkan code needed was a to create a fence and hook it up to
such an event. The existing fence code only deals with rendering
fences, so I added window system interface (WSI) fencing
infrastructure and extended the radv driver to be able to handle both
kinds of fences within that code.
Multiple waiting threads
I've now got three places which can be waiting for a DRM event to
appear:
- Frame sequence fences.
- Wait for an idle image. Necessary when you want an image to draw
the next frame to.
- Wait for the previous flip to complete. The kernel can only queue
one flip at a time, so we have to make sure the previous flip is
complete before queuing another one.
Vulkan allows these to be run from separate threads, so I needed to
deal with multiple threads waiting for a specific DRM event at the
same time.
XCB has the same problem and goes to great lengths to manage this with
a set of locking and signaling primitives so that only one thread is
ever doing poll or read from the socket at time. If another thread
wants to read at the same time, it will block on a condition variable
which is then signaled by the original reader thread at the
appropriate time. It's all very complicated, and it didn't work
reliably for a number of years.
I decided to punt and just create a separate thread for processing all
DRM events. It blocks using poll(2) until some events are readable,
processes those and then broadcasts to a condition variable to notify
any waiting threads that 'something' has happened. Each waiting thread
simply checks for the desired condition and if not satisfied, blocks
on that condition variable. It's all very simple looking, and seems to
work just fine.
Code Complete, Validation Remains
At this point, all of the necessary pieces are in place for the VR
application to take advantage of an HMD using only existing Vulkan
extensions. Those will be automatically mapped into DRM leases and DRM
events as appropriate.
The VR compositor application is working pretty well; tests with Dota
2 show occasional jerky behavior in complex scenes, so there's
clearly more work to be done somewhere. I need to go write a pile of
tests to independently verify that my code is working. I wonder if I'll
need to wire up some kind of light sensor so I can actually tell when
frames get displayed as it's pretty easy to get consistent-but-wrong
answers in this environment.
Source Code
- Linux. This is based off of a reasonably current drm-next branch
from Dave Airlie. 965 commits past 4.12 RC3.
git://people.freedesktop.org/~keithp/linux drm-lease-v3
- X server (which includes xf86-video-modesetting). This is pretty
close to master.
git://people.freedesktop.org/~keithp/xserver drm-lease
- RandR protocol changes
git://people.freedesktop.org/~keithp/randrproto drm-lease
- xcb proto (no changes to libxcb sources, but it will need to be rebuilt)
git://people.freedesktop.org/~keithp/xcb/proto drm-lease
- DRM library. About a dozen patches behind master.
git://people.freedesktop.org/~keithp/drm drm-lease
- Mesa. Branched early this month (4 June), this is pretty far from
master.
git://people.freedesktop.org/~keithp/mesa drm-lease