diff --git a/src/backend/drm/eglstream/egl.rs b/src/backend/drm/eglstream/egl.rs index 538fb00..1d5ee20 100644 --- a/src/backend/drm/eglstream/egl.rs +++ b/src/backend/drm/eglstream/egl.rs @@ -42,6 +42,7 @@ where type Surface = EglStreamSurface; type Error = Error<<::Surface as Surface>::Error>; + // create an EGLDisplay for the EGLstream platform unsafe fn get_display( display: ffi::NativeDisplayType, 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")] unsafe impl NativeSurface for EglStreamSurface> { type Error = Error; diff --git a/src/backend/drm/eglstream/mod.rs b/src/backend/drm/eglstream/mod.rs index 780f657..9c8d320 100644 --- a/src/backend/drm/eglstream/mod.rs +++ b/src/backend/drm/eglstream/mod.rs @@ -146,6 +146,7 @@ impl EglStreamDevice { .map_err(|_| Error::DeviceIsNoEGLStreamDevice)?; } + // we can now query the amount of devices implementing the required extension let mut num_devices = 0; wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices)) .map_err(Error::FailedToEnumerateDevices)?; @@ -153,6 +154,7 @@ impl EglStreamDevice { 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); wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices)) .map_err(Error::FailedToEnumerateDevices)?; @@ -162,8 +164,10 @@ impl EglStreamDevice { devices .into_iter() .find(|device| { + // we may get devices, that are - well - NO_DEVICE... *device != ffi::egl::NO_DEVICE_EXT && { + // the device then also needs EGL_EXT_device_drm let device_extensions = { let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32); if p.is_null() { @@ -179,6 +183,14 @@ impl EglStreamDevice { 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 p = ffi::egl::QueryDeviceStringEXT( *device, @@ -201,6 +213,7 @@ impl EglStreamDevice { .ok_or(Error::DeviceIsNoEGLStreamDevice)? }; + // okay the device is compatible and found, ready to go. Ok(EglStreamDevice { dev: device, raw, diff --git a/src/backend/drm/eglstream/surface.rs b/src/backend/drm/eglstream/surface.rs index 1423c05..2c231d0 100644 --- a/src/backend/drm/eglstream/surface.rs +++ b/src/backend/drm/eglstream/surface.rs @@ -107,7 +107,7 @@ impl Drop for EglStreamSurfaceInternal { // 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. // -// 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. #[cfg(feature = "backend_drm")] impl CursorBackend for EglStreamSurfaceInternal { @@ -155,6 +155,8 @@ impl CursorBackend for EglStreamSurfaceInternal { trace!(self.logger, "Setting the new imported cursor"); + // call the drm-functions directly to bypass ::commit/::page_flip and eglstream... + if self .crtc .set_cursor2( @@ -197,6 +199,8 @@ impl EglStreamSurface { 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( &self, display: &Arc, @@ -230,6 +234,7 @@ impl EglStreamSurface { } } + // again enumerate extensions let extensions = { let p = unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) }; @@ -237,6 +242,7 @@ impl EglStreamSurface { list.split(' ').map(|e| e.to_string()).collect::>() }; + // we need quite a bunch to implement a full-blown renderer. 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_KHR_stream") @@ -315,6 +321,9 @@ impl EglStreamSurface { // 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; if unsafe { ffi::egl::GetOutputLayersEXT( @@ -354,11 +363,18 @@ impl EglStreamSurface { 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]; unsafe { 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 mut out: Vec = Vec::with_capacity(7); out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32); @@ -371,12 +387,14 @@ impl EglStreamSurface { out }; + // okay, we have a config, lets create the stream. let stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) }; if stream == ffi::egl::NO_STREAM_KHR { error!(self.0.logger, "Failed to create egl stream"); 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 { error!(self.0.logger, "Failed to link Output Layer as Stream Consumer"); return Err(Error::DeviceStreamCreationFailed); @@ -394,6 +412,7 @@ impl EglStreamSurface { _surface_attribs: &[c_int], output_attribs: &[isize], ) -> Result<*const c_void, Error<<::Surface as Surface>::Error>> { + // our surface needs a stream let stream = self.create_stream(display, output_attribs)?; let (w, h) = self.current_mode().size(); @@ -408,6 +427,8 @@ impl EglStreamSurface { 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 { ffi::egl::CreateStreamProducerSurfaceKHR( display.handle, @@ -430,7 +451,15 @@ impl EglStreamSurface { display: &Arc, surface: ffi::egl::types::EGLSurface, ) -> Result<(), SwapBuffersError::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) { + // 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 = [ ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize, Into::::into(crtc) as isize, @@ -438,6 +467,9 @@ impl EglStreamSurface { ]; 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() { wrap_egl_call(|| unsafe { ffi::egl::StreamConsumerAcquireAttribNV( @@ -450,6 +482,8 @@ impl EglStreamSurface { } else { Err(Error::StreamFlipFailed(EGLError::NotInitialized)) }; + + // so we need to unlock on success and return on failure. if res.is_ok() { self.0.locked.store(false, Ordering::SeqCst); } else { @@ -458,6 +492,7 @@ impl EglStreamSurface { } } + // 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) { wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) .map_err(SwapBuffersError::EGLSwapBuffers)?;