1
0
Fork 0
forked from wry/wry

metal: support multi-gpu systems via copying

This commit is contained in:
Julian Orth 2022-11-13 21:07:32 +01:00
parent d43e7b345c
commit 28e91e0c5d
4 changed files with 167 additions and 80 deletions

View file

@ -9,7 +9,7 @@ use {
edid::Descriptor, edid::Descriptor,
format::{Format, ARGB8888, XRGB8888}, format::{Format, ARGB8888, XRGB8888},
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC},
render::{Framebuffer, RenderContext, RenderResult, ResetStatus, Texture}, render::{Framebuffer, RenderContext, RenderResult, Texture},
state::State, state::State,
udev::UdevDevice, udev::UdevDevice,
utils::{ utils::{
@ -48,8 +48,9 @@ pub struct PendingDrmDevice {
pub devnode: CString, pub devnode: CString,
} }
#[derive(Debug)]
pub struct MetalRenderContext { pub struct MetalRenderContext {
pub dev: Rc<MetalDrmDevice>, pub dev_id: DrmDeviceId,
pub egl: Rc<RenderContext>, pub egl: Rc<RenderContext>,
} }
@ -71,6 +72,7 @@ pub struct MetalDrmDevice {
pub cursor_height: u64, pub cursor_height: u64,
pub gbm: GbmDevice, pub gbm: GbmDevice,
pub handle_events: HandleEvents, pub handle_events: HandleEvents,
pub ctx: Rc<MetalRenderContext>,
} }
impl BackendDrmDevice for MetalDrmDevice { impl BackendDrmDevice for MetalDrmDevice {
@ -213,7 +215,7 @@ impl HardwareCursor for MetalHardwareCursor {
fn get_buffer(&self) -> Rc<Framebuffer> { fn get_buffer(&self) -> Rc<Framebuffer> {
let buffer = (self.connector.cursor_front_buffer.get() + 1) % 2; let buffer = (self.connector.cursor_front_buffer.get() + 1) % 2;
self.cursor_buffers[buffer].fb.clone() self.cursor_buffers[buffer].render_fb()
} }
fn set_position(&self, x: i32, y: i32) { fn set_position(&self, x: i32, y: i32) {
@ -349,13 +351,14 @@ impl MetalConnector {
let cursor = self.cursor_plane.get(); let cursor = self.cursor_plane.get();
let mut changes = self.master.change(); let mut changes = self.master.change();
if self.has_damage.get() { if self.has_damage.get() {
if !self.backend.check_render_context() { if !self.backend.check_render_context(&self.dev) {
return; return;
} }
let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()];
if let Some(node) = self.state.root.outputs.get(&self.connector_id) { if let Some(node) = self.state.root.outputs.get(&self.connector_id) {
let mut rr = self.render_result.borrow_mut(); let mut rr = self.render_result.borrow_mut();
buffer.fb.render( let render_fb = buffer.render_fb();
render_fb.render(
&*node, &*node,
&self.state, &self.state,
Some(node.global.pos.get()), Some(node.global.pos.get()),
@ -364,11 +367,14 @@ impl MetalConnector {
node.preferred_scale.get(), node.preferred_scale.get(),
!self.cursor_enabled.get(), !self.cursor_enabled.get(),
); );
if let Some(tex) = &buffer.dev_tex {
buffer.dev_fb.copy_texture(&self.state, tex, 0, 0, false);
}
for fr in rr.frame_requests.drain(..) { for fr in rr.frame_requests.drain(..) {
fr.send_done(); fr.send_done();
let _ = fr.client.remove_obj(&*fr); let _ = fr.client.remove_obj(&*fr);
} }
node.perform_screencopies(&buffer.fb, &buffer.tex); node.perform_screencopies(&render_fb, &buffer.render_tex);
} }
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _); c.change(plane.fb_id, buffer.drm.id().0 as _);
@ -377,22 +383,28 @@ impl MetalConnector {
if self.cursor_changed.get() && cursor.is_some() { if self.cursor_changed.get() && cursor.is_some() {
let plane = cursor.unwrap(); let plane = cursor.unwrap();
if self.cursor_enabled.get() { if self.cursor_enabled.get() {
if self.cursor_swap_buffer.take() { let swap_buffer = self.cursor_swap_buffer.take();
if swap_buffer {
self.cursor_front_buffer.fetch_add(1); self.cursor_front_buffer.fetch_add(1);
} }
let buffers = self.cursor_buffers.get().unwrap(); let buffers = self.cursor_buffers.get().unwrap();
let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()]; let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()];
if swap_buffer {
if let Some(tex) = &buffer.dev_tex {
buffer.dev_fb.copy_texture(&self.state, tex, 0, 0, true);
}
}
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _); c.change(plane.fb_id, buffer.drm.id().0 as _);
c.change(plane.crtc_id.id, crtc.id.0 as _); c.change(plane.crtc_id.id, crtc.id.0 as _);
c.change(plane.crtc_x.id, self.cursor_x.get() as _); c.change(plane.crtc_x.id, self.cursor_x.get() as _);
c.change(plane.crtc_y.id, self.cursor_y.get() as _); c.change(plane.crtc_y.id, self.cursor_y.get() as _);
c.change(plane.crtc_w.id, buffer.tex.width() as _); c.change(plane.crtc_w.id, buffer.render_tex.width() as _);
c.change(plane.crtc_h.id, buffer.tex.height() as _); c.change(plane.crtc_h.id, buffer.render_tex.height() as _);
c.change(plane.src_x.id, 0); c.change(plane.src_x.id, 0);
c.change(plane.src_y.id, 0); c.change(plane.src_y.id, 0);
c.change(plane.src_w.id, (buffer.tex.width() as u64) << 16); c.change(plane.src_w.id, (buffer.render_tex.width() as u64) << 16);
c.change(plane.src_h.id, (buffer.tex.height() as u64) << 16); c.change(plane.src_h.id, (buffer.render_tex.height() as u64) << 16);
}); });
} else { } else {
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
@ -864,59 +876,53 @@ struct Preserve {
} }
impl MetalBackend { impl MetalBackend {
fn check_render_context(&self) -> bool { fn check_render_context(&self, dev: &Rc<MetalDrmDevice>) -> bool {
let ctx = match self.ctx.get() { let ctx = match self.ctx.get() {
Some(ctx) => ctx, Some(ctx) => ctx,
None => return false, None => return false,
}; };
let reset = match ctx.egl.reset_status() { if let Some(r) = ctx
Some(r) => r, .egl
None => return true, .reset_status()
}; .or_else(|| dev.ctx.egl.reset_status())
log::error!("EGL context has been reset: {:?}", reset); {
if reset != ResetStatus::Innocent { fatal!("EGL context has been reset: {:?}", r);
fatal!("We are not innocent. Terminating.");
} }
log::info!("Trying to create a new context");
self.ctx.set(None);
self.state.set_render_ctx(None);
let mut old_buffers = vec![];
for dev in self.device_holder.drm_devices.lock().values() {
for connector in dev.connectors.lock().values() {
old_buffers.push(connector.buffers.take());
}
}
self.make_render_device(&ctx.dev, true)
}
fn install_render_context(&self, dev: &Rc<MetalDrmDevice>) -> bool {
let ctx = match self.create_render_context(dev) {
Ok(ctx) => ctx,
Err(e) => {
log::error!("Could not create a render context: {}", ErrorFmt(e));
return false;
}
};
self.state.set_render_ctx(Some(&ctx.egl));
self.ctx.set(Some(ctx));
true true
} }
fn create_render_context( // fn check_render_context(&self) -> bool {
&self, // let ctx = match self.ctx.get() {
dev: &Rc<MetalDrmDevice>, // Some(ctx) => ctx,
) -> Result<Rc<MetalRenderContext>, MetalError> { // None => return false,
let egl = match RenderContext::from_drm_device(&dev.master) { // };
Ok(r) => Rc::new(r), // let reset = match ctx.egl.reset_status() {
Err(e) => return Err(MetalError::CreateRenderContex(e)), // Some(r) => r,
}; // None => return true,
let ctx = Rc::new(MetalRenderContext { // };
dev: dev.clone(), // log::error!("EGL context has been reset: {:?}", reset);
egl, // if reset != ResetStatus::Innocent {
}); // fatal!("We are not innocent. Terminating.");
self.ctx.set(Some(ctx.clone())); // }
Ok(ctx) // log::info!("Trying to create a new context");
} // self.ctx.set(None);
// self.state.set_render_ctx(None);
// let mut old_buffers = vec![];
// let mut ctx_dev = None;
// for dev in self.device_holder.drm_devices.lock().values() {
// if dev.dev.id == ctx.dev_id {
// ctx_dev = Some(dev.dev.clone());
// }
// for connector in dev.connectors.lock().values() {
// old_buffers.push(connector.buffers.take());
// }
// }
// if let Some(dev) = &ctx_dev {
// self.make_render_device(dev, true)
// } else {
// false
// }
// }
pub fn handle_drm_change(self: &Rc<Self>, dev: UdevDevice) -> Option<()> { pub fn handle_drm_change(self: &Rc<Self>, dev: UdevDevice) -> Option<()> {
let dev = match self.device_holder.drm_devices.get(&dev.devnum()) { let dev = match self.device_holder.drm_devices.get(&dev.devnum()) {
@ -1083,6 +1089,15 @@ impl MetalBackend {
} }
} }
let egl = match RenderContext::from_drm_device(master) {
Ok(r) => Rc::new(r),
Err(e) => return Err(MetalError::CreateRenderContex(e)),
};
let ctx = Rc::new(MetalRenderContext {
dev_id: pending.id,
egl,
});
let gbm = match GbmDevice::new(master) { let gbm = match GbmDevice::new(master) {
Ok(g) => g, Ok(g) => g,
Err(e) => return Err(MetalError::GbmDevice(e)), Err(e) => return Err(MetalError::GbmDevice(e)),
@ -1107,6 +1122,7 @@ impl MetalBackend {
handle_events: HandleEvents { handle_events: HandleEvents {
handle_events: Cell::new(None), handle_events: Cell::new(None),
}, },
ctx,
}); });
let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?;
@ -1406,13 +1422,12 @@ impl MetalBackend {
fn make_render_device(&self, dev: &Rc<MetalDrmDevice>, log: bool) -> bool { fn make_render_device(&self, dev: &Rc<MetalDrmDevice>, log: bool) -> bool {
if let Some(ctx) = self.ctx.get() { if let Some(ctx) = self.ctx.get() {
if ctx.dev.id == dev.id { if ctx.dev_id == dev.id {
return true; return true;
} }
} }
if !self.install_render_context(dev) { self.state.set_render_ctx(Some(&dev.ctx.egl));
return false; self.ctx.set(Some(dev.ctx.clone()));
}
let mut preserve = Preserve::default(); let mut preserve = Preserve::default();
for dev in self.device_holder.drm_devices.lock().values() { for dev in self.device_holder.drm_devices.lock().values() {
if let Err(e) = self.init_drm_device(dev, &mut preserve) { if let Err(e) = self.init_drm_device(dev, &mut preserve) {
@ -1575,39 +1590,77 @@ impl MetalBackend {
format: &ModifiedFormat, format: &ModifiedFormat,
width: i32, width: i32,
height: i32, height: i32,
ctx: &MetalRenderContext, render_ctx: &MetalRenderContext,
cursor: bool, cursor: bool,
) -> Result<RenderBuffer, MetalError> { ) -> Result<RenderBuffer, MetalError> {
let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT;
if cursor || ctx.dev.id != dev.id { if cursor {
usage |= GBM_BO_USE_LINEAR; usage |= GBM_BO_USE_LINEAR;
}; };
let bo = dev.gbm.create_bo(width, height, format, usage); let dev_bo = dev.gbm.create_bo(width, height, format, usage);
let bo = match bo { let dev_bo = match dev_bo {
Ok(b) => b, Ok(b) => b,
Err(e) => return Err(MetalError::ScanoutBuffer(e)), Err(e) => return Err(MetalError::ScanoutBuffer(e)),
}; };
let drm_fb = match dev.master.add_fb(bo.dmabuf()) { let drm_fb = match dev.master.add_fb(dev_bo.dmabuf()) {
Ok(fb) => Rc::new(fb), Ok(fb) => Rc::new(fb),
Err(e) => return Err(MetalError::Framebuffer(e)), Err(e) => return Err(MetalError::Framebuffer(e)),
}; };
let egl_img = match ctx.egl.dmabuf_img(bo.dmabuf()) { let dev_img = match dev.ctx.egl.dmabuf_img(dev_bo.dmabuf()) {
Ok(img) => img, Ok(img) => img,
Err(e) => return Err(MetalError::ImportImage(e)), Err(e) => return Err(MetalError::ImportImage(e)),
}; };
let egl_fb = match egl_img.to_framebuffer() { let dev_fb = match dev_img.to_framebuffer() {
Ok(fb) => fb, Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportFb(e)), Err(e) => return Err(MetalError::ImportFb(e)),
}; };
let egl_tex = match egl_img.to_texture() { dev_fb.clear();
Ok(fb) => fb, let (dev_tex, render_tex, render_fb) = if dev.id == render_ctx.dev_id {
Err(e) => return Err(MetalError::ImportTexture(e)), let render_tex = match dev_img.to_texture() {
Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportTexture(e)),
};
(None, render_tex, None)
} else {
// Create a _bridge_ BO in the render device
usage |= GBM_BO_USE_LINEAR;
let render_bo = dev.gbm.create_bo(width, height, format, usage);
let render_bo = match render_bo {
Ok(b) => b,
Err(e) => return Err(MetalError::ScanoutBuffer(e)),
};
let render_img = match render_ctx.egl.dmabuf_img(render_bo.dmabuf()) {
Ok(img) => img,
Err(e) => return Err(MetalError::ImportImage(e)),
};
let render_fb = match render_img.to_framebuffer() {
Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportFb(e)),
};
render_fb.clear();
let render_tex = match render_img.to_texture() {
Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportTexture(e)),
};
// Import the bridge BO into the current device
let dev_img = match dev.ctx.egl.dmabuf_img(render_bo.dmabuf()) {
Ok(img) => img,
Err(e) => return Err(MetalError::ImportImage(e)),
};
let dev_tex = match dev_img.to_texture() {
Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportTexture(e)),
};
(Some(dev_tex), render_tex, Some(render_fb))
}; };
egl_fb.clear();
Ok(RenderBuffer { Ok(RenderBuffer {
drm: drm_fb, drm: drm_fb,
fb: egl_fb, dev_fb,
tex: egl_tex, dev_tex,
render_tex,
render_fb,
}) })
} }
@ -1783,8 +1836,26 @@ impl MetalBackend {
#[derive(Debug)] #[derive(Debug)]
pub struct RenderBuffer { pub struct RenderBuffer {
drm: Rc<DrmFramebuffer>, drm: Rc<DrmFramebuffer>,
fb: Rc<Framebuffer>, // ctx = dev
tex: Rc<Texture>, // buffer location = dev
dev_fb: Rc<Framebuffer>,
// ctx = dev
// buffer location = render
dev_tex: Option<Rc<Texture>>,
// ctx = render
// buffer location = render
render_tex: Rc<Texture>,
// ctx = render
// buffer location = render
render_fb: Option<Rc<Framebuffer>>,
}
impl RenderBuffer {
fn render_fb(&self) -> Rc<Framebuffer> {
self.render_fb
.clone()
.unwrap_or_else(|| self.dev_fb.clone())
}
} }
fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool {

View file

@ -163,7 +163,9 @@ impl JayScreencast {
let mut buffer = self.buffers.borrow_mut(); let mut buffer = self.buffers.borrow_mut();
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() { for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
if buffer.free { if buffer.free {
buffer.fb.copy_texture(&self.client.state, texture, 0, 0); buffer
.fb
.copy_texture(&self.client.state, texture, 0, 0, false);
self.client.event(Ready { self.client.event(Ready {
self_id: self.id, self_id: self.id,
idx: idx as _, idx: idx as _,

View file

@ -241,7 +241,13 @@ impl WlOutputGlobal {
continue; continue;
} }
}; };
fb.copy_texture(&self.state, tex, -capture.rect.x1(), -capture.rect.y1()); fb.copy_texture(
&self.state,
tex,
-capture.rect.x1(),
-capture.rect.y1(),
false,
);
} }
if capture.with_damage.get() { if capture.with_damage.get() {
capture.send_damage(); capture.send_damage();

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
cursor::Cursor, cursor::Cursor,
fixed::Fixed, fixed::Fixed,
format::{Format, XRGB8888}, format::{Format, ARGB8888, XRGB8888},
rect::Rect, rect::Rect,
render::{ render::{
gl::{ gl::{
@ -55,11 +55,15 @@ impl Framebuffer {
}); });
} }
pub fn copy_texture(&self, state: &State, texture: &Texture, x: i32, y: i32) { pub fn copy_texture(&self, state: &State, texture: &Texture, x: i32, y: i32, alpha: bool) {
let _ = self.ctx.ctx.with_current(|| { let _ = self.ctx.ctx.with_current(|| {
unsafe { unsafe {
glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo);
glViewport(0, 0, self.gl.width, self.gl.height); glViewport(0, 0, self.gl.width, self.gl.height);
if alpha {
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
}
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} }
let scale = Scale::from_int(1); let scale = Scale::from_int(1);
@ -76,9 +80,13 @@ impl Framebuffer {
result: &mut RenderResult::default(), result: &mut RenderResult::default(),
logical_extents: Rect::new_sized(0, 0, self.gl.width, self.gl.height).unwrap(), logical_extents: Rect::new_sized(0, 0, self.gl.width, self.gl.height).unwrap(),
}; };
let format = match alpha {
true => ARGB8888,
false => XRGB8888,
};
renderer renderer
.base .base
.render_texture(texture, x, y, XRGB8888, None, None, scale); .render_texture(texture, x, y, format, None, None, scale);
unsafe { unsafe {
glFlush(); glFlush();
} }