mirror_graphics/
texture.rs

1mod bgra;
2mod i420;
3mod nv12;
4mod rgba;
5
6use std::sync::Arc;
7
8use self::{bgra::Bgra, i420::I420, nv12::Nv12, rgba::Rgba};
9use crate::{interop::InteropError, Vertex};
10
11#[cfg(target_os = "windows")]
12use crate::interop::win32::Interop;
13
14#[cfg(any(target_os = "linux", target_os = "macos"))]
15type Interop = ();
16
17use mirror_common::Size;
18use smallvec::SmallVec;
19use thiserror::Error;
20
21#[cfg(target_os = "windows")]
22use mirror_common::win32::{
23    windows::Win32::Graphics::Direct3D11::ID3D11Texture2D, Direct3DDevice, EasyTexture,
24};
25
26use wgpu::{
27    include_wgsl, AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
28    BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendState,
29    ColorTargetState, ColorWrites, Device, Extent3d, FilterMode, FragmentState, ImageCopyTexture,
30    ImageDataLayout, IndexFormat, MultisampleState, Origin3d, PipelineCompilationOptions,
31    PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, Queue, RenderPipeline,
32    RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderModuleDescriptor,
33    ShaderStages, Texture as WGPUTexture, TextureAspect, TextureDescriptor, TextureDimension,
34    TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor,
35    TextureViewDimension, VertexState,
36};
37
38#[derive(Debug, Error)]
39pub enum FromNativeResourceError {
40    #[error(transparent)]
41    InteropError(#[from] InteropError),
42}
43
44#[derive(Debug)]
45pub enum Texture2DRaw {
46    #[cfg(target_os = "windows")]
47    ID3D11Texture2D(ID3D11Texture2D, u32),
48}
49
50impl Texture2DRaw {
51    #[cfg(target_os = "windows")]
52    pub(crate) fn texture<'b>(
53        &self,
54        interop: &'b mut Interop,
55    ) -> Result<&'b WGPUTexture, FromNativeResourceError> {
56        Ok(match self {
57            Self::ID3D11Texture2D(dx11, index) => interop.from_hal(dx11, *index)?,
58        })
59    }
60
61    #[cfg(target_os = "windows")]
62    pub(crate) fn size(&self) -> Size {
63        match self {
64            Self::ID3D11Texture2D(dx11, _) => {
65                let desc = dx11.desc();
66                Size {
67                    width: desc.Width,
68                    height: desc.Height,
69                }
70            }
71        }
72    }
73}
74
75#[derive(Debug)]
76pub struct Texture2DBuffer<'a> {
77    pub size: Size,
78    pub buffers: &'a [&'a [u8]],
79}
80
81#[derive(Debug)]
82pub enum Texture2DResource<'a> {
83    #[cfg(target_os = "windows")]
84    Texture(Texture2DRaw),
85    Buffer(Texture2DBuffer<'a>),
86}
87
88impl<'a> Texture2DResource<'a> {
89    /// Get the hardware texture, here does not deal with software texture, so
90    /// if it is software texture directly return None.
91    #[allow(unused_variables)]
92    pub(crate) fn texture<'b>(
93        &self,
94        interop: &'b mut Interop,
95    ) -> Result<Option<&'b WGPUTexture>, FromNativeResourceError> {
96        Ok(match self {
97            #[cfg(target_os = "windows")]
98            Texture2DResource::Texture(texture) => Some(texture.texture(interop)?),
99            Texture2DResource::Buffer(_) => None,
100        })
101    }
102
103    pub(crate) fn size(&self) -> Size {
104        match self {
105            #[cfg(target_os = "windows")]
106            Texture2DResource::Texture(texture) => texture.size(),
107            Texture2DResource::Buffer(texture) => texture.size,
108        }
109    }
110}
111
112#[derive(Debug)]
113pub enum Texture<'a> {
114    Bgra(Texture2DResource<'a>),
115    Rgba(Texture2DResource<'a>),
116    Nv12(Texture2DResource<'a>),
117    I420(Texture2DBuffer<'a>),
118}
119
120impl<'a> Texture<'a> {
121    pub(crate) fn texture<'b>(
122        &self,
123        interop: &'b mut Interop,
124    ) -> Result<Option<&'b WGPUTexture>, FromNativeResourceError> {
125        Ok(match self {
126            Texture::Rgba(texture) | Texture::Bgra(texture) | Texture::Nv12(texture) => {
127                texture.texture(interop)?
128            }
129            Texture::I420(_) => None,
130        })
131    }
132
133    pub(crate) fn size(&self) -> Size {
134        match self {
135            Texture::Rgba(texture) | Texture::Bgra(texture) | Texture::Nv12(texture) => {
136                texture.size()
137            }
138            Texture::I420(texture) => texture.size,
139        }
140    }
141}
142
143trait Texture2DSample {
144    fn create_texture_descriptor(size: Size) -> impl IntoIterator<Item = (Size, TextureFormat)>;
145
146    fn views_descriptors<'a>(
147        &'a self,
148        texture: Option<&'a WGPUTexture>,
149    ) -> impl IntoIterator<Item = (&'a WGPUTexture, TextureFormat, TextureAspect)>;
150
151    fn copy_buffer_descriptors<'a>(
152        &self,
153        buffers: &'a [&'a [u8]],
154    ) -> impl IntoIterator<Item = (&'a [u8], &WGPUTexture, TextureAspect, Size)>;
155
156    fn create(device: &Device, size: Size) -> impl Iterator<Item = WGPUTexture> {
157        Self::create_texture_descriptor(size)
158            .into_iter()
159            .map(|(size, format)| {
160                device.create_texture(&TextureDescriptor {
161                    label: None,
162                    mip_level_count: 1,
163                    sample_count: 1,
164                    dimension: TextureDimension::D2,
165                    // The textures created here are all needed to allow external writing of data,
166                    // and all need the COPY_DST flag.
167                    usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
168                    view_formats: &[],
169                    size: Extent3d {
170                        depth_or_array_layers: 1,
171                        width: size.width,
172                        height: size.height,
173                    },
174                    format,
175                })
176            })
177    }
178
179    /// Creates a new BindGroupLayout.
180    ///
181    /// A BindGroupLayout is a handle to the GPU-side layout of a binding group.
182    /// It can be used to create a BindGroupDescriptor object, which in turn can
183    /// be used to create a BindGroup object with Device::create_bind_group. A
184    /// series of BindGroupLayouts can also be used to create a
185    /// PipelineLayoutDescriptor, which can be used to create a PipelineLayout.
186    fn bind_group_layout(&self, device: &Device) -> BindGroupLayout {
187        let mut entries: SmallVec<[BindGroupLayoutEntry; 5]> = SmallVec::with_capacity(5);
188        for (i, _) in self.views_descriptors(None).into_iter().enumerate() {
189            entries.push(BindGroupLayoutEntry {
190                count: None,
191                binding: i as u32,
192                visibility: ShaderStages::FRAGMENT,
193                ty: BindingType::Texture {
194                    sample_type: TextureSampleType::Float { filterable: true },
195                    view_dimension: TextureViewDimension::D2,
196                    multisampled: false,
197                },
198            });
199        }
200
201        entries.push(BindGroupLayoutEntry {
202            binding: entries.len() as u32,
203            visibility: ShaderStages::FRAGMENT,
204            ty: BindingType::Sampler(SamplerBindingType::Filtering),
205            count: None,
206        });
207
208        device.create_bind_group_layout(&BindGroupLayoutDescriptor {
209            label: None,
210            entries: &entries,
211        })
212    }
213
214    /// Creates a new BindGroup.
215    ///
216    /// A BindGroup represents the set of resources bound to the bindings
217    /// described by a BindGroupLayout. It can be created with
218    /// Device::create_bind_group. A BindGroup can be bound to a particular
219    /// RenderPass with RenderPass::set_bind_group, or to a ComputePass with
220    /// ComputePass::set_bind_group.
221    fn bind_group(
222        &self,
223        device: &Device,
224        layout: &BindGroupLayout,
225        texture: Option<&WGPUTexture>,
226    ) -> BindGroup {
227        let sampler = device.create_sampler(&SamplerDescriptor {
228            address_mode_u: AddressMode::ClampToEdge,
229            address_mode_v: AddressMode::ClampToEdge,
230            address_mode_w: AddressMode::ClampToEdge,
231            mipmap_filter: FilterMode::Nearest,
232            mag_filter: FilterMode::Nearest,
233            min_filter: FilterMode::Nearest,
234            ..Default::default()
235        });
236
237        let mut views: SmallVec<[TextureView; 5]> = SmallVec::with_capacity(5);
238        for (texture, format, aspect) in self.views_descriptors(texture) {
239            views.push(texture.create_view(&TextureViewDescriptor {
240                dimension: Some(TextureViewDimension::D2),
241                format: Some(format),
242                aspect,
243                ..Default::default()
244            }));
245        }
246
247        let mut entries: SmallVec<[BindGroupEntry; 5]> = SmallVec::with_capacity(5);
248        for (i, view) in views.iter().enumerate() {
249            entries.push(BindGroupEntry {
250                binding: i as u32,
251                resource: BindingResource::TextureView(view),
252            });
253        }
254
255        entries.push(BindGroupEntry {
256            binding: entries.len() as u32,
257            resource: BindingResource::Sampler(&sampler),
258        });
259
260        device.create_bind_group(&BindGroupDescriptor {
261            label: None,
262            entries: &entries,
263            layout,
264        })
265    }
266
267    /// Schedule a write of some data into a texture.
268    fn update(&self, queue: &Queue, resource: &Texture2DBuffer) {
269        for (buffer, texture, aspect, size) in self.copy_buffer_descriptors(resource.buffers) {
270            queue.write_texture(
271                ImageCopyTexture {
272                    aspect,
273                    texture,
274                    mip_level: 0,
275                    origin: Origin3d::ZERO,
276                },
277                buffer,
278                ImageDataLayout {
279                    offset: 0,
280                    // Bytes per "row" in an image.
281                    //
282                    // A row is one row of pixels or of compressed blocks in the x direction.
283                    bytes_per_row: Some(size.width),
284                    rows_per_image: Some(size.height),
285                },
286                texture.size(),
287            );
288        }
289    }
290}
291
292enum Texture2DSourceSample {
293    Bgra(Bgra),
294    Rgba(Rgba),
295    Nv12(Nv12),
296    I420(I420),
297}
298
299impl Texture2DSourceSample {
300    fn from_texture(device: &Device, texture: &Texture, size: Size) -> Self {
301        match texture {
302            Texture::Bgra(_) => Self::Bgra(Bgra::new(device, size)),
303            Texture::Rgba(_) => Self::Rgba(Rgba::new(device, size)),
304            Texture::Nv12(_) => Self::Nv12(Nv12::new(device, size)),
305            Texture::I420(_) => Self::I420(I420::new(device, size)),
306        }
307    }
308
309    fn fragment(&self) -> ShaderModuleDescriptor {
310        match self {
311            Texture2DSourceSample::Rgba(_) => {
312                include_wgsl!("./shaders/fragment/any.wgsl")
313            }
314            Texture2DSourceSample::Bgra(_) => {
315                include_wgsl!("./shaders/fragment/any.wgsl")
316            }
317            Texture2DSourceSample::Nv12(_) => {
318                include_wgsl!("./shaders/fragment/nv12.wgsl")
319            }
320            Texture2DSourceSample::I420(_) => {
321                include_wgsl!("./shaders/fragment/i420.wgsl")
322            }
323        }
324    }
325
326    fn bind_group_layout(&self, device: &Device) -> BindGroupLayout {
327        match self {
328            Self::Bgra(texture) => texture.bind_group_layout(device),
329            Self::Rgba(texture) => texture.bind_group_layout(device),
330            Self::Nv12(texture) => texture.bind_group_layout(device),
331            Self::I420(texture) => texture.bind_group_layout(device),
332        }
333    }
334}
335
336pub struct Texture2DSourceOptions {
337    #[cfg(target_os = "windows")]
338    pub direct3d: Direct3DDevice,
339    pub device: Arc<Device>,
340    pub queue: Arc<Queue>,
341}
342
343pub struct Texture2DSource {
344    device: Arc<Device>,
345    queue: Arc<Queue>,
346    pipeline: Option<RenderPipeline>,
347    sample: Option<Texture2DSourceSample>,
348    bind_group_layout: Option<BindGroupLayout>,
349    interop: Interop,
350}
351
352impl Texture2DSource {
353    pub fn new(options: Texture2DSourceOptions) -> Result<Self, FromNativeResourceError> {
354        #[cfg(target_os = "windows")]
355        let interop = Interop::new(options.device.clone(), options.direct3d);
356
357        #[cfg(any(target_os = "linux", target_os = "macos"))]
358        let interop = ();
359
360        Ok(Self {
361            device: options.device,
362            queue: options.queue,
363            bind_group_layout: None,
364            pipeline: None,
365            sample: None,
366            interop,
367        })
368    }
369
370    /// If it is a hardware texture, it will directly create view for the
371    /// current texture, if it is a software texture, it will write the data to
372    /// the internal texture first, and then create the view for the internal
373    /// texture, so it is a more time-consuming operation to use the software
374    /// texture.
375    pub fn get_view(
376        &mut self,
377        texture: Texture,
378    ) -> Result<Option<(&RenderPipeline, BindGroup)>, FromNativeResourceError> {
379        // Not yet initialized, initialize the environment first.
380        if self.sample.is_none() {
381            let size = texture.size();
382            let sample = Texture2DSourceSample::from_texture(&self.device, &texture, size);
383            let bind_group_layout = sample.bind_group_layout(&self.device);
384
385            let pipeline =
386                self.device
387                    .create_render_pipeline(&RenderPipelineDescriptor {
388                        label: None,
389                        layout: Some(&self.device.create_pipeline_layout(
390                            &PipelineLayoutDescriptor {
391                                label: None,
392                                bind_group_layouts: &[&bind_group_layout],
393                                push_constant_ranges: &[],
394                            },
395                        )),
396                        vertex: VertexState {
397                            entry_point: Some("main"),
398                            module: &self
399                                .device
400                                .create_shader_module(include_wgsl!("./shaders/vertex.wgsl")),
401                            compilation_options: PipelineCompilationOptions::default(),
402                            buffers: &[Vertex::desc()],
403                        },
404                        fragment: Some(FragmentState {
405                            entry_point: Some("main"),
406                            module: &self.device.create_shader_module(sample.fragment()),
407                            compilation_options: PipelineCompilationOptions::default(),
408                            targets: &[Some(ColorTargetState {
409                                blend: Some(BlendState::REPLACE),
410                                write_mask: ColorWrites::ALL,
411                                format: TextureFormat::Bgra8Unorm,
412                            })],
413                        }),
414                        primitive: PrimitiveState {
415                            topology: PrimitiveTopology::TriangleStrip,
416                            strip_index_format: Some(IndexFormat::Uint16),
417                            ..Default::default()
418                        },
419                        multisample: MultisampleState::default(),
420                        depth_stencil: None,
421                        multiview: None,
422                        cache: None,
423                    });
424
425            self.sample = Some(sample);
426            self.pipeline = Some(pipeline);
427            self.bind_group_layout = Some(bind_group_layout);
428        }
429
430        // Only software textures need to be updated to the sample via update.
431        #[allow(unreachable_patterns)]
432        if let Some(sample) = &self.sample {
433            match &texture {
434                Texture::Bgra(Texture2DResource::Buffer(buffer)) => {
435                    if let Texture2DSourceSample::Bgra(rgba) = sample {
436                        rgba.update(&self.queue, buffer);
437                    }
438                }
439                Texture::Rgba(Texture2DResource::Buffer(buffer)) => {
440                    if let Texture2DSourceSample::Rgba(rgba) = sample {
441                        rgba.update(&self.queue, buffer);
442                    }
443                }
444                Texture::Nv12(Texture2DResource::Buffer(buffer)) => {
445                    if let Texture2DSourceSample::Nv12(nv12) = sample {
446                        nv12.update(&self.queue, buffer);
447                    }
448                }
449                Texture::I420(texture) => {
450                    if let Texture2DSourceSample::I420(i420) = sample {
451                        i420.update(&self.queue, texture);
452                    }
453                }
454                _ => (),
455            }
456        }
457
458        Ok(
459            if let (Some(layout), Some(sample), Some(pipeline)) =
460                (&self.bind_group_layout, &self.sample, &self.pipeline)
461            {
462                let texture = texture.texture(&mut self.interop)?;
463                Some((
464                    pipeline,
465                    match sample {
466                        Texture2DSourceSample::Bgra(sample) => {
467                            sample.bind_group(&self.device, layout, texture)
468                        }
469                        Texture2DSourceSample::Rgba(sample) => {
470                            sample.bind_group(&self.device, layout, texture)
471                        }
472                        Texture2DSourceSample::Nv12(sample) => {
473                            sample.bind_group(&self.device, layout, texture)
474                        }
475                        Texture2DSourceSample::I420(sample) => {
476                            sample.bind_group(&self.device, layout, texture)
477                        }
478                    },
479                ))
480            } else {
481                None
482            },
483        )
484    }
485}