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 #[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 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 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 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 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: 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 pub fn get_view(
376 &mut self,
377 texture: Texture,
378 ) -> Result<Option<(&RenderPipeline, BindGroup)>, FromNativeResourceError> {
379 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 #[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}