Skip to main content

gpui_wgpu/
custom_draw.rs

1//! Backend-owned preparation and composition of independent custom paint sources.
2mod coverage_cache;
3#[cfg(all(unix, feature = "dma-buf"))]
4pub mod dma_buf;
5use anyhow::{Result, ensure};
6pub(crate) use coverage_cache::{CoverageCache, CoverageKey};
7use gpui::{CustomDraw, DevicePixels, Size, size};
8use std::sync::{
9    Arc, Mutex,
10    atomic::{AtomicU8, Ordering},
11};
12#[cfg(test)]
13use wgpu::util::DeviceExt;
14
15const PENDING: u8 = 0;
16const SUBMITTED: u8 = 1;
17const ABANDONED: u8 = 2;
18const STAGED: u8 = 3;
19
20struct Recording {
21    guards: Mutex<Vec<ImageCopyCompletion>>,
22    state: AtomicU8,
23    batch: Option<Arc<()>>,
24}
25impl Recording {
26    fn new(batch: Option<Arc<()>>) -> Self {
27        Self {
28            guards: Mutex::new(Vec::new()),
29            state: AtomicU8::new(PENDING),
30            batch,
31        }
32    }
33    fn can_reuse(self: &Arc<Self>, output: &Arc<Self>) -> bool {
34        match output.state.load(Ordering::Acquire) {
35            SUBMITTED => true,
36            PENDING => Arc::ptr_eq(output, self),
37            STAGED => self
38                .batch
39                .as_ref()
40                .zip(output.batch.as_ref())
41                .is_some_and(|(a, b)| Arc::ptr_eq(a, b)),
42            _ => false,
43        }
44    }
45    fn abandon(&self, from: u8) {
46        if self
47            .state
48            .compare_exchange(from, ABANDONED, Ordering::AcqRel, Ordering::Acquire)
49            .is_ok()
50        {
51            self.guards.lock().unwrap().clear();
52        }
53    }
54}
55
56impl std::fmt::Debug for Recording {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.debug_struct("Recording")
59            .field("state", &self.state)
60            .finish()
61    }
62}
63fn release_after_completion(queue: &wgpu::Queue, recording: &Recording) {
64    let guards = std::mem::take(&mut *recording.guards.lock().unwrap());
65    if !guards.is_empty() {
66        #[cfg(not(target_family = "wasm"))]
67        queue.on_submitted_work_done(move || drop(guards));
68        #[cfg(target_family = "wasm")]
69        browser_completions::retain(queue, guards);
70    }
71}
72
73// Browser GPU handles are thread-affine, including in atomic WASM builds.
74// The queue callback accepts Send closures, so only a completion signal crosses
75// that boundary. Retained uploads are destroyed on the thread that adopted them.
76#[cfg(any(target_family = "wasm", test))]
77mod browser_completions {
78    use std::sync::{
79        Arc,
80        atomic::{AtomicBool, Ordering},
81    };
82
83    pub(super) struct Pending<T> {
84        pub(super) done: Arc<AtomicBool>,
85        pub(super) value: T,
86    }
87
88    pub(super) fn take_completed<T>(pending: &mut Vec<Pending<T>>) -> Vec<T> {
89        let mut completed = Vec::new();
90        let mut i = 0;
91        while i < pending.len() {
92            if pending[i].done.load(Ordering::Acquire) {
93                completed.push(pending.swap_remove(i).value);
94            } else {
95                i += 1;
96            }
97        }
98        completed
99    }
100
101    #[cfg(target_family = "wasm")]
102    thread_local! {
103        static PENDING: std::cell::RefCell<Vec<Pending<Vec<super::ImageCopyCompletion>>>> =
104            const { std::cell::RefCell::new(Vec::new()) };
105    }
106
107    #[cfg(target_family = "wasm")]
108    pub(super) fn collect() {
109        // Drop outside the RefCell borrow: admission guards may run user code.
110        let completed = PENDING.with(|pending| take_completed(&mut pending.borrow_mut()));
111        drop(completed);
112    }
113
114    #[cfg(target_family = "wasm")]
115    pub(super) fn retain(queue: &wgpu::Queue, guards: Vec<super::ImageCopyCompletion>) {
116        let done = Arc::new(AtomicBool::new(false));
117        PENDING.with(|pending| {
118            pending.borrow_mut().push(Pending {
119                done: done.clone(),
120                value: guards,
121            })
122        });
123        queue.on_submitted_work_done(move || {
124            done.store(true, Ordering::Release);
125            // Browser callbacks normally run on the submitting thread. If a
126            // backend calls elsewhere, the owner's next preparation collects.
127            collect();
128        });
129    }
130}
131
132struct EncoderState {
133    encoder: Option<wgpu::CommandEncoder>,
134    finished: bool,
135    failed: bool,
136}
137
138/// A batch owns accepted recordings until all paint preparation succeeds.
139/// Dropping an unfinished batch invalidates its staged outputs without submitting.
140pub(crate) struct PreparationBatch {
141    device: Arc<wgpu::Device>,
142    queue: Arc<wgpu::Queue>,
143    id: Arc<()>,
144    pending: Vec<(Option<wgpu::CommandBuffer>, Arc<Recording>)>,
145}
146impl PreparationBatch {
147    pub fn new(device: Arc<wgpu::Device>, queue: &Arc<wgpu::Queue>) -> Self {
148        Self {
149            device,
150            queue: queue.clone(),
151            id: Arc::new(()),
152            pending: Vec::new(),
153        }
154    }
155    /// Run producer preparation and host output validation before staging its work.
156    pub fn prepare<T>(
157        &mut self,
158        prepare: impl FnOnce(&WgpuCustomContext) -> Result<T>,
159    ) -> Result<T> {
160        let context =
161            WgpuCustomContext::with_batch(self.device.clone(), &self.queue, Some(self.id.clone()));
162        let output = prepare(&context)?;
163        let command = context.finish()?;
164        context.recording.state.store(STAGED, Ordering::Release);
165        self.pending.push((command, context.recording.clone()));
166        Ok(output)
167    }
168    pub fn submit(mut self) -> Result<()> {
169        if self.pending.iter().any(|(command, _)| command.is_some()) {
170            self.queue.submit(
171                self.pending
172                    .iter_mut()
173                    .filter_map(|(command, _)| command.take()),
174            );
175        }
176        for (_, recording) in &self.pending {
177            recording.state.store(SUBMITTED, Ordering::Release);
178            release_after_completion(&self.queue, recording);
179        }
180        Ok(())
181    }
182}
183impl Drop for PreparationBatch {
184    fn drop(&mut self) {
185        for (_, recording) in &self.pending {
186            recording.abandon(STAGED);
187        }
188    }
189}
190
191/// Host-owned recording context. Producers record work; only the renderer submits.
192pub struct WgpuCustomContext {
193    /// Device on which producer pipelines and buffers must be created.
194    pub device: Arc<wgpu::Device>,
195    key: usize,
196    queue: Arc<wgpu::Queue>,
197    encoder: Mutex<EncoderState>,
198    recording: Arc<Recording>,
199}
200
201impl WgpuCustomContext {
202    #[cfg(test)]
203    pub(crate) fn new(device: Arc<wgpu::Device>, queue: &Arc<wgpu::Queue>) -> Self {
204        Self::with_batch(device, queue, None)
205    }
206    fn with_batch(
207        device: Arc<wgpu::Device>,
208        queue: &Arc<wgpu::Queue>,
209        batch: Option<Arc<()>>,
210    ) -> Self {
211        #[cfg(target_family = "wasm")]
212        browser_completions::collect();
213        Self {
214            device,
215            key: Arc::as_ptr(queue) as usize,
216            queue: queue.clone(),
217            encoder: Mutex::new(EncoderState {
218                encoder: None,
219                finished: false,
220                failed: false,
221            }),
222            recording: Arc::new(Recording::new(batch)),
223        }
224    }
225    /// Queue-scoped cache identity; cached output retains the device and queue.
226    pub fn cache_key(&self) -> usize {
227        self.key
228    }
229    /// Allocate a host-device output with straight-alpha encoded-sRGB RGBA8 channels.
230    pub fn create_output(&self, extent: Size<DevicePixels>) -> Result<WgpuCustomOutput> {
231        ensure!(
232            self.recording.state.load(Ordering::Acquire) == PENDING,
233            "custom preparation is no longer writable"
234        );
235        let texture = new_texture(&self.device, extent, wgpu::TextureFormat::Rgba8Unorm)?;
236        Ok(WgpuCustomOutput {
237            texture,
238            source_sized: false,
239            key: self.key,
240            _queue: self.queue.clone(),
241            recording: self.recording.clone(),
242        })
243    }
244    /// Owned worker-safe handles for allocating and filling image staging data.
245    /// The worker cannot submit work on the renderer queue.
246    pub fn image_upload_context(&self) -> WgpuImageUploadContext {
247        WgpuImageUploadContext {
248            device: self.device.clone(),
249            queue: self.queue.clone(),
250        }
251    }
252    /// Synchronous convenience route. Media workers normally stage first and
253    /// call `adopt_image_upload` during host preparation instead.
254    pub fn create_image_output(&self, data: &gpui::ImageTextureData) -> Result<WgpuCustomOutput> {
255        ensure!(
256            self.recording.state.load(Ordering::Acquire) == PENDING,
257            "image preparation is no longer writable"
258        );
259        let staged = self.image_upload_context().stage(data)?;
260        self.adopt_image_upload(&staged)
261    }
262    /// Record copies of already-filled buffers. No texture allocation or pixel
263    /// copying occurs here. A staged image may be adopted again after abandonment.
264    pub fn adopt_image_upload(&self, staged: &WgpuStagedImage) -> Result<WgpuCustomOutput> {
265        self.adopt_image_upload_with_guard(staged, None)
266    }
267    /// Retain an admission lease until GPU completion, or release it when this
268    /// recording is abandoned. Staging resources have their own completion guard.
269    pub fn adopt_image_upload_with_guard(
270        &self,
271        staged: &WgpuStagedImage,
272        guard: Option<Arc<dyn Send + Sync>>,
273    ) -> Result<WgpuCustomOutput> {
274        ensure!(
275            Arc::ptr_eq(&staged.queue, &self.queue),
276            "foreign staged image queue"
277        );
278        ensure!(
279            Arc::ptr_eq(&staged.device, &self.device),
280            "foreign staged image device"
281        );
282        self.record(|encoder| {
283            for (level, mip) in staged.mips.iter().enumerate() {
284                encoder.copy_buffer_to_texture(
285                    wgpu::TexelCopyBufferInfo {
286                        buffer: staged.allocation.resource(),
287                        layout: wgpu::TexelCopyBufferLayout {
288                            offset: mip.offset,
289                            bytes_per_row: Some(mip.row),
290                            rows_per_image: None,
291                        },
292                    },
293                    wgpu::TexelCopyTextureInfo {
294                        texture: &staged.texture,
295                        mip_level: level as u32,
296                        origin: wgpu::Origin3d::ZERO,
297                        aspect: wgpu::TextureAspect::All,
298                    },
299                    wgpu::Extent3d {
300                        width: mip.width,
301                        height: mip.height,
302                        depth_or_array_layers: 1,
303                    },
304                );
305            }
306            Ok(())
307        })?;
308        // Struct fields drop in declaration order: return the source buffer
309        // before releasing admission, so the next admitted worker can check it out.
310        self.recording
311            .guards
312            .lock()
313            .unwrap()
314            .push(ImageCopyCompletion {
315                _staging: staged.allocation.clone(),
316                _admission: guard,
317            });
318        Ok(WgpuCustomOutput {
319            texture: staged.texture.clone(),
320            source_sized: true,
321            key: self.key,
322            _queue: self.queue.clone(),
323            recording: self.recording.clone(),
324        })
325    }
326    /// Record preparation commands. Do not submit work or wait on the GPU here.
327    /// An encoding error invalidates this transaction, including commands recorded earlier.
328    pub fn record<T>(
329        &self,
330        encode: impl FnOnce(&mut wgpu::CommandEncoder) -> Result<T>,
331    ) -> Result<T> {
332        let mut state = self
333            .encoder
334            .lock()
335            .map_err(|_| anyhow::anyhow!("custom encoder poisoned"))?;
336        ensure!(
337            !state.finished && !state.failed,
338            "custom preparation already finished or failed"
339        );
340        let encoder = state.encoder.get_or_insert_with(|| {
341            self.device
342                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
343                    label: Some("custom_preparation"),
344                })
345        });
346        let result = encode(encoder);
347        if result.is_err() {
348            state.failed = true;
349            state.encoder.take();
350            self.recording.abandon(PENDING);
351        }
352        result
353    }
354    /// Whether output is submitted, local to this recording, or staged earlier in this host batch.
355    pub fn can_reuse(&self, output: &WgpuCustomOutput) -> bool {
356        output.key == self.key && self.recording.can_reuse(&output.recording)
357    }
358    fn finish(&self) -> Result<Option<wgpu::CommandBuffer>> {
359        let mut state = self
360            .encoder
361            .lock()
362            .map_err(|_| anyhow::anyhow!("custom encoder poisoned"))?;
363        ensure!(
364            !state.finished && !state.failed,
365            "custom preparation already finished or failed"
366        );
367        state.finished = true;
368        Ok(state.encoder.take().map(|encoder| encoder.finish()))
369    }
370    #[cfg(test)]
371    pub(crate) fn submit(&self) -> Result<()> {
372        if let Some(command) = self.finish()? {
373            self.queue.submit([command]);
374        }
375        self.recording.state.store(SUBMITTED, Ordering::Release);
376        release_after_completion(&self.queue, &self.recording);
377        Ok(())
378    }
379}
380
381impl Drop for WgpuCustomContext {
382    fn drop(&mut self) {
383        self.recording.abandon(PENDING);
384    }
385}
386
387/// Worker-owned image allocation context; it exposes no queue submission API.
388#[derive(Clone)]
389pub struct WgpuImageUploadContext {
390    device: Arc<wgpu::Device>,
391    queue: Arc<wgpu::Queue>,
392}
393struct ImageCopyCompletion {
394    _staging: Arc<gpui::ImageStagingLease<wgpu::Buffer>>,
395    _admission: Option<Arc<dyn Send + Sync>>,
396}
397struct StagedMip {
398    offset: u64,
399    row: u32,
400    width: u32,
401    height: u32,
402}
403/// Device-bound cache of exclusive staging buffers. Completion guards prevent
404/// a buffer from returning to the cache while GPU commands still reference it.
405#[derive(Clone)]
406pub struct WgpuImageStagingPool {
407    device: Arc<wgpu::Device>,
408    queue: Arc<wgpu::Queue>,
409    buffers: Arc<gpui::ImageStagingPool<wgpu::Buffer>>,
410}
411impl WgpuImageStagingPool {
412    pub fn stats(&self) -> gpui::ImageStagingPoolStats {
413        self.buffers.stats()
414    }
415}
416/// Immutable worker-filled staging plus a sampleable destination texture.
417pub struct WgpuStagedImage {
418    device: Arc<wgpu::Device>,
419    queue: Arc<wgpu::Queue>,
420    texture: wgpu::Texture,
421    allocation: Arc<gpui::ImageStagingLease<wgpu::Buffer>>,
422    mips: Vec<StagedMip>,
423}
424impl WgpuImageUploadContext {
425    pub fn staging_pool(&self, capacity: usize) -> WgpuImageStagingPool {
426        WgpuImageStagingPool {
427            device: self.device.clone(),
428            queue: self.queue.clone(),
429            buffers: gpui::ImageStagingPool::new(capacity),
430        }
431    }
432    /// Unpooled convenience path; full media images use `stage_with_pool`.
433    pub fn stage(&self, data: &gpui::ImageTextureData) -> Result<WgpuStagedImage> {
434        self.stage_with_pool(data, &self.staging_pool(data.staging_byte_len()))
435    }
436    /// Worker-only pooled staging. Mapping a reused buffer can wait on this
437    /// worker for driver progress; this method must not run on the UI thread.
438    pub fn stage_with_pool(
439        &self,
440        data: &gpui::ImageTextureData,
441        pool: &WgpuImageStagingPool,
442    ) -> Result<WgpuStagedImage> {
443        ensure!(
444            Arc::ptr_eq(&self.device, &pool.device) && Arc::ptr_eq(&self.queue, &pool.queue),
445            "foreign staging pool"
446        );
447        ensure!(
448            data.width().max(data.height()) <= self.device.limits().max_texture_dimension_2d,
449            "image exceeds device texture limits"
450        );
451        #[cfg(target_family = "wasm")]
452        browser_completions::collect();
453        let bytes = data.staging_byte_len();
454        ensure!(
455            bytes as u64 <= self.device.limits().max_buffer_size,
456            "image staging exceeds device buffer limit"
457        );
458        let allocation = pool.buffers.checkout(bytes as u64, bytes, || {
459            Ok(self.device.create_buffer(&wgpu::BufferDescriptor {
460                label: Some("media_staging_pool"),
461                size: bytes as u64,
462                usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
463                mapped_at_creation: true,
464            }))
465        })?;
466        let fill = (|| -> Result<Vec<StagedMip>> {
467            let buffer = allocation.resource();
468            if allocation.was_reused() {
469                let (sender, receiver) = std::sync::mpsc::channel();
470                buffer
471                    .slice(..)
472                    .map_async(wgpu::MapMode::Write, move |result| {
473                        let _ = sender.send(result);
474                    });
475                self.device.poll(wgpu::PollType::Wait {
476                    submission_index: None,
477                    timeout: Some(std::time::Duration::from_secs(5)),
478                })?;
479                receiver.recv_timeout(std::time::Duration::from_secs(5))??;
480            }
481            let mut mapped = buffer.slice(..).get_mapped_range_mut()?;
482            let mut mips = Vec::new();
483            let mut offset = 0usize;
484            for (level, pixels) in data.levels().iter().enumerate() {
485                let width = (data.width() >> level).max(1);
486                let height = (data.height() >> level).max(1);
487                let bpp = data.bytes_per_pixel();
488                let row = (width * bpp as u32).div_ceil(256) * 256;
489                for y in 0..height as usize {
490                    mapped
491                        .slice(
492                            offset + y * row as usize
493                                ..offset + y * row as usize + width as usize * bpp,
494                        )
495                        .copy_from_slice(
496                            &pixels[y * width as usize * bpp..(y + 1) * width as usize * bpp],
497                        );
498                }
499                mips.push(StagedMip {
500                    offset: offset as u64,
501                    row,
502                    width,
503                    height,
504                });
505                offset += row as usize * height as usize;
506            }
507            drop(mapped);
508            buffer.unmap();
509            Ok(mips)
510        })();
511        let mips = match fill {
512            Ok(mips) => mips,
513            Err(error) => {
514                allocation.resource().unmap();
515                allocation.discard();
516                return Err(error);
517            }
518        };
519        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
520            label: Some("media_image"),
521            size: wgpu::Extent3d {
522                width: data.width(),
523                height: data.height(),
524                depth_or_array_layers: 1,
525            },
526            mip_level_count: data.levels().len() as u32,
527            sample_count: 1,
528            dimension: wgpu::TextureDimension::D2,
529            format: match data.format() {
530                gpui::ImageTextureFormat::Rgba8Srgb => wgpu::TextureFormat::Rgba8Unorm,
531                gpui::ImageTextureFormat::LinearP3Rgba16Float
532                | gpui::ImageTextureFormat::LinearRec2020Rgba16Float => {
533                    wgpu::TextureFormat::Rgba16Float
534                }
535            },
536            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
537            view_formats: &[],
538        });
539        Ok(WgpuStagedImage {
540            device: self.device.clone(),
541            queue: self.queue.clone(),
542            texture,
543            allocation: Arc::new(allocation),
544            mips,
545        })
546    }
547}
548
549/// Cacheable immutable producer output. Construct through the host context.
550#[derive(Clone, Debug)]
551pub struct WgpuCustomOutput {
552    texture: wgpu::Texture,
553    source_sized: bool,
554    key: usize,
555    _queue: Arc<wgpu::Queue>,
556    recording: Arc<Recording>,
557}
558impl WgpuCustomOutput {
559    /// Host submission has accepted this recording (GPU completion is separate).
560    pub fn is_submitted(&self) -> bool {
561        self.recording.state.load(Ordering::Acquire) == SUBMITTED
562    }
563
564    /// Texture to render into during preparation; immutable after host submission.
565    pub fn texture(&self) -> &wgpu::Texture {
566        &self.texture
567    }
568    pub(crate) fn validate(
569        self,
570        context: &WgpuCustomContext,
571        extent: Size<DevicePixels>,
572    ) -> Result<Self> {
573        ensure!(
574            context.can_reuse(&self),
575            "custom output belongs to another queue or abandoned recording"
576        );
577        ensure!(
578            self.source_sized
579                || (self.texture.width() == extent.width.0 as u32
580                    && self.texture.height() == extent.height.0 as u32),
581            "invalid custom texture extent"
582        );
583        Ok(self)
584    }
585}
586
587pub(crate) fn new_texture(
588    device: &wgpu::Device,
589    extent: Size<DevicePixels>,
590    format: wgpu::TextureFormat,
591) -> Result<wgpu::Texture> {
592    ensure!(
593        extent.width.0 > 0
594            && extent.height.0 > 0
595            && extent.width.0 as u32 <= device.limits().max_texture_dimension_2d
596            && extent.height.0 as u32 <= device.limits().max_texture_dimension_2d
597            && extent.width.0 as u64 * extent.height.0 as u64 * 4 <= 64 * 1024 * 1024,
598        "invalid custom texture extent"
599    );
600    Ok(device.create_texture(&wgpu::TextureDescriptor {
601        label: Some("custom_paint"),
602        size: wgpu::Extent3d {
603            width: extent.width.0 as u32,
604            height: extent.height.0 as u32,
605            depth_or_array_layers: 1,
606        },
607        mip_level_count: 1,
608        sample_count: 1,
609        dimension: wgpu::TextureDimension::D2,
610        format,
611        usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
612        view_formats: &[],
613    }))
614}
615
616pub(crate) fn extent(draw: &CustomDraw) -> Result<Size<DevicePixels>> {
617    ensure!(
618        draw.opacity.is_finite() && (0.0..=1.0).contains(&draw.opacity),
619        "invalid custom opacity"
620    );
621    ensure!(
622        [
623            draw.bounds.origin.x.0,
624            draw.bounds.origin.y.0,
625            draw.bounds.size.width.0,
626            draw.bounds.size.height.0,
627            draw.content_mask.bounds.origin.x.0,
628            draw.content_mask.bounds.origin.y.0,
629            draw.content_mask.bounds.size.width.0,
630            draw.content_mask.bounds.size.height.0,
631            draw.corner_radii.top_left.0,
632            draw.corner_radii.top_right.0,
633            draw.corner_radii.bottom_right.0,
634            draw.corner_radii.bottom_left.0
635        ]
636        .iter()
637        .all(|v| v.is_finite()),
638        "non-finite custom geometry"
639    );
640    if let Some(source) = draw.content.image_source_size() {
641        ensure!(
642            draw.coverage.is_none()
643                && draw.bounds.size.width.0.is_finite()
644                && draw.bounds.size.height.0.is_finite()
645                && draw.bounds.size.width.0 > 0.
646                && draw.bounds.size.height.0 > 0.
647                && source.width.0 > 0
648                && source.height.0 > 0
649                && source.width.0 <= 16384
650                && source.height.0 <= 16384,
651            "invalid source-sized image preparation"
652        );
653        return Ok(source);
654    }
655    ensure!(
656        draw.bounds.size.width.0 > 0.
657            && draw.bounds.size.height.0 > 0.
658            && draw.bounds.size.width.0 <= 16384.
659            && draw.bounds.size.height.0 <= 16384.,
660        "invalid custom extent"
661    );
662    Ok(size(
663        DevicePixels(draw.bounds.size.width.0.ceil() as i32),
664        DevicePixels(draw.bounds.size.height.0.ceil() as i32),
665    ))
666}
667
668pub(crate) struct PreparedDraw {
669    pub bind_group: wgpu::BindGroup,
670    pub uniform_offset: u32,
671}
672
673const UNIFORM_BYTES: u64 = 144;
674/// Renderer-private arena. Reuse only after previous render commands are submitted;
675/// queue-ordered uploads then preserve reads by still-running previous frames.
676struct UniformArena {
677    buffer: Option<wgpu::Buffer>,
678    bytes: Vec<u8>,
679    stride: u64,
680    capacity: u64,
681    cursor: usize,
682    #[cfg(test)]
683    creations: usize,
684}
685impl UniformArena {
686    fn new(device: &wgpu::Device) -> Self {
687        Self {
688            buffer: None,
689            bytes: Vec::new(),
690            stride: UNIFORM_BYTES
691                .next_multiple_of(device.limits().min_uniform_buffer_offset_alignment as u64),
692            capacity: 0,
693            cursor: 0,
694            #[cfg(test)]
695            creations: 0,
696        }
697    }
698    fn begin(&mut self, device: &wgpu::Device, count: usize) -> Result<()> {
699        let required = (count as u64)
700            .checked_mul(self.stride)
701            .ok_or_else(|| anyhow::anyhow!("custom uniform size overflow"))?;
702        let limit = device.limits().max_buffer_size.min(u32::MAX as u64);
703        ensure!(
704            required <= limit,
705            "custom uniforms exceed buffer/offset limits"
706        );
707        if required > self.capacity {
708            let capacity = required
709                .checked_next_power_of_two()
710                .unwrap_or(required)
711                .min(limit);
712            self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
713                label: Some("custom_params_arena"),
714                size: capacity,
715                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
716                mapped_at_creation: false,
717            }));
718            self.capacity = capacity;
719            #[cfg(test)]
720            {
721                self.creations += 1;
722            }
723        }
724        self.bytes.resize(required as usize, 0);
725        self.cursor = 0;
726        Ok(())
727    }
728    fn write(&mut self, params: &[[f32; 4]; 9]) -> u32 {
729        let offset = self.cursor * self.stride as usize;
730        self.cursor += 1;
731        self.bytes[offset..offset + UNIFORM_BYTES as usize]
732            .copy_from_slice(bytemuck::cast_slice(params));
733        offset as u32
734    }
735    fn upload(&self, queue: &wgpu::Queue) {
736        debug_assert_eq!(self.cursor as u64 * self.stride, self.bytes.len() as u64);
737        if !self.bytes.is_empty() {
738            queue.write_buffer(self.buffer.as_ref().expect("uniform arena"), 0, &self.bytes);
739        }
740    }
741}
742pub(crate) struct CustomCompositor {
743    pub pipeline: wgpu::RenderPipeline,
744    layout: wgpu::BindGroupLayout,
745    sampler: wgpu::Sampler,
746    uniforms: UniformArena,
747    pub masks: CoverageCache<wgpu::Texture>,
748    pub mask_bytes: usize,
749}
750impl CustomCompositor {
751    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
752        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
753            label: Some("custom_compositor"),
754            source: wgpu::ShaderSource::Wgsl(include_str!("custom_draw.wgsl").into()),
755        });
756        let entries = [
757            wgpu::BindGroupLayoutEntry {
758                binding: 0,
759                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
760                ty: wgpu::BindingType::Buffer {
761                    ty: wgpu::BufferBindingType::Uniform,
762                    has_dynamic_offset: true,
763                    min_binding_size: wgpu::BufferSize::new(UNIFORM_BYTES),
764                },
765                count: None,
766            },
767            wgpu::BindGroupLayoutEntry {
768                binding: 1,
769                visibility: wgpu::ShaderStages::FRAGMENT,
770                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
771                count: None,
772            },
773            wgpu::BindGroupLayoutEntry {
774                binding: 2,
775                visibility: wgpu::ShaderStages::FRAGMENT,
776                ty: wgpu::BindingType::Texture {
777                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
778                    view_dimension: wgpu::TextureViewDimension::D2,
779                    multisampled: false,
780                },
781                count: None,
782            },
783            wgpu::BindGroupLayoutEntry {
784                binding: 3,
785                visibility: wgpu::ShaderStages::FRAGMENT,
786                ty: wgpu::BindingType::Texture {
787                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
788                    view_dimension: wgpu::TextureViewDimension::D2,
789                    multisampled: false,
790                },
791                count: None,
792            },
793        ];
794        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
795            label: Some("custom_paint"),
796            entries: &entries,
797        });
798        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
799            label: Some("custom_paint"),
800            bind_group_layouts: &[Some(&layout)],
801            immediate_size: 0,
802        });
803        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
804            label: Some("custom_paint"),
805            layout: Some(&pipeline_layout),
806            vertex: wgpu::VertexState {
807                module: &shader,
808                entry_point: Some("custom_vertex"),
809                buffers: &[],
810                compilation_options: Default::default(),
811            },
812            fragment: Some(wgpu::FragmentState {
813                module: &shader,
814                entry_point: Some("custom_fragment"),
815                targets: &[Some(wgpu::ColorTargetState {
816                    format,
817                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
818                    write_mask: wgpu::ColorWrites::ALL,
819                })],
820                compilation_options: Default::default(),
821            }),
822            primitive: Default::default(),
823            depth_stencil: None,
824            multisample: Default::default(),
825            multiview_mask: None,
826            cache: None,
827        });
828        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
829            label: Some("custom_paint"),
830            mag_filter: wgpu::FilterMode::Linear,
831            min_filter: wgpu::FilterMode::Linear,
832            mipmap_filter: wgpu::MipmapFilterMode::Linear,
833            ..Default::default()
834        });
835        Self {
836            pipeline,
837            layout,
838            sampler,
839            uniforms: UniformArena::new(device),
840            masks: Default::default(),
841            mask_bytes: 0,
842        }
843    }
844    pub(crate) fn begin_uniforms(&mut self, device: &wgpu::Device, count: usize) -> Result<()> {
845        self.uniforms.begin(device, count)
846    }
847    pub(crate) fn upload_uniforms(&self, queue: &wgpu::Queue) {
848        self.uniforms.upload(queue);
849    }
850    pub fn prepare(
851        &mut self,
852        device: &wgpu::Device,
853        draw: &CustomDraw,
854        source: &wgpu::Texture,
855        mask: Option<&wgpu::Texture>,
856        native: bool,
857        viewport: Size<DevicePixels>,
858    ) -> PreparedDraw {
859        let adjustments = draw.content.image_linear_adjustments();
860        let transform = adjustments
861            .map(|v| v.rows())
862            .unwrap_or_else(|| draw.content.image_color_transform());
863        let vignette = adjustments
864            .map(|v| v.vignette())
865            .unwrap_or([0., 0., 1., 1.]);
866        let linear = [
867            if adjustments.is_some() { 1. } else { 0. },
868            draw.content.image_color_mode().shader_value(),
869            if adjustments.is_some_and(|v| v.is_p3()) {
870                1.
871            } else {
872                0.
873            },
874            0.,
875        ];
876        let params: [[f32; 4]; 9] = [
877            [
878                draw.bounds.origin.x.0,
879                draw.bounds.origin.y.0,
880                draw.bounds.size.width.0,
881                draw.bounds.size.height.0,
882            ],
883            [
884                draw.content_mask.bounds.origin.x.0,
885                draw.content_mask.bounds.origin.y.0,
886                draw.content_mask.bounds.size.width.0,
887                draw.content_mask.bounds.size.height.0,
888            ],
889            [
890                draw.corner_radii.top_left.0,
891                draw.corner_radii.top_right.0,
892                draw.corner_radii.bottom_right.0,
893                draw.corner_radii.bottom_left.0,
894            ],
895            [
896                viewport.width.0 as f32,
897                viewport.height.0 as f32,
898                draw.opacity * draw.content.image_sample_opacity().clamp(0., 1.),
899                u32::from(mask.is_some()) as f32 + if native { 2. } else { 0. },
900            ],
901            transform[0],
902            transform[1],
903            transform[2],
904            vignette,
905            linear,
906        ];
907        let uniform_offset = self.uniforms.write(&params);
908        let uniform = self.uniforms.buffer.as_ref().expect("uniform arena");
909        let source = source.create_view(&Default::default());
910        let mask = mask
911            .map(|t| t.create_view(&Default::default()))
912            .unwrap_or_else(|| source.clone());
913        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
914            label: Some("custom_paint"),
915            layout: &self.layout,
916            entries: &[
917                wgpu::BindGroupEntry {
918                    binding: 0,
919                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
920                        buffer: uniform,
921                        offset: 0,
922                        size: wgpu::BufferSize::new(UNIFORM_BYTES),
923                    }),
924                },
925                wgpu::BindGroupEntry {
926                    binding: 1,
927                    resource: wgpu::BindingResource::Sampler(&self.sampler),
928                },
929                wgpu::BindGroupEntry {
930                    binding: 2,
931                    resource: wgpu::BindingResource::TextureView(&source),
932                },
933                wgpu::BindGroupEntry {
934                    binding: 3,
935                    resource: wgpu::BindingResource::TextureView(&mask),
936                },
937            ],
938        });
939        PreparedDraw {
940            bind_group,
941            uniform_offset,
942        }
943    }
944}
945
946pub(crate) fn coverage_scene(draw: &CustomDraw, extent: Size<DevicePixels>) -> Result<gpui::Scene> {
947    use gpui::*;
948    let mut scene = Scene::default();
949    let bounds = Bounds::new(
950        point(ScaledPixels(0.), ScaledPixels(0.)),
951        size(
952            ScaledPixels(extent.width.0 as f32),
953            ScaledPixels(extent.height.0 as f32),
954        ),
955    );
956    let mask = ContentMask { bounds };
957    match draw.coverage.as_ref().expect("coverage") {
958        CustomCoverage::Border { widths, style } => {
959            ensure!(
960                [widths.top.0, widths.right.0, widths.bottom.0, widths.left.0]
961                    .iter()
962                    .all(|v| v.is_finite() && *v >= 0.),
963                "invalid border widths"
964            );
965            scene.insert_primitive(Quad {
966                bounds,
967                content_mask: mask,
968                background: transparent_black().into(),
969                border_color: white(),
970                border_widths: *widths,
971                corner_radii: draw.corner_radii,
972                border_style: *style,
973                ..Default::default()
974            });
975        }
976        CustomCoverage::Path(path) => {
977            ensure!(path.vertices.len() <= 1_000_000, "coverage path too large");
978            let mut path = path.as_ref().clone();
979            let sx = bounds.size.width.0 / draw.bounds.size.width.0;
980            let sy = bounds.size.height.0 / draw.bounds.size.height.0;
981            for vertex in &mut path.vertices {
982                vertex.xy_position.x =
983                    ScaledPixels((vertex.xy_position.x.0 - draw.bounds.origin.x.0) * sx);
984                vertex.xy_position.y =
985                    ScaledPixels((vertex.xy_position.y.0 - draw.bounds.origin.y.0) * sy);
986                ensure!(
987                    [
988                        vertex.xy_position.x.0,
989                        vertex.xy_position.y.0,
990                        vertex.st_position.x,
991                        vertex.st_position.y
992                    ]
993                    .iter()
994                    .all(|v| v.is_finite()),
995                    "non-finite coverage path"
996                );
997                vertex.content_mask = mask;
998            }
999            path.bounds = bounds;
1000            path.content_mask = mask;
1001            path.color = white().into();
1002            scene.insert_primitive(path);
1003        }
1004    }
1005    scene.finish();
1006    Ok(scene)
1007}
1008
1009#[cfg(test)]
1010mod tests;