docs: Add more explanations to the eglstream-drm-code
This commit is contained in:
parent
978415987f
commit
a3459cda31
|
@ -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>;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
Loading…
Reference in New Issue