Skip to main content

script_webgpu/
gpuexternaltexture.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::marker::PhantomData;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use euclid::default::Size2D;
11use js::context::JSContext;
12use log::warn;
13use malloc_size_of_derive::MallocSizeOf;
14use pixels::Snapshot;
15use script_bindings::cell::DomRefCell;
16use script_bindings::codegen::GenericBindings::WebGPUBinding::{
17    GPUDeviceMethods, GPUExternalTextureDescriptor, GPUExternalTextureMethods,
18    GPUExternalTextureWrap,
19};
20use script_bindings::error::{Error, Fallible};
21use script_bindings::interfaces::PromiseHelpers;
22use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
23use script_bindings::{DomTypes, task};
24use webgpu_traits::{
25    Features, WebGPU, WebGPUDevice, WebGPUExternalTexture, WebGPUQueue, WebGPURequest,
26    WebGPUTexture, WebGPUTextureView,
27};
28
29use crate::JSTraceable;
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::root::DomRoot;
32use crate::dom::bindings::str::USVString;
33use crate::gpudevice::GPUDevice;
34use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUHTMLVideoTrait, WebGPUPromise};
35
36/// Backing of GPUExternalTexture
37#[derive(JSTraceable, MallocSizeOf)]
38pub struct PlanarTexture<D: DomTypes> {
39    #[no_trace]
40    channel: WebGPU,
41    #[no_trace]
42    device_id: WebGPUDevice,
43    #[no_trace]
44    queue_id: WebGPUQueue,
45    #[no_trace]
46    texture_id: WebGPUTexture,
47    #[no_trace]
48    texture_view_id: WebGPUTextureView,
49    expired: Cell<bool>,
50    #[no_trace]
51    size: Size2D<u32>,
52    #[no_trace = "PhantomData does not exist"]
53    phantom: PhantomData<D>,
54}
55
56impl<D> PlanarTexture<D>
57where
58    D: Equivalence,
59    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
60{
61    pub fn new(channel: WebGPU, device: &GPUDevice<D>, snapshot: Snapshot) -> Self {
62        let device_id = device.id();
63        let queue_id = device.queue_id();
64        let texture_id = WebGPUTexture(
65            device
66                .global_from_reflector()
67                .global_wgpu_id_hub()
68                .create_texture_id(),
69        );
70        let texture_view_id = WebGPUTextureView(
71            device
72                .global_from_reflector()
73                .global_wgpu_id_hub()
74                .create_texture_view_id(),
75        );
76        let size = snapshot.size();
77        if let Err(error) = channel.0.send(WebGPURequest::CreatePlanarTexture {
78            device_id: device_id.0,
79            texture_id: texture_id.0,
80            texture_view_id: texture_view_id.0,
81            size,
82            format: snapshot.format(),
83        }) {
84            warn!("Failed to send CreatePlanarTexture ({error})");
85        }
86        let self_ = Self {
87            channel,
88            device_id,
89            queue_id,
90            texture_id,
91            texture_view_id,
92            size,
93            expired: Cell::new(true),
94            phantom: PhantomData,
95        };
96        self_.update(snapshot);
97        self_
98    }
99
100    pub fn size(&self) -> Size2D<u32> {
101        self.size
102    }
103
104    pub fn update(&self, snapshot: Snapshot) {
105        if !self.expired.get() {
106            return;
107        }
108        if let Err(error) = self.channel.0.send(WebGPURequest::UpdatePlanarTexture {
109            device_id: self.device_id.0,
110            queue_id: self.queue_id.0,
111            texture_id: self.texture_id.0,
112            snapshot: snapshot.into_shared(),
113        }) {
114            warn!("Failed to send UpdatePlanarTexture ({error})");
115        }
116        self.expired.set(false);
117    }
118
119    pub(crate) fn expire(&self) {
120        self.expired.set(true);
121    }
122
123    pub fn is_expired(&self) -> bool {
124        self.expired.get()
125    }
126}
127
128impl<D: DomTypes> Drop for PlanarTexture<D> {
129    fn drop(&mut self) {
130        if let Err(error) = self.channel.0.send(WebGPURequest::DropPlanarTexture(
131            self.texture_id.0,
132            self.texture_view_id.0,
133        )) {
134            warn!("Failed to send DropPlanarTexture ({error})");
135        }
136    }
137}
138
139#[derive(JSTraceable, MallocSizeOf)]
140struct DroppableGPUExternalTexture {
141    #[no_trace]
142    channel: WebGPU,
143    #[no_trace]
144    external_texture: WebGPUExternalTexture,
145}
146
147impl Drop for DroppableGPUExternalTexture {
148    fn drop(&mut self) {
149        if let Err(error) = self
150            .channel
151            .0
152            .send(WebGPURequest::DropExternalTexture(self.external_texture.0))
153        {
154            warn!(
155                "Failed to send DropExternalTexture ({:?}) ({error})",
156                self.external_texture.0
157            );
158        }
159    }
160}
161
162#[dom_struct]
163pub struct GPUExternalTexture<D: DomTypes> {
164    reflector_: Reflector,
165    label: DomRefCell<USVString>,
166    #[conditional_malloc_size_of]
167    planar_texture: Option<Rc<PlanarTexture<D>>>,
168    droppable: DroppableGPUExternalTexture,
169    #[no_trace = "PhantomData does not exist"]
170    phantom: PhantomData<D>,
171}
172
173impl<D> GPUExternalTexture<D>
174where
175    D: Equivalence,
176    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
177{
178    fn new_inherited(
179        channel: WebGPU,
180        external_texture: WebGPUExternalTexture,
181        label: USVString,
182        planar_texture: Option<Rc<PlanarTexture<D>>>,
183    ) -> GPUExternalTexture<D> {
184        Self {
185            reflector_: Reflector::new(),
186            label: DomRefCell::new(label),
187            droppable: DroppableGPUExternalTexture {
188                channel,
189                external_texture,
190            },
191            planar_texture,
192            phantom: PhantomData,
193        }
194    }
195
196    pub(crate) fn new(
197        cx: &mut JSContext,
198        global: &D::GlobalScope,
199        channel: WebGPU,
200        external_texture: WebGPUExternalTexture,
201        label: USVString,
202        planar_texture: Option<Rc<PlanarTexture<D>>>,
203    ) -> DomRoot<GPUExternalTexture<D>> {
204        reflect_dom_object_with_wrap::<D, _, _>(
205            cx,
206            Box::new(GPUExternalTexture::new_inherited(
207                channel,
208                external_texture,
209                label,
210                planar_texture,
211            )),
212            global,
213            GPUExternalTextureWrap::<D>,
214        )
215    }
216
217    pub(crate) fn expire(&self) {
218        if let Some(planar_texture) = &self.planar_texture {
219            planar_texture.expire();
220        }
221        if let Err(error) = self
222            .droppable
223            .channel
224            .0
225            .send(WebGPURequest::DestroyExternalTexture(
226                self.droppable.external_texture.0,
227            ))
228        {
229            warn!(
230                "Failed to send DestroyExternalTexture ({:?}) ({error})",
231                self.droppable.external_texture.0
232            );
233        }
234    }
235
236    /// <https://www.w3.org/TR/webgpu/#dom-gpudevice-importexternaltexture>
237    pub(crate) fn create(
238        cx: &mut JSContext,
239        device: &GPUDevice<D>,
240        descriptor: &GPUExternalTextureDescriptor<D>,
241    ) -> Fallible<DomRoot<GPUExternalTexture<D>>> {
242        let (size, planar_texture) = if device
243            .Features()
244            .wgpu_features()
245            .contains(Features::EXTERNAL_TEXTURE)
246        {
247            // 2.1 - 2.4 inside the method
248            descriptor.source.planar_video_for_webgpu(device)?
249        } else {
250            // spec assumes that this is always supported, but that is not the case in wgpu
251            return Err(Error::NotSupported(Some(
252                "ExternalTexture is not supported on this device".to_string(),
253            )));
254        };
255        // 2.5. Let result be a new GPUExternalTexture object wrapping data.
256        let device_id = device.id().0;
257        let channel = device.channel();
258        let external_texture_id = device
259            .global_from_reflector()
260            .global_wgpu_id_hub()
261            .create_external_texture_id();
262
263        if let Err(error) = channel.0.send(WebGPURequest::ImportExternalTexture {
264            device_id,
265            external_texture_id,
266            size,
267            label: descriptor.parent.label.to_string(),
268            plane0: planar_texture
269                .as_ref()
270                .map(|planar_texture| planar_texture.texture_view_id.0),
271        }) {
272            warn!("Failed to send ImportExternalTexture ({error})");
273        };
274        let result = Self::new(
275            cx,
276            &device.global_from_reflector(),
277            channel,
278            WebGPUExternalTexture(external_texture_id),
279            // 5. Set result.label to descriptor.label.
280            descriptor.parent.label.clone(),
281            planar_texture,
282        );
283        // 3. If source is an HTMLVideoElement, queue an automatic expiry task with device this and the following steps
284        let this = Trusted::new(&*result);
285
286        device
287            .global_from_reflector()
288            .queue_webgpu_task_source(task!(expire:  move || {
289                this.root().expire();
290            }));
291        // 6. Return result.
292        Ok(result)
293    }
294}
295
296impl<D: Equivalence> GPUExternalTexture<D> {
297    pub(crate) fn id(&self) -> WebGPUExternalTexture {
298        self.droppable.external_texture
299    }
300}
301
302impl<D: Equivalence> GPUExternalTextureMethods<D> for GPUExternalTexture<D> {
303    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
304    fn Label(&self) -> USVString {
305        self.label.borrow().clone()
306    }
307
308    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
309    fn SetLabel(&self, value: USVString) {
310        *self.label.borrow_mut() = value;
311    }
312}