docs: Add more explanations to the eglstream-drm-code

This commit is contained in:
Victor Brekenfeld 2020-06-05 22:16:28 +02:00
parent 978415987f
commit a3459cda31
3 changed files with 55 additions and 1 deletions

View File

@ -42,6 +42,7 @@ where
type Surface = EglStreamSurface<D>; type Surface = EglStreamSurface<D>;
type Error = Error<<<D as Device>::Surface as Surface>::Error>; type Error = Error<<<D as Device>::Surface as Surface>::Error>;
// create an EGLDisplay for the EGLstream platform
unsafe fn get_display<F>( unsafe fn get_display<F>(
display: ffi::NativeDisplayType, display: ffi::NativeDisplayType,
attribs: &[ffi::EGLint], attribs: &[ffi::EGLint],
@ -104,6 +105,11 @@ where
} }
} }
// we need either a `crtc` or a `plane` for EGLStream initializations,
// which totally breaks our abstraction.. (This is normally an implementation details of `RawSurface`-implementations).
//
// as a result, we need three implemenations for atomic, legacy and fallback...
#[cfg(feature = "backend_drm_atomic")] #[cfg(feature = "backend_drm_atomic")]
unsafe impl<A: AsRawFd + 'static> NativeSurface for EglStreamSurface<AtomicDrmDevice<A>> { unsafe impl<A: AsRawFd + 'static> NativeSurface for EglStreamSurface<AtomicDrmDevice<A>> {
type Error = Error<DrmError>; type Error = Error<DrmError>;

View File

@ -146,6 +146,7 @@ impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
.map_err(|_| Error::DeviceIsNoEGLStreamDevice)?; .map_err(|_| Error::DeviceIsNoEGLStreamDevice)?;
} }
// we can now query the amount of devices implementing the required extension
let mut num_devices = 0; let mut num_devices = 0;
wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices)) wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices))
.map_err(Error::FailedToEnumerateDevices)?; .map_err(Error::FailedToEnumerateDevices)?;
@ -153,6 +154,7 @@ impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
return Err(Error::DeviceIsNoEGLStreamDevice); return Err(Error::DeviceIsNoEGLStreamDevice);
} }
// afterwards we can allocate a buffer large enough and query the actual device (this is a common pattern in egl).
let mut devices = Vec::with_capacity(num_devices as usize); let mut devices = Vec::with_capacity(num_devices as usize);
wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices)) wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices))
.map_err(Error::FailedToEnumerateDevices)?; .map_err(Error::FailedToEnumerateDevices)?;
@ -162,8 +164,10 @@ impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
devices devices
.into_iter() .into_iter()
.find(|device| { .find(|device| {
// we may get devices, that are - well - NO_DEVICE...
*device != ffi::egl::NO_DEVICE_EXT *device != ffi::egl::NO_DEVICE_EXT
&& { && {
// the device then also needs EGL_EXT_device_drm
let device_extensions = { let device_extensions = {
let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32); let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32);
if p.is_null() { if p.is_null() {
@ -179,6 +183,14 @@ impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
device_extensions.iter().any(|s| *s == "EGL_EXT_device_drm") device_extensions.iter().any(|s| *s == "EGL_EXT_device_drm")
} }
&& { && {
// and we want to get the file descriptor to check, that we found
// the device the user wants to initialize.
//
// notice how this is kinda the other way around.
// EGL_EXT_device_query expects use to find all devices using this extension...
// But there is no way, we are going to replace our udev-interface with this, so we list devices
// just to find the id of the one, that we actually want, because we cannot
// request it directly afaik...
let path = { let path = {
let p = ffi::egl::QueryDeviceStringEXT( let p = ffi::egl::QueryDeviceStringEXT(
*device, *device,
@ -201,6 +213,7 @@ impl<D: RawDevice + ControlDevice + 'static> EglStreamDevice<D> {
.ok_or(Error::DeviceIsNoEGLStreamDevice)? .ok_or(Error::DeviceIsNoEGLStreamDevice)?
}; };
// okay the device is compatible and found, ready to go.
Ok(EglStreamDevice { Ok(EglStreamDevice {
dev: device, dev: device,
raw, raw,

View File

@ -107,7 +107,7 @@ impl<D: RawDevice + 'static> Drop for EglStreamSurfaceInternal<D> {
// That way we can use hardware cursors at least on all drm-backends (including atomic), although // That way we can use hardware cursors at least on all drm-backends (including atomic), although
// this is a little hacky. Overlay planes however are completely out of question for now. // this is a little hacky. Overlay planes however are completely out of question for now.
// //
// Note that this might still appear a little chuppy, we should just use software cursors // Note that this might still appear a little choppy, we should just use software cursors
// on eglstream devices by default and only use this, if the user really wants it. // on eglstream devices by default and only use this, if the user really wants it.
#[cfg(feature = "backend_drm")] #[cfg(feature = "backend_drm")]
impl<D: RawDevice + 'static> CursorBackend for EglStreamSurfaceInternal<D> { impl<D: RawDevice + 'static> CursorBackend for EglStreamSurfaceInternal<D> {
@ -155,6 +155,8 @@ impl<D: RawDevice + 'static> CursorBackend for EglStreamSurfaceInternal<D> {
trace!(self.logger, "Setting the new imported cursor"); trace!(self.logger, "Setting the new imported cursor");
// call the drm-functions directly to bypass ::commit/::page_flip and eglstream...
if self if self
.crtc .crtc
.set_cursor2( .set_cursor2(
@ -197,6 +199,8 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
self.0.crtc.commit_pending() || self.0.stream.borrow().is_none() self.0.crtc.commit_pending() || self.0.stream.borrow().is_none()
} }
// An EGLStream is basically the pump that requests and consumes images to display them.
// The heart of this weird api. Any changes to its configuration, require a re-creation.
pub(super) fn create_stream( pub(super) fn create_stream(
&self, &self,
display: &Arc<EGLDisplayHandle>, display: &Arc<EGLDisplayHandle>,
@ -230,6 +234,7 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
} }
} }
// again enumerate extensions
let extensions = { let extensions = {
let p = let p =
unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) }; unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) };
@ -237,6 +242,7 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>() list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
}; };
// we need quite a bunch to implement a full-blown renderer.
if !extensions.iter().any(|s| *s == "EGL_EXT_output_base") if !extensions.iter().any(|s| *s == "EGL_EXT_output_base")
|| !extensions.iter().any(|s| *s == "EGL_EXT_output_drm") || !extensions.iter().any(|s| *s == "EGL_EXT_output_drm")
|| !extensions.iter().any(|s| *s == "EGL_KHR_stream") || !extensions.iter().any(|s| *s == "EGL_KHR_stream")
@ -315,6 +321,9 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
// TEST END // TEST END
} }
// alright, if the surface appears to be supported, we need an "output layer".
// this is basically just a fancy name for a `crtc` or a `plane`.
// those are exactly whats inside the `output_attribs` depending on underlying device.
let mut num_layers = 0; let mut num_layers = 0;
if unsafe { if unsafe {
ffi::egl::GetOutputLayersEXT( ffi::egl::GetOutputLayersEXT(
@ -354,11 +363,18 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
layers.set_len(num_layers as usize); layers.set_len(num_layers as usize);
} }
// lets just use the first layer and try to set the swap interval.
// this is needed to make sure `eglSwapBuffers` does not block.
let layer = layers[0]; let layer = layers[0];
unsafe { unsafe {
ffi::egl::OutputLayerAttribEXT(display.handle, layer, ffi::egl::SWAP_INTERVAL_EXT as i32, 0); ffi::egl::OutputLayerAttribEXT(display.handle, layer, ffi::egl::SWAP_INTERVAL_EXT as i32, 0);
} }
// The stream needs to know, it needs to request frames
// as soon as another one is rendered (we do not want to build a buffer and delay frames),
// which is handled by STREAM_FIFO_LENGTH_KHR = 0.
// We also want to "acquire" the frames manually. Like this we can request page-flip events
// to drive our event loop. Otherwise we would have no way to know rendering is finished.
let stream_attributes = { let stream_attributes = {
let mut out: Vec<c_int> = Vec::with_capacity(7); let mut out: Vec<c_int> = Vec::with_capacity(7);
out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32); out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32);
@ -371,12 +387,14 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
out out
}; };
// okay, we have a config, lets create the stream.
let stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) }; let stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) };
if stream == ffi::egl::NO_STREAM_KHR { if stream == ffi::egl::NO_STREAM_KHR {
error!(self.0.logger, "Failed to create egl stream"); error!(self.0.logger, "Failed to create egl stream");
return Err(Error::DeviceStreamCreationFailed); return Err(Error::DeviceStreamCreationFailed);
} }
// we have a stream, lets connect it to our output layer
if unsafe { ffi::egl::StreamConsumerOutputEXT(display.handle, stream, layer) } == 0 { if unsafe { ffi::egl::StreamConsumerOutputEXT(display.handle, stream, layer) } == 0 {
error!(self.0.logger, "Failed to link Output Layer as Stream Consumer"); error!(self.0.logger, "Failed to link Output Layer as Stream Consumer");
return Err(Error::DeviceStreamCreationFailed); return Err(Error::DeviceStreamCreationFailed);
@ -394,6 +412,7 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
_surface_attribs: &[c_int], _surface_attribs: &[c_int],
output_attribs: &[isize], output_attribs: &[isize],
) -> Result<*const c_void, Error<<<D as Device>::Surface as Surface>::Error>> { ) -> Result<*const c_void, Error<<<D as Device>::Surface as Surface>::Error>> {
// our surface needs a stream
let stream = self.create_stream(display, output_attribs)?; let stream = self.create_stream(display, output_attribs)?;
let (w, h) = self.current_mode().size(); let (w, h) = self.current_mode().size();
@ -408,6 +427,8 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
out out
}; };
// the stream is already connected to the consumer (output layer) during creation.
// we now connect the producer (out egl surface, that we render to).
let surface = unsafe { let surface = unsafe {
ffi::egl::CreateStreamProducerSurfaceKHR( ffi::egl::CreateStreamProducerSurfaceKHR(
display.handle, display.handle,
@ -430,7 +451,15 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
display: &Arc<EGLDisplayHandle>, display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface, surface: ffi::egl::types::EGLSurface,
) -> Result<(), SwapBuffersError<Error<<<D as Device>::Surface as Surface>::Error>>> { ) -> Result<(), SwapBuffersError<Error<<<D as Device>::Surface as Surface>::Error>>> {
// if we have already swapped the buffer successfully, we need to free it again.
//
// we need to do this here, because the call may fail (compare this with gbm's unlock_buffer...).
// if it fails we do not want to swap, because that would block and as a result deadlock us.
if self.0.locked.load(Ordering::SeqCst) { if self.0.locked.load(Ordering::SeqCst) {
// which means in eglstream terms: "acquire it".
// here we set the user data of the page_flip event
// (which is also only triggered if we manually acquire frames).
// This is the crtc id like always to get the matching surface for the device handler later.
let acquire_attributes = [ let acquire_attributes = [
ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize, ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize,
Into::<u32>::into(crtc) as isize, Into::<u32>::into(crtc) as isize,
@ -438,6 +467,9 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
]; ];
if let Ok(stream) = self.0.stream.try_borrow() { if let Ok(stream) = self.0.stream.try_borrow() {
// lets try to acquire the frame.
// this may fail, if the buffer is still in use by the gpu,
// e.g. the flip was not done yet. In this case this call fails as `BUSY`.
let res = if let Some(&(ref display, ref stream)) = stream.as_ref() { let res = if let Some(&(ref display, ref stream)) = stream.as_ref() {
wrap_egl_call(|| unsafe { wrap_egl_call(|| unsafe {
ffi::egl::StreamConsumerAcquireAttribNV( ffi::egl::StreamConsumerAcquireAttribNV(
@ -450,6 +482,8 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
} else { } else {
Err(Error::StreamFlipFailed(EGLError::NotInitialized)) Err(Error::StreamFlipFailed(EGLError::NotInitialized))
}; };
// so we need to unlock on success and return on failure.
if res.is_ok() { if res.is_ok() {
self.0.locked.store(false, Ordering::SeqCst); self.0.locked.store(false, Ordering::SeqCst);
} else { } else {
@ -458,6 +492,7 @@ impl<D: RawDevice + 'static> EglStreamSurface<D> {
} }
} }
// so if we are not locked any more we can send the next frame by calling swap buffers.
if !self.0.locked.load(Ordering::SeqCst) { if !self.0.locked.load(Ordering::SeqCst) {
wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) })
.map_err(SwapBuffersError::EGLSwapBuffers)?; .map_err(SwapBuffersError::EGLSwapBuffers)?;