Skip to main content

script_webgpu/
gputexture.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::string::String;
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use log::warn;
10use malloc_size_of_derive::MallocSizeOf;
11use script_bindings::DomTypes;
12use script_bindings::cell::DomRefCell;
13use script_bindings::codegen::GenericBindings::WebGPUBinding::{
14    GPUTextureAspect, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat,
15    GPUTextureMethods, GPUTextureViewDescriptor, GPUTextureWrap,
16};
17use script_bindings::dom::MutNullableDom;
18use script_bindings::interfaces::PromiseHelpers;
19use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
20use webgpu_traits::{
21    Extent3d, ImageSubresourceRange, TextureAspect, TextureDescriptor, TextureUsages,
22    TextureViewDescriptor, WebGPU, WebGPURequest, WebGPUTexture, WebGPUTextureView,
23};
24
25use crate::JSTraceable;
26use crate::dom::bindings::error::Fallible;
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::bindings::str::USVString;
29use crate::gpuconvert::{WebGPUConvert, convert_texture_descriptor};
30use crate::gputextureview::GPUTextureView;
31use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise};
32
33#[derive(JSTraceable, MallocSizeOf)]
34struct DroppableGPUTexture {
35    #[no_trace]
36    channel: WebGPU,
37    #[no_trace]
38    texture: WebGPUTexture,
39}
40
41impl Drop for DroppableGPUTexture {
42    fn drop(&mut self) {
43        if let Err(e) = self
44            .channel
45            .0
46            .send(WebGPURequest::DropTexture(self.texture.0))
47        {
48            warn!(
49                "Failed to send WebGPURequest::DropTexture({:?}) ({})",
50                self.texture.0, e
51            );
52        };
53    }
54}
55
56#[dom_struct]
57pub struct GPUTexture<D: DomTypes> {
58    reflector_: Reflector,
59    label: DomRefCell<USVString>,
60    device: Dom<D::GPUDevice>,
61    #[no_trace]
62    #[ignore_malloc_size_of = "External type"]
63    texture_size: Extent3d,
64    mip_level_count: u32,
65    sample_count: u32,
66    dimension: GPUTextureDimension,
67    format: GPUTextureFormat,
68    texture_usage: u32,
69    droppable: DroppableGPUTexture,
70    default_view: MutNullableDom<GPUTextureView<D>>,
71}
72
73impl<D: Equivalence> GPUTexture<D> {
74    #[expect(clippy::too_many_arguments)]
75    fn new_inherited(
76        texture: WebGPUTexture,
77        device: &D::GPUDevice,
78        channel: WebGPU,
79        texture_size: Extent3d,
80        mip_level_count: u32,
81        sample_count: u32,
82        dimension: GPUTextureDimension,
83        format: GPUTextureFormat,
84        texture_usage: u32,
85        label: USVString,
86    ) -> Self {
87        Self {
88            reflector_: Reflector::new(),
89            label: DomRefCell::new(label),
90            device: Dom::from_ref(device),
91            texture_size,
92            mip_level_count,
93            sample_count,
94            dimension,
95            format,
96            texture_usage,
97            droppable: DroppableGPUTexture { channel, texture },
98            default_view: MutNullableDom::new(None),
99        }
100    }
101
102    #[expect(clippy::too_many_arguments)]
103    pub(crate) fn new(
104        cx: &mut JSContext,
105        global: &D::GlobalScope,
106        texture: WebGPUTexture,
107        device: &D::GPUDevice,
108        channel: WebGPU,
109        texture_size: Extent3d,
110        mip_level_count: u32,
111        sample_count: u32,
112        dimension: GPUTextureDimension,
113        format: GPUTextureFormat,
114        texture_usage: u32,
115        label: USVString,
116    ) -> DomRoot<Self> {
117        reflect_dom_object_with_wrap::<D, _, _>(
118            cx,
119            Box::new(GPUTexture::new_inherited(
120                texture,
121                device,
122                channel,
123                texture_size,
124                mip_level_count,
125                sample_count,
126                dimension,
127                format,
128                texture_usage,
129                label,
130            )),
131            global,
132            GPUTextureWrap::<D>,
133        )
134    }
135}
136
137impl<D> GPUTexture<D>
138where
139    D: Equivalence,
140    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
141{
142    pub fn id(&self) -> WebGPUTexture {
143        self.droppable.texture
144    }
145
146    pub fn wgpu_texture_descriptor(&self) -> TextureDescriptor<'static> {
147        TextureDescriptor {
148            label: Some(self.label.borrow().to_string().into()),
149            size: self.texture_size,
150            mip_level_count: self.mip_level_count,
151            sample_count: self.sample_count,
152            dimension: self.dimension.convert(),
153            format: self.format.convert(),
154            usage: TextureUsages::from_bits_retain(self.texture_usage),
155            view_formats: vec![],
156        }
157    }
158
159    /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createtexture>
160    pub(crate) fn create(
161        cx: &mut JSContext,
162        device: &D::GPUDevice,
163        descriptor: &GPUTextureDescriptor,
164    ) -> Fallible<DomRoot<GPUTexture<D>>> {
165        let (desc, size) = convert_texture_descriptor::<D>(descriptor, device)?;
166
167        let global: DomRoot<D::GlobalScope> = device.global_from_reflector();
168        let texture_id = global.global_wgpu_id_hub().create_texture_id();
169
170        device
171            .channel()
172            .0
173            .send(WebGPURequest::CreateTexture {
174                device_id: device.id().0,
175                texture_id,
176                descriptor: desc,
177            })
178            .expect("Failed to create WebGPU Texture");
179
180        let texture = WebGPUTexture(texture_id);
181
182        Ok(GPUTexture::new(
183            cx,
184            &*device.global_from_reflector(),
185            texture,
186            device,
187            device.channel(),
188            size,
189            descriptor.mipLevelCount,
190            descriptor.sampleCount,
191            descriptor.dimension,
192            descriptor.format,
193            descriptor.usage,
194            descriptor.parent.label.clone(),
195        ))
196    }
197
198    pub(crate) fn get_default_view(&self, cx: &mut JSContext) -> WebGPUTextureView {
199        self.default_view
200            .or_init(|| {
201                self.CreateView(cx, &GPUTextureViewDescriptor::default())
202                    .expect("Default descriptor should always be valid.")
203            })
204            .id()
205    }
206}
207
208impl<D> GPUTextureMethods<D> for GPUTexture<D>
209where
210    D: Equivalence,
211    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
212{
213    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
214    fn Label(&self) -> USVString {
215        self.label.borrow().clone()
216    }
217
218    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
219    fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
220        *self.label.safe_borrow_mut(no_gc) = value;
221    }
222
223    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-createview>
224    fn CreateView(
225        &self,
226        cx: &mut JSContext,
227        descriptor: &GPUTextureViewDescriptor,
228    ) -> Fallible<DomRoot<GPUTextureView<D>>> {
229        let desc = if !matches!(descriptor.mipLevelCount, Some(0)) &&
230            !matches!(descriptor.arrayLayerCount, Some(0))
231        {
232            Some(TextureViewDescriptor {
233                label: (&descriptor.parent).convert(),
234                format: descriptor
235                    .format
236                    .map(|f| self.device.validate_texture_format_required_features(&f))
237                    .transpose()?,
238                dimension: descriptor.dimension.map(|dimension| dimension.convert()),
239                usage: Some(TextureUsages::from_bits_retain(descriptor.usage)),
240                range: ImageSubresourceRange {
241                    aspect: match descriptor.aspect {
242                        GPUTextureAspect::All => TextureAspect::All,
243                        GPUTextureAspect::Stencil_only => TextureAspect::StencilOnly,
244                        GPUTextureAspect::Depth_only => TextureAspect::DepthOnly,
245                    },
246                    base_mip_level: descriptor.baseMipLevel,
247                    mip_level_count: descriptor.mipLevelCount,
248                    base_array_layer: descriptor.baseArrayLayer,
249                    array_layer_count: descriptor.arrayLayerCount,
250                },
251            })
252        } else {
253            self.device
254                .dispatch_error(webgpu_traits::Error::Validation(String::from(
255                    "arrayLayerCount and mipLevelCount cannot be 0",
256                )));
257            None
258        };
259
260        let texture_view_id = self
261            .global_from_reflector()
262            .global_wgpu_id_hub()
263            .create_texture_view_id();
264
265        self.droppable
266            .channel
267            .0
268            .send(WebGPURequest::CreateTextureView {
269                texture_id: self.id().0,
270                texture_view_id,
271                device_id: self.device.id().0,
272                descriptor: desc,
273            })
274            .expect("Failed to create WebGPU texture view");
275
276        let texture_view = WebGPUTextureView(texture_view_id);
277
278        Ok(GPUTextureView::new(
279            cx,
280            &*self.global_from_reflector(),
281            self.droppable.channel.clone(),
282            texture_view,
283            self,
284            descriptor.parent.label.clone(),
285        ))
286    }
287
288    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-destroy>
289    fn Destroy(&self) {
290        if let Err(e) = self
291            .droppable
292            .channel
293            .0
294            .send(WebGPURequest::DestroyTexture(self.id().0))
295        {
296            warn!(
297                "Failed to send WebGPURequest::DestroyTexture({:?}) ({})",
298                self.id().0,
299                e
300            );
301        };
302    }
303
304    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-width>
305    fn Width(&self) -> u32 {
306        self.texture_size.width
307    }
308
309    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-height>
310    fn Height(&self) -> u32 {
311        self.texture_size.height
312    }
313
314    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-depthorarraylayers>
315    fn DepthOrArrayLayers(&self) -> u32 {
316        self.texture_size.depth_or_array_layers
317    }
318
319    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-miplevelcount>
320    fn MipLevelCount(&self) -> u32 {
321        self.mip_level_count
322    }
323
324    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-samplecount>
325    fn SampleCount(&self) -> u32 {
326        self.sample_count
327    }
328
329    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-dimension>
330    fn Dimension(&self) -> GPUTextureDimension {
331        self.dimension
332    }
333
334    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-format>
335    fn Format(&self) -> GPUTextureFormat {
336        self.format
337    }
338
339    /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-usage>
340    fn Usage(&self) -> u32 {
341        self.texture_usage
342    }
343}