Skip to main content

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