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