diff --git a/src/async_engine.rs b/src/async_engine.rs index ca118545..e936dd46 100644 --- a/src/async_engine.rs +++ b/src/async_engine.rs @@ -35,6 +35,8 @@ pub struct AsyncEngine { yield_stash: RefCell>, stopped: Cell, now: Cell>, + #[cfg(feature = "it")] + idle: Cell>, } impl AsyncEngine { @@ -48,6 +50,8 @@ impl AsyncEngine { yield_stash: Default::default(), stopped: Cell::new(false), now: Default::default(), + #[cfg(feature = "it")] + idle: Default::default(), }) } @@ -91,7 +95,15 @@ impl AsyncEngine { pub fn dispatch(&self) { let mut stash = self.stash.borrow_mut(); let mut yield_stash = self.yield_stash.borrow_mut(); - while self.num_queued.get() > 0 { + loop { + if self.num_queued.get() == 0 { + #[cfg(feature = "it")] + if let Some(idle) = self.idle.take() { + idle.wake(); + continue; + } + break; + } self.now.take(); self.iteration.fetch_add(1); let mut phase = 0; @@ -116,6 +128,22 @@ impl AsyncEngine { } } + #[cfg(feature = "it")] + pub async fn idle(&self) { + use std::{future::poll_fn, task::Poll}; + let mut register = true; + poll_fn(|ctx| { + if register { + self.idle.set(Some(ctx.waker().clone())); + register = false; + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await + } + fn push(&self, runnable: Runnable, phase: Phase) { self.queues[phase as usize].push(runnable); self.num_queued.fetch_add(1); diff --git a/src/clientmem.rs b/src/clientmem.rs index 1255ee22..1f763a8d 100644 --- a/src/clientmem.rs +++ b/src/clientmem.rs @@ -2,11 +2,14 @@ use { crate::{ client::Client, cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}, + gfx_api::{ShmMemory, ShmMemoryBacking}, utils::vec_ext::VecExt, }, std::{ cell::Cell, + error::Error, mem::{ManuallyDrop, MaybeUninit}, + ops::Deref, ptr, rc::Rc, sync::atomic::{compiler_fence, Ordering}, @@ -105,6 +108,7 @@ impl ClientMem { } } + #[expect(dead_code)] pub fn fd(&self) -> &Rc { &self.fd } @@ -115,14 +119,17 @@ impl ClientMem { } impl ClientMemOffset { + #[expect(dead_code)] pub fn pool(&self) -> &ClientMem { &self.mem } + #[expect(dead_code)] pub fn offset(&self) -> usize { self.offset } + #[expect(dead_code)] pub fn ptr(&self) -> *const [Cell] { self.data } @@ -263,3 +270,20 @@ impl CpuWork for CloseMemWork { None } } + +impl ShmMemory for ClientMemOffset { + fn len(&self) -> usize { + self.data.len() + } + + fn safe_access(&self) -> ShmMemoryBacking { + match self.mem.sigbus_impossible() { + true => ShmMemoryBacking::Ptr(self.data), + false => ShmMemoryBacking::Fd(self.mem.fd.deref().clone(), self.offset), + } + } + + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { + self.access(f).map_err(|e| e.into()) + } +} diff --git a/src/compositor.rs b/src/compositor.rs index c3bc5fc4..193024d4 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -35,9 +35,9 @@ use { tasks::{self, idle}, tracy::enable_profiler, tree::{ - container_layout, container_render_data, float_layout, float_titles, - output_render_data, DisplayNode, NodeIds, OutputNode, TearingMode, VrrMode, - WorkspaceNode, + container_layout, container_render_positions, container_render_titles, float_layout, + float_titles, output_render_data, placeholder_render_textures, DisplayNode, NodeIds, + OutputNode, TearingMode, VrrMode, WorkspaceNode, }, user_session::import_environment, utils::{ @@ -180,13 +180,15 @@ fn start_compositor2( input_device_handlers: Default::default(), theme: Default::default(), pending_container_layout: Default::default(), - pending_container_render_data: Default::default(), + pending_container_render_positions: Default::default(), + pending_container_render_title: Default::default(), pending_output_render_data: Default::default(), pending_float_layout: Default::default(), pending_float_titles: Default::default(), pending_input_popup_positioning: Default::default(), pending_toplevel_screencasts: Default::default(), pending_screencast_reallocs_or_reconfigures: Default::default(), + pending_placeholder_render_textures: Default::default(), dbus: Dbus::new(&engine, &ring, &run_toplevel), fdcloser: FdCloser::new(), logger: logger.clone(), @@ -374,9 +376,19 @@ fn start_global_event_handlers( container_layout(state.clone()), ), eng.spawn2( - "container render", + "container render positions", Phase::PostLayout, - container_render_data(state.clone()), + container_render_positions(state.clone()), + ), + eng.spawn2( + "container titles", + Phase::PostLayout, + container_render_titles(state.clone()), + ), + eng.spawn2( + "placeholder textures", + Phase::PostLayout, + placeholder_render_textures(state.clone()), ), eng.spawn2( "output render", @@ -577,7 +589,7 @@ fn create_dummy_output(state: &Rc) { jay_workspaces: Default::default(), may_capture: Cell::new(false), has_capture: Cell::new(false), - title_texture: Cell::new(None), + title_texture: Default::default(), attention_requests: Default::default(), render_highlight: Default::default(), }); diff --git a/src/config/handler.rs b/src/config/handler.rs index 418d10c6..0477b79b 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -13,7 +13,7 @@ use { output_schedule::map_cursor_hz, scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, - theme::{Color, ThemeSized, DEFAULT_FONT}, + theme::{Color, ThemeSized}, tree::{ move_ws_to_output, ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode, TearingMode, VrrMode, WsMoveConfig, @@ -57,7 +57,7 @@ use { }, libloading::Library, log::Level, - std::{cell::Cell, ops::Deref, rc::Rc, time::Duration}, + std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::{c, fcntl_dupfd_cloexec, OwnedFd}, }; @@ -1525,15 +1525,18 @@ impl ConfigProxyHandler { } fn handle_reset_font(&self) { - *self.state.theme.font.borrow_mut() = DEFAULT_FONT.to_string(); + self.state + .theme + .font + .set(self.state.theme.default_font.clone()); } fn handle_set_font(&self, font: &str) { - *self.state.theme.font.borrow_mut() = font.to_string(); + self.state.theme.font.set(Arc::new(font.to_string())); } fn handle_get_font(&self) { - let font = self.state.theme.font.borrow_mut().clone(); + let font = self.state.theme.font.get().to_string(); self.respond(Response::GetFont { font }); } diff --git a/src/cpu_worker.rs b/src/cpu_worker.rs index a17888b7..fb88dfb4 100644 --- a/src/cpu_worker.rs +++ b/src/cpu_worker.rs @@ -11,7 +11,7 @@ use { ptr_ext::MutPtrExt, queue::AsyncQueue, stack::Stack, }, }, - parking_lot::Mutex, + parking_lot::{Condvar, Mutex}, std::{ any::Any, cell::{Cell, RefCell}, @@ -113,18 +113,25 @@ enum Job { unsafe impl Send for Job {} +#[derive(Default)] +struct CompletedJobsExchange { + queue: VecDeque, + condvar: Option>, +} + struct CpuWorkerData { next: CpuJobIds, jobs_to_enqueue: AsyncQueue, new_jobs: Arc>>, have_new_jobs: Rc, - completed_jobs_remote: Arc>>, + completed_jobs_remote: Arc>, completed_jobs_local: RefCell>, have_completed_jobs: Rc, pending_jobs: CopyHashMap>, ring: Rc, _stop: OwnedFd, pending_job_data_cache: Stack>, + sync_wake_condvar: Arc, } linear_ids!(CpuJobIds, CpuJobId, u64); @@ -172,12 +179,16 @@ impl Drop for PendingJob { self.job_data.state.set(PendingJobState::Abandoned); data.jobs_to_enqueue.push(Job::Cancel { id }); data.do_equeue_jobs(); - let mut buf = 0u64; - while data.pending_jobs.contains(&id) { - if let Err(e) = uapi::read(data.have_completed_jobs.raw(), &mut buf) { - panic!("Could not wait for job completions: {}", ErrorFmt(e)); - } + loop { data.dispatch_completions(); + if !data.pending_jobs.contains(&id) { + break; + } + let mut remote = data.completed_jobs_remote.lock(); + while remote.queue.is_empty() { + remote.condvar = Some(data.sync_wake_condvar.clone()); + data.sync_wake_condvar.wait(&mut remote); + } } } PendingJobState::Abandoned => {} @@ -204,7 +215,7 @@ impl CpuWorkerData { fn dispatch_completions(&self) { let completions = &mut *self.completed_jobs_local.borrow_mut(); - mem::swap(completions, &mut *self.completed_jobs_remote.lock()); + mem::swap(completions, &mut self.completed_jobs_remote.lock().queue); while let Some(id) = completions.pop_front() { let job_data = self.pending_jobs.remove(&id).unwrap(); let job = job_data.job.take().unwrap(); @@ -242,7 +253,7 @@ impl CpuWorkerData { impl CpuWorker { pub fn new(ring: &Rc, eng: &Rc) -> Result { let new_jobs: Arc>> = Default::default(); - let completed_jobs: Arc>> = Default::default(); + let completed_jobs: Arc> = Default::default(); let (stop_read, stop_write) = uapi::pipe2(c::O_CLOEXEC).map_err(|e| CpuWorkerError::Pipe(e.into()))?; let have_new_jobs = @@ -281,6 +292,7 @@ impl CpuWorker { ring: ring.clone(), _stop: stop_read, pending_job_data_cache: Default::default(), + sync_wake_condvar: Arc::new(Condvar::new()), }); Ok(Self { _completions_listener: eng.spawn( @@ -309,11 +321,28 @@ impl CpuWorker { job_data, } } + + #[cfg(feature = "it")] + pub fn wait_idle(&self) -> bool { + let was_idle = self.data.pending_jobs.is_empty(); + loop { + self.data.dispatch_completions(); + if self.data.pending_jobs.is_empty() { + break; + } + let mut remote = self.data.completed_jobs_remote.lock(); + while remote.queue.is_empty() { + remote.condvar = Some(self.data.sync_wake_condvar.clone()); + self.data.sync_wake_condvar.wait(&mut remote); + } + } + was_idle + } } fn work( new_jobs: Arc>>, - completed_jobs: Arc>>, + completed_jobs: Arc>, stop: OwnedFd, have_new_jobs: OwnedFd, have_completed_jobs: OwnedFd, @@ -343,7 +372,7 @@ fn work( struct Worker { eng: Rc, ring: Rc, - completed_jobs: Arc>>, + completed_jobs: Arc>, have_completed_jobs: OwnedFd, async_jobs: CopyHashMap, stopped: Cell, @@ -428,7 +457,14 @@ impl Worker { } fn send_completion(&self, id: CpuJobId) { - self.completed_jobs.lock().push_back(id); + let cv = { + let mut exchange = self.completed_jobs.lock(); + exchange.queue.push_back(id); + exchange.condvar.take() + }; + if let Some(cv) = cv { + cv.notify_all(); + } if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) { panic!("Could not signal job completion: {}", ErrorFmt(e)); } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 9fe14ef3..825a4a42 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,7 +1,6 @@ use { crate::{ allocator::Allocator, - clientmem::ClientMemOffset, cpu_worker::CpuWorker, cursor::Cursor, damage::DamageVisualizer, @@ -513,11 +512,37 @@ pub struct PendingShmUpload { id: u64, } +pub trait ShmMemory { + fn len(&self) -> usize; + fn safe_access(&self) -> ShmMemoryBacking; + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box>; +} + +pub enum ShmMemoryBacking { + Ptr(*const [Cell]), + Fd(Rc, usize), +} + +impl ShmMemory for Vec> { + fn len(&self) -> usize { + self.len() + } + + fn safe_access(&self) -> ShmMemoryBacking { + ShmMemoryBacking::Ptr(&**self) + } + + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { + f(self); + Ok(()) + } +} + pub trait AsyncShmGfxTexture: GfxTexture { fn async_upload( self: Rc, callback: Rc, - mem: &Rc, + mem: Rc, damage: Region, ) -> Result, GfxError>; diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index 3ef1e19f..dd45501a 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -67,7 +67,6 @@ macro_rules! dynload { use { crate::{ - clientmem::ClientMemError, gfx_api::{ AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, ReleaseSync, SyncFile, @@ -95,7 +94,7 @@ use { }, isnt::std_1::vec::IsntVecExt, once_cell::sync::Lazy, - std::{cell::RefCell, rc::Rc, sync::Arc}, + std::{cell::RefCell, error::Error, rc::Rc, sync::Arc}, thiserror::Error, }; @@ -199,7 +198,7 @@ enum RenderError { #[error("Buffer format {0} is not supported for shm buffers in OpenGL context")] UnsupportedShmFormat(&'static str), #[error("Could not access the client memory")] - AccessFailed(#[source] ClientMemError), + AccessFailed(#[source] Box), } #[derive(Default)] diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index efa45b84..7add2b78 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -1,10 +1,9 @@ use { crate::{ - clientmem::ClientMemOffset, format::Format, gfx_api::{ AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxError, GfxTexture, PendingShmUpload, - ShmGfxTexture, + ShmGfxTexture, ShmMemory, }, gfx_apis::gl::{ gl::texture::GlTexture, @@ -102,12 +101,15 @@ impl AsyncShmGfxTexture for Texture { fn async_upload( self: Rc, _callback: Rc, - mem: &Rc, - damage: Region, + mem: Rc, + _damage: Region, ) -> Result, GfxError> { - mem.access(|data| self.clone().sync_upload(data, damage)) - .map_err(RenderError::AccessFailed)??; - Ok(None) + let mut res = Ok(()); + mem.access(&mut |data| { + res = self.clone().sync_upload(data, Region::default()); + }) + .map_err(RenderError::AccessFailed)?; + res.map(|_| None) } fn sync_upload(self: Rc, data: &[Cell], _damage: Region) -> Result<(), GfxError> { diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index aa02a734..6826c2f6 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,11 +1,10 @@ use { crate::{ - clientmem::ClientMemOffset, format::Format, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, AsyncShmGfxTextureUploadCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, - GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, SyncFile, + GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, ShmMemory, SyncFile, }, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanModifierLimits, @@ -579,13 +578,13 @@ impl AsyncShmGfxTexture for VulkanImage { fn async_upload( self: Rc, callback: Rc, - mem: &Rc, + mem: Rc, damage: Region, ) -> Result, GfxError> { let VulkanImageMemory::Internal(shm) = &self.ty else { unreachable!(); }; - let pending = shm.async_upload(&self, mem, damage, callback)?; + let pending = shm.async_upload(&self, &mem, damage, callback)?; Ok(pending) } diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index 7dcbb199..ced7ea0a 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -1,6 +1,5 @@ use { crate::{ - clientmem::ClientMemOffset, cpu_worker::{ jobs::{ img_copy::ImgCopyWork, @@ -9,7 +8,9 @@ use { CpuJob, CpuWork, CpuWorker, }, format::{Format, FormatShmInfo}, - gfx_api::{AsyncShmGfxTextureCallback, PendingShmUpload, SyncFile}, + gfx_api::{ + AsyncShmGfxTextureCallback, PendingShmUpload, ShmMemory, ShmMemoryBacking, SyncFile, + }, gfx_apis::vulkan::{ allocator::VulkanAllocation, command::VulkanCommandBuffer, @@ -329,7 +330,7 @@ impl VulkanShmImage { pub fn async_upload( &self, img: &Rc, - client_mem: &Rc, + client_mem: &Rc, damage: Region, callback: Rc, ) -> Result, VulkanError> { @@ -350,13 +351,13 @@ impl VulkanShmImage { &self, img: &Rc, data: &VulkanShmImageAsyncData, - client_mem: &Rc, + client_mem: &Rc, mut damage: Region, ) -> Result<(), VulkanError> { if data.busy.get() { return Err(VulkanError::AsyncCopyBusy); } - if self.size > client_mem.ptr().len() as u64 { + if self.size > client_mem.len() as u64 { return Err(VulkanError::InvalidBufferSize); } data.busy.set(true); @@ -530,7 +531,7 @@ impl VulkanShmImage { fn async_upload_after_allocation( &self, img: &Rc, - client_mem: &Rc, + client_mem: &Rc, res: Result, ) -> Result<(), VulkanError> { let staging = Rc::new(res?); @@ -546,73 +547,76 @@ impl VulkanShmImage { data: &VulkanShmImageAsyncData, staging: &VulkanStagingBuffer, copies: &[BufferImageCopy2], - client_mem: &Rc, + client_mem: &Rc, ) -> Result<(), VulkanError> { img.renderer.check_defunct()?; let id = img.renderer.allocate_point(); let pending; - if client_mem.pool().sigbus_impossible() { - let mut job = data.copy_job.take().unwrap_or_else(|| { - Box::new(CopyUploadJob { - img: None, - id, - _mem: None, - work: unsafe { ImgCopyWork::new() }, - }) - }); - job.id = id; - job.img = Some(img.clone()); - job._mem = Some(client_mem.clone()); - job.work.src = client_mem.ptr() as _; - job.work.dst = staging.allocation.mem.unwrap(); - job.work.width = img.width as _; - job.work.stride = img.stride as _; - job.work.bpp = self.shm_info.bpp as _; - job.work.rects.clear(); - for copy in copies { - job.work.rects.push( - Rect::new_sized( - copy.image_offset.x as _, - copy.image_offset.y as _, - copy.image_extent.width as _, - copy.image_extent.height as _, - ) - .unwrap(), - ); - } - pending = data.cpu.submit(job); - } else { - let mut min_offset = client_mem.ptr().len() as u64; - let mut max_offset = 0; - for copy in copies { - min_offset = min_offset.min(copy.buffer_offset); - let len = img.stride * (copy.image_extent.height - 1) - + copy.image_extent.width * self.shm_info.bpp; - max_offset = max_offset.max(copy.buffer_offset + len as u64); + match client_mem.safe_access() { + ShmMemoryBacking::Ptr(ptr) => { + let mut job = data.copy_job.take().unwrap_or_else(|| { + Box::new(CopyUploadJob { + img: None, + id, + _mem: None, + work: unsafe { ImgCopyWork::new() }, + }) + }); + job.id = id; + job.img = Some(img.clone()); + job._mem = Some(client_mem.clone()); + job.work.src = ptr as _; + job.work.dst = staging.allocation.mem.unwrap(); + job.work.width = img.width as _; + job.work.stride = img.stride as _; + job.work.bpp = self.shm_info.bpp as _; + job.work.rects.clear(); + for copy in copies { + job.work.rects.push( + Rect::new_sized( + copy.image_offset.x as _, + copy.image_offset.y as _, + copy.image_extent.width as _, + copy.image_extent.height as _, + ) + .unwrap(), + ); + } + pending = data.cpu.submit(job); } - let mut job = data.io_job.take().unwrap_or_else(|| { - Box::new(IoUploadJob { - img: None, - id, - _mem: None, - work: unsafe { ReadWriteWork::new() }, - fd: None, - }) - }); - job.id = id; - job.img = Some(img.clone()); - job._mem = Some(client_mem.clone()); - job.fd = Some(client_mem.pool().fd().clone()); - unsafe { - let config = job.work.config(); - config.fd = client_mem.pool().fd().raw(); - config.offset = client_mem.offset() + min_offset as usize; - config.ptr = staging.allocation.mem.unwrap().add(min_offset as _); - config.len = max_offset.saturating_sub(min_offset) as usize; - config.write = false; + ShmMemoryBacking::Fd(fd, offset) => { + let mut min_offset = client_mem.len() as u64; + let mut max_offset = 0; + for copy in copies { + min_offset = min_offset.min(copy.buffer_offset); + let len = img.stride * (copy.image_extent.height - 1) + + copy.image_extent.width * self.shm_info.bpp; + max_offset = max_offset.max(copy.buffer_offset + len as u64); + } + let mut job = data.io_job.take().unwrap_or_else(|| { + Box::new(IoUploadJob { + img: None, + id, + _mem: None, + work: unsafe { ReadWriteWork::new() }, + fd: None, + }) + }); + job.id = id; + job.img = Some(img.clone()); + job._mem = Some(client_mem.clone()); + job.fd = Some(fd.clone()); + unsafe { + let config = job.work.config(); + config.fd = fd.raw(); + config.offset = offset + min_offset as usize; + config.ptr = staging.allocation.mem.unwrap().add(min_offset as _); + config.len = max_offset.saturating_sub(min_offset) as usize; + config.write = false; + } + pending = data.cpu.submit(job); } - pending = data.cpu.submit(job); } img.renderer.pending_cpu_jobs.set(id, pending); @@ -653,7 +657,7 @@ impl VulkanShmImage { pub(super) struct IoUploadJob { img: Option>, id: u64, - _mem: Option>, + _mem: Option>, fd: Option>, work: ReadWriteWork, } @@ -661,7 +665,7 @@ pub(super) struct IoUploadJob { pub(super) struct CopyUploadJob { img: Option>, id: u64, - _mem: Option>, + _mem: Option>, work: ImgCopyWork, } diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index da4a2c46..544d98a6 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -460,7 +460,7 @@ fn schedule_async_upload( } }; back_tex - .async_upload(node_ref.clone(), mem, back.damage.get()) + .async_upload(node_ref.clone(), mem.clone(), back.damage.get()) .map_err(WlSurfaceError::PrepareAsyncUpload) } diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 83db6ace..79f846d1 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -85,10 +85,9 @@ impl TestClient { } pub async fn sync(&self) { - self.run.state.eng.yield_now().await; self.run.sync().await; self.tran.sync().await; - self.run.state.eng.yield_now().await; + self.run.state.idle().await; } pub async fn take_screenshot(&self, include_cursor: bool) -> Result, TestError> { diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 90157cfa..95c52864 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -1,14 +1,13 @@ use { crate::{ allocator::{Allocator, AllocatorError, BufferObject, BufferUsage}, - clientmem::{ClientMemError, ClientMemOffset}, cpu_worker::CpuWorker, format::{Format, ARGB8888, XRGB8888}, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, GfxTexture, GfxWriteModifier, PendingShmUpload, ReleaseSync, ResetStatus, - ShmGfxTexture, SyncFile, + ShmGfxTexture, ShmMemory, SyncFile, }, rect::{Rect, Region}, theme::Color, @@ -20,6 +19,7 @@ use { std::{ any::Any, cell::{Cell, RefCell}, + error::Error, ffi::CString, fmt::{Debug, Formatter}, ops::Deref, @@ -36,7 +36,7 @@ enum TestGfxError { #[error("Could not import dmabuf")] ImportDmaBuf(#[source] AllocatorError), #[error("Could not access the client memory")] - AccessFailed(#[source] ClientMemError), + AccessFailed(#[source] Box), } impl From for GfxError { @@ -336,12 +336,15 @@ impl AsyncShmGfxTexture for TestGfxImage { fn async_upload( self: Rc, _callback: Rc, - mem: &Rc, - damage: Region, + mem: Rc, + _damage: Region, ) -> Result, GfxError> { - mem.access(|d| self.clone().sync_upload(d, damage)) - .map_err(TestGfxError::AccessFailed)??; - Ok(None) + let mut res = Ok(()); + mem.access(&mut |d| { + res = self.clone().sync_upload(d, Region::default()); + }) + .map_err(TestGfxError::AccessFailed)?; + res.map(|_| None) } fn sync_upload(self: Rc, mem: &[Cell], _damage: Region) -> Result<(), GfxError> { diff --git a/src/it/test_ifs/test_jay_compositor.rs b/src/it/test_ifs/test_jay_compositor.rs index 71298877..9e57540b 100644 --- a/src/it/test_ifs/test_jay_compositor.rs +++ b/src/it/test_ifs/test_jay_compositor.rs @@ -54,6 +54,8 @@ impl TestJayCompositor { &self, include_cursor: bool, ) -> Result<(DmaBuf, Option>), TestError> { + self.tran.sync().await; + self.tran.run.state.idle().await; let js = Rc::new(TestJayScreenshot { id: self.tran.id(), state: self.tran.run.state.clone(), diff --git a/src/portal.rs b/src/portal.rs index 5f9905b6..d601475f 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -2,6 +2,7 @@ mod ptl_display; mod ptl_remote_desktop; mod ptl_render_ctx; mod ptl_screencast; +mod ptl_text; mod ptr_gui; use { diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs new file mode 100644 index 00000000..81112393 --- /dev/null +++ b/src/portal/ptl_text.rs @@ -0,0 +1,101 @@ +use { + crate::{ + format::ARGB8888, + gfx_api::{GfxContext, GfxTexture}, + pango::{ + consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE}, + CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout, + }, + rect::Rect, + theme::Color, + }, + std::{ops::Neg, rc::Rc, sync::Arc}, +}; + +struct Data { + image: Rc, + cctx: Rc, + _pctx: Rc, + _fd: PangoFontDescription, + layout: PangoLayout, +} + +fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Option { + let image = CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height).ok()?; + let cctx = image.create_context().ok()?; + let pctx = cctx.create_pango_context().ok()?; + let mut fd = PangoFontDescription::from_string(font); + if let Some(scale) = scale { + fd.set_size((fd.size() as f64 * scale).round() as _); + } + let layout = pctx.create_layout().ok()?; + layout.set_font_description(&fd); + Some(Data { + image, + cctx, + _pctx: pctx, + _fd: fd, + layout, + }) +} + +fn measure(font: &str, text: &str, scale: Option, full: bool) -> Option { + let data = create_data(font, 1, 1, scale)?; + data.layout.set_text(text); + let mut res = TextMeasurement::default(); + res.ink_rect = data.layout.inc_pixel_rect(); + if full { + res.logical_rect = data.layout.logical_pixel_rect(); + res.baseline = data.layout.pixel_baseline(); + } + Some(res) +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct TextMeasurement { + pub ink_rect: Rect, + pub logical_rect: Rect, + pub baseline: i32, +} + +pub fn render( + ctx: &Rc, + height: Option, + font: &Arc, + text: &str, + color: Color, + scale: Option, + include_measurements: bool, +) -> Option<(Rc, TextMeasurement)> { + let measurement = measure(font, text, scale, include_measurements)?; + let y = match height { + Some(_) => None, + _ => Some(measurement.ink_rect.y1().neg()), + }; + let x = measurement.ink_rect.x1().neg(); + let width = measurement.ink_rect.width(); + let height = height.unwrap_or(measurement.ink_rect.height()); + let data = create_data(font, width, height, scale)?; + data.layout.set_text(text); + let font_height = data.layout.pixel_size().1; + data.cctx.set_operator(CAIRO_OPERATOR_SOURCE); + data.cctx + .set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _); + let y = y.unwrap_or((height - font_height) / 2); + data.cctx.move_to(x as f64, y as f64); + data.layout.show_layout(); + data.image.flush(); + let bytes = data.image.data().ok()?; + ctx.clone() + .shmem_texture( + None, + bytes, + ARGB8888, + width, + height, + data.image.stride(), + None, + ) + .ok() + .map(|t| (t.into_texture(), measurement)) +} diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 23ba3ff6..fd5a1510 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -5,12 +5,16 @@ use { cursor::KnownCursor, fixed::Fixed, format::ARGB8888, - gfx_api::{needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync}, + gfx_api::{ + needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, + }, ifs::zwlr_layer_shell_v1::OVERLAY, - portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, + portal::{ + ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, + ptl_text::{self, TextMeasurement}, + }, renderer::renderer_base::RendererBase, scale::Scale, - text::{self, TextMeasurement, TextTexture}, theme::Color, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -31,10 +35,10 @@ use { }, ahash::AHashSet, std::{ - borrow::Cow, cell::{Cell, RefCell}, ops::Deref, rc::Rc, + sync::Arc, }, }; @@ -117,8 +121,8 @@ pub struct Button { pub bg_color: Cell, pub bg_hover_color: Cell, pub text: RefCell, - pub font: RefCell>, - pub tex: CloneCell>, + pub font: Arc, + pub tex: CloneCell>>, pub owner: CloneCell>>, } @@ -139,7 +143,7 @@ impl Default for Button { bg_color: Cell::new(Color::from_gray(255)), bg_hover_color: Cell::new(Color::from_gray(255)), text: Default::default(), - font: RefCell::new(DEFAULT_FONT.into()), + font: Arc::new(DEFAULT_FONT.to_string()), tex: Default::default(), owner: Default::default(), } @@ -162,21 +166,16 @@ impl GuiElement for Button { _max_width: f32, _max_height: f32, ) -> (f32, f32) { - let old_tex = self.tex.take(); - let font = self.font.borrow_mut(); let text = self.text.borrow_mut(); - let tex = text::render_fitting2( + let tex = ptl_text::render( ctx, - old_tex, None, - &font, + &self.font, &text, Color::from_gray(0), - false, Some(scale as _), true, - ) - .ok(); + ); let (tex, msmt) = match tex { Some((a, b)) => (Some(a), Some(b)), _ => (None, None), @@ -215,7 +214,7 @@ impl GuiElement for Button { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); r.render_texture( - &tex.texture, + &tex, None, tx.round() as _, ty.round() as _, @@ -262,16 +261,16 @@ const DEFAULT_FONT: &str = "sans-serif 16"; pub struct Label { pub data: GuiElementData, - pub font: RefCell>, + pub font: Arc, pub text: RefCell, - pub tex: CloneCell>, + pub tex: CloneCell>>, } impl Default for Label { fn default() -> Self { Self { data: Default::default(), - font: RefCell::new(DEFAULT_FONT.into()), + font: Arc::new(DEFAULT_FONT.into()), text: RefCell::new("".to_string()), tex: Default::default(), } @@ -290,24 +289,19 @@ impl GuiElement for Label { _max_width: f32, _max_height: f32, ) -> (f32, f32) { - let old_tex = self.tex.take(); let text = self.text.borrow_mut(); - let font = self.font.borrow_mut(); - let tex = text::render_fitting2( + let tex = ptl_text::render( ctx, - old_tex, None, - &font, + &self.font, &text, Color::from_gray(255), - false, Some(scale as _), false, - ) - .ok(); + ); let (tex, width, height) = match tex { Some((t, _)) => { - let (width, height) = t.texture.size(); + let (width, height) = t.size(); (Some(t.clone()), width, height) } _ => (None, 0, 0), @@ -320,7 +314,7 @@ impl GuiElement for Label { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( - &tex.texture, + &tex, None, tx.round() as _, ty.round() as _, diff --git a/src/renderer.rs b/src/renderer.rs index b5fb7e16..eb83d00a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -131,20 +131,22 @@ impl Renderer<'_> { ); } if let Some(status) = &rd.status { - let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); - self.base.render_texture( - &status.tex.texture, - None, - x, - y, - None, - None, - scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(texture) = status.tex.texture() { + let (x, y) = self.base.scale_point(x + status.tex_x, y); + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } } if let Some(ws) = output.workspace.get() { @@ -196,23 +198,25 @@ impl Renderer<'_> { std::slice::from_ref(&pos.at_point(x, y)), &Color::from_rgba_straight(20, 20, 20, 255), ); - if let Some(tex) = placeholder.textures.get(&self.base.scale) { - let (tex_width, tex_height) = tex.texture.size(); - let x = x + (pos.width() - tex_width) / 2; - let y = y + (pos.height() - tex_height) / 2; - self.base.render_texture( - &tex.texture, - None, - x, - y, - None, - None, - self.base.scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) { + if let Some(texture) = tex.texture() { + let (tex_width, tex_height) = texture.size(); + let x = x + (pos.width() - tex_width) / 2; + let y = y + (pos.height() - tex_height) / 2; + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + self.base.scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } self.render_tl_aux(placeholder.tl_data(), bounds, true); } @@ -243,7 +247,7 @@ impl Renderer<'_> { for title in titles { let (x, y) = self.base.scale_point(x + title.x, y + title.y); self.base.render_texture( - &title.tex.texture, + &title.tex, None, x, y, @@ -466,21 +470,23 @@ impl Renderer<'_> { let title_underline = [Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()]; self.base.fill_boxes(&title_underline, &uc); - if let Some(title) = floating.title_textures.get(&self.base.scale) { - let (x, y) = self.base.scale_point(x + bw, y + bw); - self.base.render_texture( - &title.texture, - None, - x, - y, - None, - None, - self.base.scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) { + if let Some(texture) = title.texture() { + let (x, y) = self.base.scale_point(x + bw, y + bw); + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + self.base.scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } let body = Rect::new_sized( x + bw, diff --git a/src/state.rs b/src/state.rs index 45c30708..b46655a6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -148,13 +148,15 @@ pub struct State { pub config: CloneCell>>, pub theme: Theme, pub pending_container_layout: AsyncQueue>, - pub pending_container_render_data: AsyncQueue>, + pub pending_container_render_positions: AsyncQueue>, + pub pending_container_render_title: AsyncQueue>, pub pending_output_render_data: AsyncQueue>, pub pending_float_layout: AsyncQueue>, pub pending_float_titles: AsyncQueue>, pub pending_input_popup_positioning: AsyncQueue>, pub pending_toplevel_screencasts: AsyncQueue>, pub pending_screencast_reallocs_or_reconfigures: AsyncQueue>, + pub pending_placeholder_render_textures: AsyncQueue>, pub dbus: Dbus, pub fdcloser: Arc, pub logger: Option>, @@ -343,8 +345,10 @@ impl DrmDevData { struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { - node.children.iter().for_each(|c| c.title_tex.clear()); - node.schedule_compute_render_data(); + node.children + .iter() + .for_each(|c| c.title_tex.borrow_mut().clear()); + node.schedule_render_titles(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { @@ -352,13 +356,17 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.clear(); + node.title_textures.borrow_mut().clear(); node.schedule_render_titles(); node.node_visit_children(self); } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.take(); + node.node_visit_children(self); + } fn visit_placeholder(&mut self, node: &Rc) { - node.textures.clear(); - node.update_texture(); + node.textures.borrow_mut().clear(); + node.schedule_update_texture(); node.node_visit_children(self); } } @@ -463,11 +471,13 @@ impl State { impl NodeVisitorBase for Walker { fn visit_container(&mut self, node: &Rc) { node.render_data.borrow_mut().titles.clear(); - node.children.iter().for_each(|c| c.title_tex.clear()); + node.children + .iter() + .for_each(|c| c.title_tex.borrow_mut().clear()); node.node_visit_children(self); } fn visit_workspace(&mut self, node: &Rc) { - node.title_texture.set(None); + node.title_texture.take(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { @@ -477,11 +487,11 @@ impl State { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.clear(); + node.title_textures.borrow_mut().clear(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { - node.textures.clear(); + node.textures.borrow_mut().clear(); node.node_visit_children(self); } } @@ -819,13 +829,15 @@ impl State { } self.dbus.clear(); self.pending_container_layout.clear(); - self.pending_container_render_data.clear(); + self.pending_container_render_positions.clear(); + self.pending_container_render_title.clear(); self.pending_output_render_data.clear(); self.pending_float_layout.clear(); self.pending_float_titles.clear(); self.pending_input_popup_positioning.clear(); self.pending_toplevel_screencasts.clear(); self.pending_screencast_reallocs_or_reconfigures.clear(); + self.pending_placeholder_render_textures.clear(); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.clear(); @@ -1218,6 +1230,16 @@ impl State { output.vblank(); } } + + #[cfg(feature = "it")] + pub async fn idle(&self) { + loop { + self.eng.idle().await; + if self.cpu_worker.wait_idle() { + break; + } + } + } } #[derive(Debug, Error)] diff --git a/src/text.rs b/src/text.rs index f0176800..7eed4e21 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,7 +1,11 @@ use { crate::{ + cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, - gfx_api::{GfxContext, GfxError, GfxTexture, ShmGfxTexture}, + gfx_api::{ + AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxTexture, + PendingShmUpload, + }, pango::{ consts::{ CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, PANGO_ELLIPSIZE_END, PANGO_SCALE, @@ -9,14 +13,19 @@ use { CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription, PangoLayout, }, - rect::Rect, + rect::{Rect, Region}, theme::Color, - utils::clonecell::UnsafeCellCloneSafe, + utils::{ + clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent, + }, }, std::{ borrow::Cow, - ops::{Deref, Neg}, - rc::Rc, + cell::{Cell, RefCell}, + mem, + ops::Neg, + rc::{Rc, Weak}, + sync::Arc, }, thiserror::Error, }; @@ -31,54 +40,64 @@ pub enum TextError { PangoContext(#[source] PangoError), #[error("Could not create a pango layout")] CreateLayout(#[source] PangoError), - #[error("Could not import the rendered text")] - RenderError(#[source] GfxError), #[error("Could not access the cairo image data")] ImageData(#[source] PangoError), -} - -#[derive(PartialEq)] -struct Config<'a> { - x: i32, - y: Option, - width: i32, - height: i32, - padding: i32, - font: Cow<'a, str>, - text: Cow<'a, str>, - color: Color, - ellipsize: bool, - markup: bool, - scale: Option, + #[error("Texture upload failed")] + Upload(#[source] GfxError), + #[error("Could not create a texture")] + CreateTexture(#[source] GfxError), + #[error("Rendering is not scheduled or not yet completed")] + NotScheduled, } impl<'a> Config<'a> { fn to_static(self) -> Config<'static> { - Config { - x: self.x, - y: self.y, - width: self.width, - height: self.height, - padding: self.padding, - font: Cow::Owned(self.font.into_owned()), - text: Cow::Owned(self.text.into_owned()), - color: self.color, - ellipsize: self.ellipsize, - markup: self.markup, - scale: self.scale, + match self { + Config::None => Config::None, + Config::RenderFitting { + height, + font, + text, + color, + markup, + scale, + } => Config::RenderFitting { + height, + font, + text: text.into_owned().into(), + color, + markup, + scale, + }, + Config::Render { + x, + y, + width, + height, + padding, + font, + text, + color, + ellipsize, + markup, + scale, + } => Config::Render { + x, + y, + width, + height, + padding, + font, + text: text.into_owned().into(), + color, + ellipsize, + markup, + scale, + }, } } } -#[derive(Clone)] -pub struct TextTexture { - config: Rc>, - shm_texture: Rc, - pub texture: Rc, -} - -unsafe impl UnsafeCellCloneSafe for TextTexture {} - struct Data { image: Rc, cctx: Rc, @@ -118,12 +137,11 @@ fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Resul }) } -pub fn measure( +fn measure( font: &str, text: &str, markup: bool, scale: Option, - full: bool, ) -> Result { let data = create_data(font, 1, 1, scale)?; if markup { @@ -133,31 +151,10 @@ pub fn measure( } let mut res = TextMeasurement::default(); res.ink_rect = data.layout.inc_pixel_rect(); - if full { - res.logical_rect = data.layout.logical_pixel_rect(); - res.baseline = data.layout.pixel_baseline(); - } Ok(res) } -pub fn render( - ctx: &Rc, - old: Option, - width: i32, - height: i32, - font: &str, - text: &str, - color: Color, - scale: Option, -) -> Result { - render2( - ctx, old, 1, None, width, height, 1, font, text, color, true, false, scale, - ) -} - -fn render2( - ctx: &Rc, - old: Option, +fn render( x: i32, y: Option, width: i32, @@ -169,25 +166,14 @@ fn render2( ellipsize: bool, markup: bool, scale: Option, -) -> Result { - let width = width.min(3840); - let config = Config { - x, - y, - width, - height, - padding, - font: Cow::Borrowed(font), - text: Cow::Borrowed(text), - color, - ellipsize, - markup, - scale, - }; - if let Some(old2) = &old { - if old2.config.deref() == &config { - return Ok(old.unwrap()); - } +) -> Result { + if width == 0 || height == 0 { + return Ok(RenderedText { + width, + height, + stride: width * 4, + data: vec![], + }); } let data = create_data(font, width, height, scale)?; if ellipsize { @@ -208,79 +194,368 @@ fn render2( data.cctx.move_to(x as f64, y as f64); data.layout.show_layout(); data.image.flush(); - let bytes = match data.image.data() { - Ok(d) => d, - Err(e) => return Err(TextError::ImageData(e)), - }; - let old = old.map(|o| o.shm_texture); - match ctx.clone().shmem_texture( - old, - bytes, - ARGB8888, + Ok(RenderedText { width, height, - data.image.stride(), - None, - ) { - Ok(t) => Ok(TextTexture { - config: Rc::new(config.to_static()), - texture: t.clone().into_texture(), - shm_texture: t, - }), - Err(e) => Err(TextError::RenderError(e)), - } + stride: data.image.stride(), + data: data.image.data().map_err(TextError::ImageData)?.to_vec(), + }) } -pub fn render_fitting( - ctx: &Rc, - old: Option, +fn render_fitting( height: Option, font: &str, text: &str, color: Color, markup: bool, scale: Option, -) -> Result { - render_fitting2(ctx, old, height, font, text, color, markup, scale, false).map(|(a, _)| a) +) -> Result { + let measurement = measure(font, text, markup, scale)?; + let x = measurement.ink_rect.x1().neg(); + let y = match height { + Some(_) => None, + _ => Some(measurement.ink_rect.y1().neg()), + }; + let width = measurement.ink_rect.width(); + let height = height.unwrap_or(measurement.ink_rect.height()); + render( + x, y, width, height, 0, font, text, color, false, markup, scale, + ) } #[derive(Debug, Copy, Clone, Default)] pub struct TextMeasurement { pub ink_rect: Rect, - pub logical_rect: Rect, - pub baseline: i32, } -pub fn render_fitting2( - ctx: &Rc, - old: Option, - height: Option, - font: &str, - text: &str, - color: Color, - markup: bool, - scale: Option, - include_measurements: bool, -) -> Result<(TextTexture, TextMeasurement), TextError> { - let measurement = measure(font, text, markup, scale, include_measurements)?; - let y = match height { - Some(_) => None, - _ => Some(measurement.ink_rect.y1().neg()), - }; - let res = render2( - ctx, - old, - measurement.ink_rect.x1().neg(), - y, - measurement.ink_rect.width(), - height.unwrap_or(measurement.ink_rect.height()), - 0, - font, - text, - color, - false, - markup, - scale, - ); - res.map(|r| (r, measurement)) +struct RenderedText { + width: i32, + height: i32, + stride: i32, + data: Vec>, +} + +#[derive(Default)] +struct RenderWork { + config: Config<'static>, + result: Option>, +} + +struct RenderJob { + work: RenderWork, + data: Weak, +} + +impl CpuWork for RenderWork { + fn run(&mut self) -> Option> { + self.result = Some(self.render()); + None + } +} + +impl RenderWork { + fn render(&mut self) -> Result { + match self.config { + Config::None => unreachable!(), + Config::RenderFitting { + height, + ref font, + ref text, + color, + markup, + scale, + } => render_fitting(height, font, text, color, markup, scale), + Config::Render { + x, + y, + width, + height, + padding, + ref font, + ref text, + color, + ellipsize, + markup, + scale, + } => render( + x, y, width, height, padding, font, text, color, ellipsize, markup, scale, + ), + } + } +} + +pub struct TextTexture { + data: Rc, +} + +impl Drop for TextTexture { + fn drop(&mut self) { + if let Some(pending) = self.data.pending_render.take() { + pending.detach(); + } + self.data.pending_upload.take(); + self.data.render_job.take(); + self.data.waiter.take(); + } +} + +struct Shared { + cpu_worker: Rc, + ctx: Rc, + textures: DoubleBuffered, + pending_render: Cell>, + pending_upload: Cell>, + render_job: Cell>>, + result: Cell>>, + waiter: Cell>>, + busy: Cell, + flip_is_noop: Cell, +} + +impl Shared { + fn complete(&self, res: Result<(), TextError>) { + if res.is_err() { + self.textures.back().config.take(); + } + self.busy.set(false); + self.result.set(Some(res)); + if let Some(waiter) = self.waiter.take() { + waiter.completed(); + } + } +} + +#[derive(PartialEq, Default)] +enum Config<'a> { + #[default] + None, + RenderFitting { + height: Option, + font: Arc, + text: Cow<'a, str>, + color: Color, + markup: bool, + scale: Option, + }, + Render { + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: Arc, + text: Cow<'a, str>, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, + }, +} + +#[derive(Default)] +struct TextBuffer { + config: RefCell>, + tex: CloneCell>>, +} + +pub trait OnCompleted { + fn completed(self: Rc); +} + +impl TextTexture { + pub fn new(cpu_worker: &Rc, ctx: &Rc) -> Self { + let data = Rc::new(Shared { + cpu_worker: cpu_worker.clone(), + ctx: ctx.clone(), + textures: Default::default(), + pending_render: Default::default(), + pending_upload: Default::default(), + render_job: Default::default(), + result: Default::default(), + waiter: Default::default(), + busy: Default::default(), + flip_is_noop: Default::default(), + }); + Self { data } + } + + pub fn texture(&self) -> Option> { + self.data + .textures + .front() + .tex + .get() + .map(|t| t.into_texture()) + } + + fn apply_config(&self, on_completed: Rc, config: Config<'_>) { + if self.data.busy.replace(true) { + unreachable!(); + } + self.data.waiter.set(Some(on_completed)); + self.data.flip_is_noop.set(false); + if *self.data.textures.front().config.borrow() == config { + self.data.flip_is_noop.set(true); + self.data.complete(Ok(())); + return; + } + if *self.data.textures.back().config.borrow() == config { + self.data.complete(Ok(())); + return; + } + let mut job = self.data.render_job.take().unwrap_or_else(|| { + Box::new(RenderJob { + work: Default::default(), + data: Rc::downgrade(&self.data), + }) + }); + job.work = RenderWork { + config: config.to_static(), + result: None, + }; + let pending = self.data.cpu_worker.submit(job); + self.data.pending_render.set(Some(pending)); + } + + pub fn schedule_render( + &self, + on_completed: Rc, + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: &Arc, + text: &str, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, + ) { + let config = Config::Render { + x, + y, + width, + height, + padding, + font: font.clone(), + text: Cow::Borrowed(text), + color, + ellipsize, + markup, + scale, + }; + self.apply_config(on_completed, config) + } + + pub fn schedule_render_fitting( + &self, + on_completed: Rc, + height: Option, + font: &Arc, + text: &str, + color: Color, + markup: bool, + scale: Option, + ) { + let config = Config::RenderFitting { + height, + font: font.clone(), + text: text.into(), + color, + markup, + scale, + }; + self.apply_config(on_completed, config) + } + + pub fn flip(&self) -> Result<(), TextError> { + let res = self + .data + .result + .take() + .unwrap_or(Err(TextError::NotScheduled)); + if res.is_ok() && !self.data.flip_is_noop.get() { + self.data.textures.flip(); + } + res + } +} + +impl CpuJob for RenderJob { + fn work(&mut self) -> &mut dyn CpuWork { + &mut self.work + } + + fn completed(mut self: Box) { + let Some(data) = self.data.upgrade() else { + return; + }; + let result = self.work.result.take().unwrap(); + *data.textures.back().config.borrow_mut() = mem::take(&mut self.work.config); + data.render_job.set(Some(self)); + let rt = match result { + Ok(d) => d, + Err(e) => { + data.complete(Err(e)); + return; + } + }; + let mut tex = data.textures.back().tex.take(); + if rt.width == 0 || rt.height == 0 { + data.complete(Ok(())); + return; + } + if let Some(t) = &tex { + if !t.compatible_with(ARGB8888, rt.width, rt.height, rt.stride) { + tex = None; + } + } + let tex = match tex { + Some(t) => t, + _ => { + let tex = data + .ctx + .clone() + .async_shmem_texture(ARGB8888, rt.width, rt.height, rt.stride, &data.cpu_worker) + .map_err(TextError::CreateTexture); + match tex { + Ok(t) => t, + Err(e) => { + data.complete(Err(e)); + return; + } + } + } + }; + let pending = tex + .clone() + .async_upload( + data.clone(), + Rc::new(rt.data), + Region::new2(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)), + ) + .map_err(TextError::Upload); + if pending.is_ok() { + data.textures.back().tex.set(Some(tex)); + } + match pending { + Ok(Some(p)) => data.pending_upload.set(Some(p)), + Ok(None) => data.complete(Ok(())), + Err(e) => data.complete(Err(e)), + } + } +} + +impl AsyncShmGfxTextureCallback for Shared { + fn completed(self: Rc, res: Result<(), GfxError>) { + self.pending_upload.take(); + self.complete(res.map_err(TextError::Upload)); + } +} + +impl OnCompleted for OnDropEvent { + fn completed(self: Rc) { + // nothing + } } diff --git a/src/theme.rs b/src/theme.rs index ef351d31..44a38635 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,7 +1,6 @@ -use std::{ - cell::{Cell, RefCell}, - cmp::Ordering, - ops::Mul, +use { + crate::utils::clonecell::CloneCell, + std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; #[derive(Copy, Clone, Debug, PartialEq)] @@ -290,15 +289,18 @@ pub const DEFAULT_FONT: &str = "monospace 8"; pub struct Theme { pub colors: ThemeColors, pub sizes: ThemeSizes, - pub font: RefCell, + pub font: CloneCell>, + pub default_font: Arc, } impl Default for Theme { fn default() -> Self { + let default_font = Arc::new(DEFAULT_FONT.to_string()); Self { colors: Default::default(), sizes: Default::default(), - font: RefCell::new(DEFAULT_FONT.to_string()), + font: CloneCell::new(default_font.clone()), + default_font, } } } diff --git a/src/tree/container.rs b/src/tree/container.rs index af6f6171..7240ac77 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -4,6 +4,7 @@ use { cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, + gfx_api::GfxTexture, ifs::wl_seat::{ collect_kb_foci, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, @@ -14,21 +15,23 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, }, utils::{ + asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, + on_drop_event::OnDropEvent, rc_eq::rc_eq, scroller::Scroller, - smallmap::{SmallMap, SmallMapMut}, + smallmap::SmallMapMut, threshold_counter::ThresholdCounter, }, }, @@ -81,7 +84,7 @@ tree_id!(ContainerNodeId); pub struct ContainerTitle { pub x: i32, pub y: i32, - pub tex: TextTexture, + pub tex: Rc, } #[derive(Default)] @@ -109,7 +112,8 @@ pub struct ContainerNode { pub content_height: Cell, pub sum_factors: Cell, layout_scheduled: Cell, - compute_render_data_scheduled: Cell, + compute_render_positions_scheduled: Cell, + render_titles_scheduled: Cell, num_children: NumCell, pub children: LinkedList, focus_history: LinkedList>, @@ -134,7 +138,7 @@ pub struct ContainerChild { pub active: Cell, pub attention_requested: Cell, title: RefCell, - pub title_tex: SmallMap, + pub title_tex: RefCell>, pub title_rect: Cell, focus_history: Cell>>>, @@ -213,7 +217,8 @@ impl ContainerNode { content_height: Cell::new(0), sum_factors: Cell::new(1.0), layout_scheduled: Cell::new(false), - compute_render_data_scheduled: Cell::new(false), + compute_render_positions_scheduled: Cell::new(false), + render_titles_scheduled: Cell::new(false), num_children: NumCell::new(1), children, focus_history: Default::default(), @@ -351,7 +356,8 @@ impl ContainerNode { pub fn on_colors_changed(self: &Rc) { // log::info!("on_colors_changed"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); + self.schedule_compute_render_positions(); } fn damage(&self) { @@ -387,7 +393,8 @@ impl ContainerNode { } self.state.tree_changed(); // log::info!("perform_layout"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); + self.schedule_compute_render_positions(); } fn perform_mono_layout(self: &Rc, child: &ContainerChild) { @@ -660,23 +667,115 @@ impl ContainerNode { self.tl_title_changed(); } - pub fn schedule_compute_render_data(self: &Rc) { - if !self.compute_render_data_scheduled.replace(true) { - self.state.pending_container_render_data.push(self.clone()); + pub fn schedule_render_titles(self: &Rc) { + if !self.render_titles_scheduled.replace(true) { + self.state.pending_container_render_title.push(self.clone()); } } - fn compute_render_data(&self) { - self.compute_render_data_scheduled.set(false); + fn render_titles(&self) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); + let Some(ctx) = self.state.render_ctx.get() else { + return on_completed.event(); + }; + let theme = &self.state.theme; + let th = theme.sizes.title_height.get(); + let font = theme.font.get(); + let last_active = self.focus_history.last().map(|v| v.node.node_id()); + let have_active = self.children.iter().any(|c| c.active.get()); + let scales = self.state.scales.lock(); + for child in self.children.iter() { + let rect = child.title_rect.get(); + let color = if child.active.get() { + theme.colors.focused_title_text.get() + } else if child.attention_requested.get() { + theme.colors.unfocused_title_text.get() + } else if !have_active && last_active == Some(child.node.node_id()) { + theme.colors.focused_inactive_title_text.get() + } else { + theme.colors.unfocused_title_text.get() + }; + let title = child.title.borrow_mut(); + let tt = &mut *child.title_tex.borrow_mut(); + for (scale, _) in scales.iter() { + let tex = tt + .get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let mut th = th; + let mut scalef = None; + let mut width = rect.width(); + if *scale != 1 { + let scale = scale.to_f64(); + th = (th as f64 * scale).round() as _; + width = (width as f64 * scale).round() as _; + scalef = Some(scale); + } + tex.schedule_render( + on_completed.clone(), + 1, + None, + width, + th, + 1, + &font, + title.deref(), + color, + true, + false, + scalef, + ); + } + } + on_completed.event() + } + + fn compute_title_data(&self) { + let rd = &mut *self.render_data.borrow_mut(); + for (_, v) in rd.titles.iter_mut() { + v.clear(); + } + let abs_x = self.abs_x1.get(); + let abs_y = self.abs_y1.get(); + for child in self.children.iter() { + let rect = child.title_rect.get(); + if self.toplevel_data.visible.get() { + self.state.damage(rect.move_(abs_x, abs_y)); + } + let title = child.title.borrow_mut(); + let tt = &*child.title_tex.borrow(); + for (scale, tex) in tt { + if let Err(e) = tex.flip() { + log::error!("Could not render title {}: {}", title, ErrorFmt(e)); + } + if let Some(tex) = tex.texture() { + let titles = rd.titles.get_or_default_mut(*scale); + titles.push(ContainerTitle { + x: rect.x1(), + y: rect.y1(), + tex, + }) + } + } + } + rd.titles.remove_if(|_, v| v.is_empty()); + } + + fn schedule_compute_render_positions(self: &Rc) { + if !self.compute_render_positions_scheduled.replace(true) { + self.state + .pending_container_render_positions + .push(self.clone()); + } + } + + fn compute_render_positions(&self) { + self.compute_render_positions_scheduled.set(false); let mut rd = self.render_data.borrow_mut(); let rd = rd.deref_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); - let font = theme.font.borrow_mut(); let cwidth = self.width.get(); let cheight = self.height.get(); - let ctx = self.state.render_ctx.get(); for (_, v) in rd.titles.iter_mut() { v.clear(); } @@ -690,7 +789,6 @@ impl ContainerNode { let mono = self.mono_child.is_some(); let split = self.split.get(); let have_active = self.children.iter().any(|c| c.active.get()); - let scales = self.state.scales.lock(); let abs_x = self.abs_x1.get(); let abs_y = self.abs_y1.get(); for (i, child) in self.children.iter().enumerate() { @@ -708,64 +806,28 @@ impl ContainerNode { }; rd.border_rects.push(rect.unwrap()); } - let color = if child.active.get() { + if child.active.get() { rd.active_title_rects.push(rect); - theme.colors.focused_title_text.get() } else if child.attention_requested.get() { rd.attention_title_rects.push(rect); - theme.colors.unfocused_title_text.get() } else if !have_active && last_active == Some(child.node.node_id()) { rd.last_active_rect = Some(rect); - theme.colors.focused_inactive_title_text.get() } else { rd.title_rects.push(rect); - theme.colors.unfocused_title_text.get() - }; + } if !mono { let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap(); rd.underline_rects.push(rect); } - let title = child.title.borrow_mut(); - for (scale, _) in scales.iter() { - let old_tex = child.title_tex.remove(scale); - let titles = rd.titles.get_or_default_mut(*scale); - 'render_title: { - let mut th = th; - let mut scalef = None; - let mut width = rect.width(); - if *scale != 1 { - let scale = scale.to_f64(); - th = (th as f64 * scale).round() as _; - width = (width as f64 * scale).round() as _; - scalef = Some(scale); - } - if th == 0 || width == 0 || title.is_empty() { - break 'render_title; - } - if let Some(ctx) = &ctx { - match text::render( - ctx, - old_tex, - width, - th, - &font, - title.deref(), - color, - scalef, - ) { - Ok(t) => { - child.title_tex.insert(*scale, t.clone()); - titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex: t, - }) - } - Err(e) => { - log::error!("Could not render title {}: {}", title, ErrorFmt(e)); - } - } - } + let tt = &*child.title_tex.borrow(); + for (scale, tex) in tt { + if let Some(tex) = tex.texture() { + let titles = rd.titles.get_or_default_mut(*scale); + titles.push(ContainerTitle { + x: rect.x1(), + y: rect.y1(), + tex, + }) } } } @@ -1010,7 +1072,7 @@ impl ContainerNode { } self.update_title(); // log::info!("node_child_title_changed"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); } fn update_child_active( @@ -1027,7 +1089,8 @@ impl ContainerNode { .set(Some(self.focus_history.add_last(node.clone()))); } // log::info!("node_child_active_changed"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); + self.schedule_compute_render_positions(); if let Some(parent) = self.toplevel_data.parent.get() { parent.node_child_active_changed(self.deref(), active, depth + 1); } @@ -1175,11 +1238,22 @@ pub async fn container_layout(state: Rc) { } } -pub async fn container_render_data(state: Rc) { +pub async fn container_render_positions(state: Rc) { + loop { + let container = state.pending_container_render_positions.pop().await; + if container.compute_render_positions_scheduled.get() { + container.compute_render_positions(); + } + } +} + +pub async fn container_render_titles(state: Rc) { loop { - let container = state.pending_container_render_data.pop().await; - if container.compute_render_data_scheduled.get() { - container.compute_render_data(); + let container = state.pending_container_render_title.pop().await; + if container.render_titles_scheduled.get() { + container.render_titles_scheduled.set(false); + container.render_titles().triggered().await; + container.compute_title_data(); } } } @@ -1562,7 +1636,7 @@ impl ContainingNode for ContainerNode { return; } self.mod_attention_requests(set); - self.schedule_compute_render_data(); + self.schedule_compute_render_positions(); } fn cnode_workspace(self: Rc) -> Rc { diff --git a/src/tree/float.rs b/src/tree/float.rs index 11929502..5e13d955 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -12,14 +12,15 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState, - errorfmt::ErrorFmt, linkedlist::LinkedNode, + asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, + errorfmt::ErrorFmt, linkedlist::LinkedNode, on_drop_event::OnDropEvent, + smallmap::SmallMapMut, }, }, ahash::AHashMap, @@ -47,7 +48,7 @@ pub struct FloatNode { pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title: RefCell, - pub title_textures: CopyHashMap, + pub title_textures: RefCell>, cursors: RefCell>, pub attention_requested: Cell, } @@ -96,7 +97,9 @@ pub async fn float_titles(state: Rc) { loop { let node = state.pending_float_titles.pop().await; if node.render_titles_scheduled.get() { - node.render_title(); + node.render_titles_scheduled.set(false); + node.render_title_phase1().triggered().await; + node.render_title_phase2(); } } } @@ -182,8 +185,8 @@ impl FloatNode { } } - fn render_title(&self) { - self.render_titles_scheduled.set(false); + fn render_title_phase1(&self) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let tc = match self.active.get() { @@ -191,20 +194,22 @@ impl FloatNode { false => theme.colors.unfocused_title_text.get(), }; let bw = theme.sizes.border_width.get(); - let font = theme.font.borrow_mut(); + let font = theme.font.get(); let title = self.title.borrow_mut(); let pos = self.position.get(); - if pos.width() <= 2 * bw || title.is_empty() { - return; + if pos.width() <= 2 * bw { + return on_completed.event(); } let ctx = match self.state.render_ctx.get() { Some(c) => c, - _ => return, + _ => return on_completed.event(), }; let scales = self.state.scales.lock(); let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap(); + let tt = &mut *self.title_textures.borrow_mut(); for (scale, _) in scales.iter() { - let old_tex = self.title_textures.remove(scale); + let tex = + tt.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); let mut th = tr.height(); let mut scalef = None; let mut width = tr.width(); @@ -217,16 +222,39 @@ impl FloatNode { if th == 0 || width == 0 { continue; } - let texture = match text::render(&ctx, old_tex, width, th, &font, &title, tc, scalef) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", title, ErrorFmt(e)); - return; - } - }; - self.title_textures.set(*scale, texture); + tex.schedule_render( + on_completed.clone(), + 1, + None, + width, + th, + 1, + &font, + &title, + tc, + true, + false, + scalef, + ); + } + on_completed.event() + } + + fn render_title_phase2(&self) { + let theme = &self.state.theme; + let th = theme.sizes.title_height.get(); + let bw = theme.sizes.border_width.get(); + let title = self.title.borrow(); + let tt = &*self.title_textures.borrow(); + for (_, tt) in tt { + if let Err(e) = tt.flip() { + log::error!("Could not render title {}: {}", title, ErrorFmt(e)); + } } - if self.visible.get() { + let pos = self.position.get(); + if self.visible.get() && pos.width() >= 2 * bw { + let tr = + Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap(); self.state.damage(tr); } } diff --git a/src/tree/output.rs b/src/tree/output.rs index 93b43aef..e68bfb19 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -30,15 +30,16 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - event_listener::EventSource, hash_map_ext::HashMapExt, linkedlist::LinkedList, - scroller::Scroller, transform_ext::TransformExt, + asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, event_listener::EventSource, hash_map_ext::HashMapExt, + linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, + transform_ext::TransformExt, }, wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id}, }, @@ -114,12 +115,14 @@ pub enum PointerType { pub async fn output_render_data(state: Rc) { loop { - let container = state.pending_output_render_data.pop().await; - if container.global.destroyed.get() { + let output = state.pending_output_render_data.pop().await; + if output.global.destroyed.get() { continue; } - if container.update_render_data_scheduled.get() { - container.update_render_data(); + if output.update_render_data_scheduled.get() { + output.update_render_data_scheduled.set(false); + output.update_render_data_phase1().triggered().await; + output.update_render_data_phase2(); } } } @@ -367,17 +370,69 @@ impl OutputNode { } } - fn update_render_data(&self) { - self.update_render_data_scheduled.set(false); + fn update_render_data_phase1(self: &Rc) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); + let Some(ctx) = self.state.render_ctx.get() else { + return on_completed.event(); + }; + let font = self.state.theme.font.get(); + let theme = &self.state.theme; + let th = theme.sizes.title_height.get(); + let scale = self.global.persistent.scale.get(); + let scale = if scale != 1 { + Some(scale.to_f64()) + } else { + None + }; + let mut texture_height = th; + if let Some(scale) = scale { + texture_height = (th as f64 * scale).round() as _; + } + let active_id = self.workspace.get().map(|w| w.id); + for ws in self.workspaces.iter() { + let tex = &mut *ws.title_texture.borrow_mut(); + let tex = tex.get_or_insert_with(|| TextTexture::new(&self.state.cpu_worker, &ctx)); + let tc = match active_id == Some(ws.id) { + true => theme.colors.focused_title_text.get(), + false => theme.colors.unfocused_title_text.get(), + }; + tex.schedule_render_fitting( + on_completed.clone(), + Some(texture_height), + &font, + &ws.name, + tc, + false, + scale, + ); + } + let mut rd = self.render_data.borrow_mut(); + let tex = rd.status.get_or_insert_with(|| OutputStatus { + tex_x: 0, + tex: TextTexture::new(&self.state.cpu_worker, &ctx), + }); + let status = self.status.get(); + let tc = self.state.theme.colors.bar_text.get(); + tex.tex.schedule_render_fitting( + on_completed.clone(), + Some(texture_height), + &font, + &status, + tc, + true, + scale, + ); + on_completed.event() + } + + fn update_render_data_phase2(&self) { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); rd.inactive_workspaces.clear(); rd.attention_requested_workspaces.clear(); rd.captured_inactive_workspaces.clear(); rd.active_workspace = None; - rd.status = None; let mut pos = 0; - let font = self.state.theme.font.borrow_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let scale = self.global.persistent.scale.get(); @@ -386,45 +441,20 @@ impl OutputNode { } else { None }; - let mut texture_height = th; - if let Some(scale) = scale { - texture_height = (th as f64 * scale).round() as _; - } let active_id = self.workspace.get().map(|w| w.id); let non_exclusive_rect = self.non_exclusive_rect.get(); let output_width = non_exclusive_rect.width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { - let old_tex = ws.title_texture.take(); let mut title_width = th; - 'create_texture: { - if let Some(ctx) = self.state.render_ctx.get() { - if th == 0 || ws.name.is_empty() { - break 'create_texture; - } - let tc = match active_id == Some(ws.id) { - true => theme.colors.focused_title_text.get(), - false => theme.colors.unfocused_title_text.get(), - }; - let title = match text::render_fitting( - &ctx, - old_tex, - Some(texture_height), - &font, - &ws.name, - tc, - false, - scale, - ) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); - break 'create_texture; - } - }; - ws.title_texture.set(Some(title.clone())); + let title = &*ws.title_texture.borrow(); + if let Some(title) = title { + if let Err(e) = title.flip() { + log::error!("Could not render title: {}", ErrorFmt(e)); + } + if let Some(texture) = title.texture() { let mut x = pos + 1; - let (mut width, _) = title.texture.size(); + let (mut width, _) = texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -438,7 +468,7 @@ impl OutputNode { x2: pos + title_width, tex_x: x, tex_y: 0, - tex: title.texture, + tex: texture, ws: ws.deref().clone(), }); } @@ -461,43 +491,18 @@ impl OutputNode { } pos += title_width; } - 'set_status: { - let old_tex = rd.status.take().map(|s| s.tex); - let ctx = match self.state.render_ctx.get() { - Some(ctx) => ctx, - _ => break 'set_status, - }; - let status = self.status.get(); - if status.is_empty() { - break 'set_status; + if let Some(status) = &mut rd.status { + if let Err(e) = status.tex.flip() { + log::error!("Could not render status: {}", ErrorFmt(e)); } - let tc = self.state.theme.colors.bar_text.get(); - let title = match text::render_fitting( - &ctx, - old_tex, - Some(texture_height), - &font, - &status, - tc, - true, - scale, - ) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render status {}: {}", status, ErrorFmt(e)); - break 'set_status; + if let Some(texture) = status.tex.texture() { + let (mut width, _) = texture.size(); + if let Some(scale) = scale { + width = (width as f64 / scale).round() as _; } - }; - let (mut width, _) = title.texture.size(); - if let Some(scale) = scale { - width = (width as f64 / scale).round() as _; + let pos = output_width - width - 1; + status.tex_x = pos; } - let pos = output_width - width - 1; - rd.status = Some(OutputStatus { - tex_x: pos, - tex_y: 0, - tex: title, - }); } if self.title_visible.get() { let title_rect = Rect::new_sized( @@ -945,7 +950,6 @@ pub struct OutputTitle { pub struct OutputStatus { pub tex_x: i32, - pub tex_y: i32, pub tex: TextTexture, } diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 549a301d..b579e7bd 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -8,14 +8,22 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, ToplevelNode, ToplevelNodeBase, }, - utils::{errorfmt::ErrorFmt, smallmap::SmallMap}, + utils::{ + asyncevent::AsyncEvent, errorfmt::ErrorFmt, on_drop_event::OnDropEvent, + smallmap::SmallMapMut, + }, + }, + std::{ + cell::{Cell, RefCell}, + ops::Deref, + rc::Rc, + sync::Arc, }, - std::{cell::Cell, ops::Deref, rc::Rc}, }; tree_id!(PlaceholderNodeId); @@ -24,7 +32,18 @@ pub struct PlaceholderNode { id: PlaceholderNodeId, toplevel: ToplevelData, destroyed: Cell, - pub textures: SmallMap, + update_textures_scheduled: Cell, + state: Rc, + pub textures: RefCell>, +} + +pub async fn placeholder_render_textures(state: Rc) { + loop { + let container = state.pending_placeholder_render_textures.pop().await; + container.update_textures_scheduled.set(false); + container.update_texture_phase1().triggered().await; + container.update_texture_phase2(); + } } impl PlaceholderNode { @@ -37,6 +56,8 @@ impl PlaceholderNode { node.node_client(), ), destroyed: Default::default(), + update_textures_scheduled: Cell::new(false), + state: state.clone(), textures: Default::default(), } } @@ -45,39 +66,53 @@ impl PlaceholderNode { self.destroyed.get() } - pub fn update_texture(&self) { - if let Some(ctx) = self.toplevel.state.render_ctx.get() { - let scales = self.toplevel.state.scales.lock(); - let rect = self.toplevel.pos.get(); - for (scale, _) in scales.iter() { - let old_tex = self.textures.remove(scale); - let mut width = rect.width(); - let mut height = rect.height(); - if *scale != 1 { - let scale = scale.to_f64(); - width = (width as f64 * scale).round() as _; - height = (height as f64 * scale).round() as _; - } - if width != 0 && height != 0 { - let font = format!("monospace {}", width / 10); - match text::render_fitting( - &ctx, - old_tex, - Some(height), - &font, - "Fullscreen", - self.toplevel.state.theme.colors.unfocused_title_text.get(), - false, - None, - ) { - Ok(t) => { - self.textures.insert(*scale, t); - } - Err(e) => { - log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e)); - } - } - } + pub fn schedule_update_texture(self: &Rc) { + if !self.update_textures_scheduled.replace(true) { + self.state + .pending_placeholder_render_textures + .push(self.clone()); + } + } + + fn update_texture_phase1(&self) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); + let Some(ctx) = self.toplevel.state.render_ctx.get() else { + return on_completed.event(); + }; + let scales = self.toplevel.state.scales.lock(); + let rect = self.toplevel.pos.get(); + let mut textures = self.textures.borrow_mut(); + for (scale, _) in scales.iter() { + let tex = textures + .get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let mut width = rect.width(); + let mut height = rect.height(); + if *scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 * scale).round() as _; + height = (height as f64 * scale).round() as _; + } + if width != 0 && height != 0 { + let font = Arc::new(format!("monospace {}", width / 10)); + tex.schedule_render_fitting( + on_completed.clone(), + Some(height), + &font, + "Fullscreen", + self.toplevel.state.theme.colors.unfocused_title_text.get(), + false, + None, + ); + } + } + on_completed.event() + } + + fn update_texture_phase2(&self) { + let textures = &*self.textures.borrow(); + for (_, texture) in textures { + if let Err(e) = texture.flip() { + log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e)); } } } @@ -162,7 +197,7 @@ impl ToplevelNodeBase for PlaceholderNode { if let Some(p) = self.toplevel.parent.get() { p.node_child_size_changed(self.deref(), rect.width(), rect.height()); } - self.update_texture(); + self.schedule_update_texture(); } fn tl_close(self: Rc) { diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 5f77afcb..6e96682e 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -57,7 +57,7 @@ pub struct WorkspaceNode { pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, pub may_capture: Cell, pub has_capture: Cell, - pub title_texture: Cell>, + pub title_texture: RefCell>, pub attention_requests: ThresholdCounter, pub render_highlight: NumCell, } diff --git a/src/utils.rs b/src/utils.rs index fa1fd936..d60be29e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -30,6 +30,7 @@ pub mod num_cpus; pub mod numcell; pub mod on_change; pub mod on_drop; +pub mod on_drop_event; pub mod once; pub mod opaque; pub mod opaque_cell; diff --git a/src/utils/clonecell.rs b/src/utils/clonecell.rs index 08597b75..1364aa81 100644 --- a/src/utils/clonecell.rs +++ b/src/utils/clonecell.rs @@ -9,6 +9,7 @@ use { fmt::{Debug, Formatter}, mem, rc::{Rc, Weak}, + sync::Arc, }, }; @@ -83,6 +84,7 @@ unsafe impl UnsafeCellCloneSafe for Option {} unsafe impl UnsafeCellCloneSafe for Rc {} unsafe impl UnsafeCellCloneSafe for Weak {} +unsafe impl UnsafeCellCloneSafe for Arc {} unsafe impl UnsafeCellCloneSafe for NodeRef {} diff --git a/src/utils/on_drop_event.rs b/src/utils/on_drop_event.rs new file mode 100644 index 00000000..e3ceea04 --- /dev/null +++ b/src/utils/on_drop_event.rs @@ -0,0 +1,18 @@ +use {crate::utils::asyncevent::AsyncEvent, std::rc::Rc}; + +#[derive(Default)] +pub struct OnDropEvent { + ae: Rc, +} + +impl OnDropEvent { + pub fn event(&self) -> Rc { + self.ae.clone() + } +} + +impl Drop for OnDropEvent { + fn drop(&mut self) { + self.ae.trigger() + } +} diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index 624c653a..c4ea872e 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -201,13 +201,20 @@ impl SmallMapMut { pub fn get_or_default_mut(&mut self, k: K) -> &mut V where V: Default, + { + self.get_or_insert_with(k, || V::default()) + } + + pub fn get_or_insert_with(&mut self, k: K, f: F) -> &mut V + where + F: FnOnce() -> V, { for (ek, ev) in &mut self.m { if ek == &k { return unsafe { (ev as *mut V).deref_mut() }; } } - self.m.push((k, V::default())); + self.m.push((k, f())); &mut self.m.last_mut().unwrap().1 }