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 => wgpu::TextureFormat::Rgba16Float,
532            },
533            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
534            view_formats: &[],
535        });
536        Ok(WgpuStagedImage {
537            device: self.device.clone(),
538            queue: self.queue.clone(),
539            texture,
540            allocation: Arc::new(allocation),
541            mips,
542        })
543    }
544}
545
546/// Cacheable immutable producer output. Construct through the host context.
547#[derive(Clone, Debug)]
548pub struct WgpuCustomOutput {
549    texture: wgpu::Texture,
550    source_sized: bool,
551    key: usize,
552    _queue: Arc<wgpu::Queue>,
553    recording: Arc<Recording>,
554}
555impl WgpuCustomOutput {
556    /// Host submission has accepted this recording (GPU completion is separate).
557    pub fn is_submitted(&self) -> bool {
558        self.recording.state.load(Ordering::Acquire) == SUBMITTED
559    }
560
561    /// Texture to render into during preparation; immutable after host submission.
562    pub fn texture(&self) -> &wgpu::Texture {
563        &self.texture
564    }
565    pub(crate) fn validate(
566        self,
567        context: &WgpuCustomContext,
568        extent: Size<DevicePixels>,
569    ) -> Result<Self> {
570        ensure!(
571            context.can_reuse(&self),
572            "custom output belongs to another queue or abandoned recording"
573        );
574        ensure!(
575            self.source_sized
576                || (self.texture.width() == extent.width.0 as u32
577                    && self.texture.height() == extent.height.0 as u32),
578            "invalid custom texture extent"
579        );
580        Ok(self)
581    }
582}
583
584pub(crate) fn new_texture(
585    device: &wgpu::Device,
586    extent: Size<DevicePixels>,
587    format: wgpu::TextureFormat,
588) -> Result<wgpu::Texture> {
589    ensure!(
590        extent.width.0 > 0
591            && extent.height.0 > 0
592            && extent.width.0 as u32 <= device.limits().max_texture_dimension_2d
593            && extent.height.0 as u32 <= device.limits().max_texture_dimension_2d
594            && extent.width.0 as u64 * extent.height.0 as u64 * 4 <= 64 * 1024 * 1024,
595        "invalid custom texture extent"
596    );
597    Ok(device.create_texture(&wgpu::TextureDescriptor {
598        label: Some("custom_paint"),
599        size: wgpu::Extent3d {
600            width: extent.width.0 as u32,
601            height: extent.height.0 as u32,
602            depth_or_array_layers: 1,
603        },
604        mip_level_count: 1,
605        sample_count: 1,
606        dimension: wgpu::TextureDimension::D2,
607        format,
608        usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
609        view_formats: &[],
610    }))
611}
612
613pub(crate) fn extent(draw: &CustomDraw) -> Result<Size<DevicePixels>> {
614    ensure!(
615        draw.opacity.is_finite() && (0.0..=1.0).contains(&draw.opacity),
616        "invalid custom opacity"
617    );
618    ensure!(
619        [
620            draw.bounds.origin.x.0,
621            draw.bounds.origin.y.0,
622            draw.bounds.size.width.0,
623            draw.bounds.size.height.0,
624            draw.content_mask.bounds.origin.x.0,
625            draw.content_mask.bounds.origin.y.0,
626            draw.content_mask.bounds.size.width.0,
627            draw.content_mask.bounds.size.height.0,
628            draw.corner_radii.top_left.0,
629            draw.corner_radii.top_right.0,
630            draw.corner_radii.bottom_right.0,
631            draw.corner_radii.bottom_left.0
632        ]
633        .iter()
634        .all(|v| v.is_finite()),
635        "non-finite custom geometry"
636    );
637    if let Some(source) = draw.content.image_source_size() {
638        ensure!(
639            draw.coverage.is_none()
640                && draw.bounds.size.width.0.is_finite()
641                && draw.bounds.size.height.0.is_finite()
642                && draw.bounds.size.width.0 > 0.
643                && draw.bounds.size.height.0 > 0.
644                && source.width.0 > 0
645                && source.height.0 > 0
646                && source.width.0 <= 16384
647                && source.height.0 <= 16384,
648            "invalid source-sized image preparation"
649        );
650        return Ok(source);
651    }
652    ensure!(
653        draw.bounds.size.width.0 > 0.
654            && draw.bounds.size.height.0 > 0.
655            && draw.bounds.size.width.0 <= 16384.
656            && draw.bounds.size.height.0 <= 16384.,
657        "invalid custom extent"
658    );
659    Ok(size(
660        DevicePixels(draw.bounds.size.width.0.ceil() as i32),
661        DevicePixels(draw.bounds.size.height.0.ceil() as i32),
662    ))
663}
664
665pub(crate) struct PreparedDraw {
666    pub bind_group: wgpu::BindGroup,
667    pub uniform_offset: u32,
668}
669
670const UNIFORM_BYTES: u64 = 144;
671/// Renderer-private arena. Reuse only after previous render commands are submitted;
672/// queue-ordered uploads then preserve reads by still-running previous frames.
673struct UniformArena {
674    buffer: Option<wgpu::Buffer>,
675    bytes: Vec<u8>,
676    stride: u64,
677    capacity: u64,
678    cursor: usize,
679    #[cfg(test)]
680    creations: usize,
681}
682impl UniformArena {
683    fn new(device: &wgpu::Device) -> Self {
684        Self {
685            buffer: None,
686            bytes: Vec::new(),
687            stride: UNIFORM_BYTES
688                .next_multiple_of(device.limits().min_uniform_buffer_offset_alignment as u64),
689            capacity: 0,
690            cursor: 0,
691            #[cfg(test)]
692            creations: 0,
693        }
694    }
695    fn begin(&mut self, device: &wgpu::Device, count: usize) -> Result<()> {
696        let required = (count as u64)
697            .checked_mul(self.stride)
698            .ok_or_else(|| anyhow::anyhow!("custom uniform size overflow"))?;
699        let limit = device.limits().max_buffer_size.min(u32::MAX as u64);
700        ensure!(
701            required <= limit,
702            "custom uniforms exceed buffer/offset limits"
703        );
704        if required > self.capacity {
705            let capacity = required
706                .checked_next_power_of_two()
707                .unwrap_or(required)
708                .min(limit);
709            self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
710                label: Some("custom_params_arena"),
711                size: capacity,
712                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
713                mapped_at_creation: false,
714            }));
715            self.capacity = capacity;
716            #[cfg(test)]
717            {
718                self.creations += 1;
719            }
720        }
721        self.bytes.resize(required as usize, 0);
722        self.cursor = 0;
723        Ok(())
724    }
725    fn write(&mut self, params: &[[f32; 4]; 9]) -> u32 {
726        let offset = self.cursor * self.stride as usize;
727        self.cursor += 1;
728        self.bytes[offset..offset + UNIFORM_BYTES as usize]
729            .copy_from_slice(bytemuck::cast_slice(params));
730        offset as u32
731    }
732    fn upload(&self, queue: &wgpu::Queue) {
733        debug_assert_eq!(self.cursor as u64 * self.stride, self.bytes.len() as u64);
734        if !self.bytes.is_empty() {
735            queue.write_buffer(self.buffer.as_ref().expect("uniform arena"), 0, &self.bytes);
736        }
737    }
738}
739pub(crate) struct CustomCompositor {
740    pub pipeline: wgpu::RenderPipeline,
741    layout: wgpu::BindGroupLayout,
742    sampler: wgpu::Sampler,
743    uniforms: UniformArena,
744    pub masks: CoverageCache<wgpu::Texture>,
745    pub mask_bytes: usize,
746}
747impl CustomCompositor {
748    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
749        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
750            label: Some("custom_compositor"),
751            source: wgpu::ShaderSource::Wgsl(include_str!("custom_draw.wgsl").into()),
752        });
753        let entries = [
754            wgpu::BindGroupLayoutEntry {
755                binding: 0,
756                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
757                ty: wgpu::BindingType::Buffer {
758                    ty: wgpu::BufferBindingType::Uniform,
759                    has_dynamic_offset: true,
760                    min_binding_size: wgpu::BufferSize::new(UNIFORM_BYTES),
761                },
762                count: None,
763            },
764            wgpu::BindGroupLayoutEntry {
765                binding: 1,
766                visibility: wgpu::ShaderStages::FRAGMENT,
767                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
768                count: None,
769            },
770            wgpu::BindGroupLayoutEntry {
771                binding: 2,
772                visibility: wgpu::ShaderStages::FRAGMENT,
773                ty: wgpu::BindingType::Texture {
774                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
775                    view_dimension: wgpu::TextureViewDimension::D2,
776                    multisampled: false,
777                },
778                count: None,
779            },
780            wgpu::BindGroupLayoutEntry {
781                binding: 3,
782                visibility: wgpu::ShaderStages::FRAGMENT,
783                ty: wgpu::BindingType::Texture {
784                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
785                    view_dimension: wgpu::TextureViewDimension::D2,
786                    multisampled: false,
787                },
788                count: None,
789            },
790        ];
791        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
792            label: Some("custom_paint"),
793            entries: &entries,
794        });
795        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
796            label: Some("custom_paint"),
797            bind_group_layouts: &[Some(&layout)],
798            immediate_size: 0,
799        });
800        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
801            label: Some("custom_paint"),
802            layout: Some(&pipeline_layout),
803            vertex: wgpu::VertexState {
804                module: &shader,
805                entry_point: Some("custom_vertex"),
806                buffers: &[],
807                compilation_options: Default::default(),
808            },
809            fragment: Some(wgpu::FragmentState {
810                module: &shader,
811                entry_point: Some("custom_fragment"),
812                targets: &[Some(wgpu::ColorTargetState {
813                    format,
814                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
815                    write_mask: wgpu::ColorWrites::ALL,
816                })],
817                compilation_options: Default::default(),
818            }),
819            primitive: Default::default(),
820            depth_stencil: None,
821            multisample: Default::default(),
822            multiview_mask: None,
823            cache: None,
824        });
825        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
826            label: Some("custom_paint"),
827            mag_filter: wgpu::FilterMode::Linear,
828            min_filter: wgpu::FilterMode::Linear,
829            mipmap_filter: wgpu::MipmapFilterMode::Linear,
830            ..Default::default()
831        });
832        Self {
833            pipeline,
834            layout,
835            sampler,
836            uniforms: UniformArena::new(device),
837            masks: Default::default(),
838            mask_bytes: 0,
839        }
840    }
841    pub(crate) fn begin_uniforms(&mut self, device: &wgpu::Device, count: usize) -> Result<()> {
842        self.uniforms.begin(device, count)
843    }
844    pub(crate) fn upload_uniforms(&self, queue: &wgpu::Queue) {
845        self.uniforms.upload(queue);
846    }
847    pub fn prepare(
848        &mut self,
849        device: &wgpu::Device,
850        draw: &CustomDraw,
851        source: &wgpu::Texture,
852        mask: Option<&wgpu::Texture>,
853        native: bool,
854        viewport: Size<DevicePixels>,
855    ) -> PreparedDraw {
856        let adjustments = draw.content.image_linear_adjustments();
857        let transform = adjustments
858            .map(|v| v.rows())
859            .unwrap_or_else(|| draw.content.image_color_transform());
860        let vignette = adjustments
861            .map(|v| v.vignette())
862            .unwrap_or([0., 0., 1., 1.]);
863        let linear = [
864            if adjustments.is_some() { 1. } else { 0. },
865            draw.content.image_color_mode().shader_value(),
866            if adjustments.is_some_and(|v| v.is_p3()) {
867                1.
868            } else {
869                0.
870            },
871            0.,
872        ];
873        let params: [[f32; 4]; 9] = [
874            [
875                draw.bounds.origin.x.0,
876                draw.bounds.origin.y.0,
877                draw.bounds.size.width.0,
878                draw.bounds.size.height.0,
879            ],
880            [
881                draw.content_mask.bounds.origin.x.0,
882                draw.content_mask.bounds.origin.y.0,
883                draw.content_mask.bounds.size.width.0,
884                draw.content_mask.bounds.size.height.0,
885            ],
886            [
887                draw.corner_radii.top_left.0,
888                draw.corner_radii.top_right.0,
889                draw.corner_radii.bottom_right.0,
890                draw.corner_radii.bottom_left.0,
891            ],
892            [
893                viewport.width.0 as f32,
894                viewport.height.0 as f32,
895                draw.opacity * draw.content.image_sample_opacity().clamp(0., 1.),
896                u32::from(mask.is_some()) as f32 + if native { 2. } else { 0. },
897            ],
898            transform[0],
899            transform[1],
900            transform[2],
901            vignette,
902            linear,
903        ];
904        let uniform_offset = self.uniforms.write(&params);
905        let uniform = self.uniforms.buffer.as_ref().expect("uniform arena");
906        let source = source.create_view(&Default::default());
907        let mask = mask
908            .map(|t| t.create_view(&Default::default()))
909            .unwrap_or_else(|| source.clone());
910        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
911            label: Some("custom_paint"),
912            layout: &self.layout,
913            entries: &[
914                wgpu::BindGroupEntry {
915                    binding: 0,
916                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
917                        buffer: uniform,
918                        offset: 0,
919                        size: wgpu::BufferSize::new(UNIFORM_BYTES),
920                    }),
921                },
922                wgpu::BindGroupEntry {
923                    binding: 1,
924                    resource: wgpu::BindingResource::Sampler(&self.sampler),
925                },
926                wgpu::BindGroupEntry {
927                    binding: 2,
928                    resource: wgpu::BindingResource::TextureView(&source),
929                },
930                wgpu::BindGroupEntry {
931                    binding: 3,
932                    resource: wgpu::BindingResource::TextureView(&mask),
933                },
934            ],
935        });
936        PreparedDraw {
937            bind_group,
938            uniform_offset,
939        }
940    }
941}
942
943pub(crate) fn coverage_scene(draw: &CustomDraw, extent: Size<DevicePixels>) -> Result<gpui::Scene> {
944    use gpui::*;
945    let mut scene = Scene::default();
946    let bounds = Bounds::new(
947        point(ScaledPixels(0.), ScaledPixels(0.)),
948        size(
949            ScaledPixels(extent.width.0 as f32),
950            ScaledPixels(extent.height.0 as f32),
951        ),
952    );
953    let mask = ContentMask { bounds };
954    match draw.coverage.as_ref().expect("coverage") {
955        CustomCoverage::Border { widths, style } => {
956            ensure!(
957                [widths.top.0, widths.right.0, widths.bottom.0, widths.left.0]
958                    .iter()
959                    .all(|v| v.is_finite() && *v >= 0.),
960                "invalid border widths"
961            );
962            scene.insert_primitive(Quad {
963                bounds,
964                content_mask: mask,
965                background: transparent_black().into(),
966                border_color: white(),
967                border_widths: *widths,
968                corner_radii: draw.corner_radii,
969                border_style: *style,
970                ..Default::default()
971            });
972        }
973        CustomCoverage::Path(path) => {
974            ensure!(path.vertices.len() <= 1_000_000, "coverage path too large");
975            let mut path = path.as_ref().clone();
976            let sx = bounds.size.width.0 / draw.bounds.size.width.0;
977            let sy = bounds.size.height.0 / draw.bounds.size.height.0;
978            for vertex in &mut path.vertices {
979                vertex.xy_position.x =
980                    ScaledPixels((vertex.xy_position.x.0 - draw.bounds.origin.x.0) * sx);
981                vertex.xy_position.y =
982                    ScaledPixels((vertex.xy_position.y.0 - draw.bounds.origin.y.0) * sy);
983                ensure!(
984                    [
985                        vertex.xy_position.x.0,
986                        vertex.xy_position.y.0,
987                        vertex.st_position.x,
988                        vertex.st_position.y
989                    ]
990                    .iter()
991                    .all(|v| v.is_finite()),
992                    "non-finite coverage path"
993                );
994                vertex.content_mask = mask;
995            }
996            path.bounds = bounds;
997            path.content_mask = mask;
998            path.color = white().into();
999            scene.insert_primitive(path);
1000        }
1001    }
1002    scene.finish();
1003    Ok(scene)
1004}
1005
1006#[cfg(test)]
1007mod tests;