mirror_graphics/
lib.rs

1mod interop;
2mod texture;
3mod vertex;
4
5use std::sync::Arc;
6
7use self::vertex::Vertex;
8
9pub use self::texture::{
10    FromNativeResourceError, Texture, Texture2DBuffer, Texture2DRaw, Texture2DResource,
11};
12
13use mirror_common::Size;
14use pollster::FutureExt;
15use texture::{Texture2DSource, Texture2DSourceOptions};
16use thiserror::Error;
17use wgpu::{
18    util::{BufferInitDescriptor, DeviceExt},
19    Backends, Buffer, BufferUsages, Color, CommandEncoderDescriptor, CompositeAlphaMode, Device,
20    DeviceDescriptor, IndexFormat, Instance, InstanceDescriptor, LoadOp, MemoryHints, Operations,
21    PowerPreference, PresentMode, Queue, RenderPassColorAttachment, RenderPassDescriptor,
22    RequestAdapterOptions, StoreOp, Surface, TextureFormat, TextureUsages, TextureViewDescriptor,
23};
24
25pub use wgpu::{rwh as raw_window_handle, SurfaceTarget};
26
27#[derive(Debug, Error)]
28pub enum GraphicsError {
29    #[error("not found graphics adaper")]
30    NotFoundAdapter,
31    #[error("not found graphics surface default config")]
32    NotFoundSurfaceDefaultConfig,
33    #[error(transparent)]
34    RequestDeviceError(#[from] wgpu::RequestDeviceError),
35    #[error(transparent)]
36    SurfaceGetTextureFailed(#[from] wgpu::SurfaceError),
37    #[error(transparent)]
38    CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
39    #[error(transparent)]
40    FromNativeResourceError(#[from] FromNativeResourceError),
41}
42
43#[derive(Debug)]
44pub struct RendererOptions<T> {
45    #[cfg(target_os = "windows")]
46    pub direct3d: mirror_common::win32::Direct3DDevice,
47    pub window: T,
48    pub size: Size,
49}
50
51/// Window Renderer.
52///
53/// Supports rendering RGBA or NV12 hardware or software textures to system
54/// native windows.
55///
56/// Note that the renderer uses a hardware implementation by default, i.e. it
57/// uses the underlying GPU device, and the use of software devices is not
58/// currently supported.
59pub struct Renderer<'a> {
60    surface: Surface<'a>,
61    device: Arc<Device>,
62    queue: Arc<Queue>,
63    vertex_buffer: Buffer,
64    index_buffer: Buffer,
65    source: Texture2DSource,
66}
67
68impl<'a> Renderer<'a> {
69    pub fn new<T: Into<SurfaceTarget<'a>>>(
70        options: RendererOptions<T>,
71    ) -> Result<Self, GraphicsError> {
72        let instance = Instance::new(InstanceDescriptor {
73            backends: if cfg!(target_os = "windows") {
74                Backends::DX12
75            } else if cfg!(target_os = "linux") {
76                Backends::VULKAN
77            } else {
78                Backends::METAL
79            },
80            ..Default::default()
81        });
82
83        let surface = instance.create_surface(options.window)?;
84        let adapter = instance
85            .request_adapter(&RequestAdapterOptions {
86                power_preference: PowerPreference::LowPower,
87                force_fallback_adapter: false,
88                compatible_surface: Some(&surface),
89                ..Default::default()
90            })
91            .block_on()
92            .ok_or_else(|| GraphicsError::NotFoundAdapter)?;
93
94        let (device, queue) = adapter
95            .request_device(
96                &DeviceDescriptor {
97                    label: None,
98                    memory_hints: MemoryHints::Performance,
99                    required_features: adapter.features(),
100                    required_limits: adapter.limits(),
101                },
102                None,
103            )
104            .block_on()?;
105
106        let device = Arc::new(device);
107        let queue = Arc::new(queue);
108
109        // Configure surface as BGRA, BGRA this format compatibility is the best, in
110        // order to unnecessary trouble, directly fixed to BGRA is the best.
111        {
112            let mut config = surface
113                .get_default_config(&adapter, options.size.width, options.size.height)
114                .ok_or_else(|| GraphicsError::NotFoundSurfaceDefaultConfig)?;
115
116            config.present_mode = if cfg!(target_os = "windows") {
117                PresentMode::Mailbox
118            } else if cfg!(target_os = "linux") {
119                PresentMode::Fifo
120            } else {
121                PresentMode::Immediate
122            };
123
124            config.format = TextureFormat::Bgra8Unorm;
125            config.alpha_mode = CompositeAlphaMode::Opaque;
126            config.usage = TextureUsages::RENDER_ATTACHMENT;
127            surface.configure(&device, &config);
128        };
129
130        let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor {
131            label: None,
132            contents: bytemuck::cast_slice(Vertex::VERTICES),
133            usage: BufferUsages::VERTEX,
134        });
135
136        let index_buffer = device.create_buffer_init(&BufferInitDescriptor {
137            label: None,
138            contents: bytemuck::cast_slice(Vertex::INDICES),
139            usage: BufferUsages::INDEX,
140        });
141
142        Ok(Self {
143            source: Texture2DSource::new(Texture2DSourceOptions {
144                #[cfg(target_os = "windows")]
145                direct3d: options.direct3d,
146                device: device.clone(),
147                queue: queue.clone(),
148            })?,
149            vertex_buffer,
150            index_buffer,
151            surface,
152            device,
153            queue,
154        })
155    }
156
157    // Submit the texture to the renderer, it should be noted that the renderer will
158    // not render this texture immediately, the processing flow will enter the
159    // render queue and wait for the queue to automatically schedule the rendering
160    // to the surface.
161    pub fn submit(&mut self, texture: Texture) -> Result<(), GraphicsError> {
162        if let Some((pipeline, bind_group)) = self.source.get_view(texture)? {
163            let output = self.surface.get_current_texture()?;
164            let view = output
165                .texture
166                .create_view(&TextureViewDescriptor::default());
167
168            let mut encoder = self
169                .device
170                .create_command_encoder(&CommandEncoderDescriptor { label: None });
171
172            {
173                let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
174                    color_attachments: &[Some(RenderPassColorAttachment {
175                        view: &view,
176                        resolve_target: None,
177                        ops: Operations {
178                            load: LoadOp::Clear(Color::BLACK),
179                            store: StoreOp::Store,
180                        },
181                    })],
182                    ..Default::default()
183                });
184
185                render_pass.set_pipeline(pipeline);
186                render_pass.set_bind_group(0, Some(&bind_group), &[]);
187                render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
188                render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
189                render_pass.draw_indexed(0..Vertex::INDICES.len() as u32, 0, 0..1);
190            }
191
192            self.queue.submit(Some(encoder.finish()));
193            output.present();
194        }
195
196        Ok(())
197    }
198}
199
200#[cfg(target_os = "windows")]
201pub mod dx11 {
202    use mirror_common::{
203        win32::{
204            windows::Win32::{
205                Foundation::HWND,
206                Graphics::{
207                    Direct3D11::{ID3D11RenderTargetView, ID3D11Texture2D, D3D11_VIEWPORT},
208                    Dxgi::{
209                        Common::{DXGI_FORMAT_NV12, DXGI_FORMAT_R8G8B8A8_UNORM},
210                        CreateDXGIFactory, IDXGIFactory, IDXGISwapChain, DXGI_PRESENT,
211                        DXGI_SWAP_CHAIN_DESC, DXGI_USAGE_RENDER_TARGET_OUTPUT,
212                    },
213                },
214            },
215            Direct3DDevice,
216        },
217        Size,
218    };
219
220    use mirror_resample::win32::{Resource, VideoResampler, VideoResamplerDescriptor};
221    use thiserror::Error;
222
223    use crate::{Texture, Texture2DRaw, Texture2DResource};
224
225    #[derive(Debug, Error)]
226    pub enum Dx11GraphicsError {
227        #[error(transparent)]
228        WindowsError(#[from] mirror_common::win32::windows::core::Error),
229    }
230
231    pub struct Dx11Renderer {
232        direct3d: Direct3DDevice,
233        swap_chain: IDXGISwapChain,
234        render_target_view: ID3D11RenderTargetView,
235        video_processor: Option<VideoResampler>,
236    }
237
238    unsafe impl Send for Dx11Renderer {}
239    unsafe impl Sync for Dx11Renderer {}
240
241    impl Dx11Renderer {
242        pub fn new(
243            window: HWND,
244            size: Size,
245            direct3d: Direct3DDevice,
246        ) -> Result<Self, Dx11GraphicsError> {
247            let swap_chain = unsafe {
248                let dxgi_factory = CreateDXGIFactory::<IDXGIFactory>()?;
249
250                let mut desc = DXGI_SWAP_CHAIN_DESC::default();
251                desc.BufferCount = 1;
252                desc.BufferDesc.Width = size.width;
253                desc.BufferDesc.Height = size.height;
254                desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
255                desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
256                desc.OutputWindow = window;
257                desc.SampleDesc.Count = 1;
258                desc.Windowed = true.into();
259
260                let mut swap_chain = None;
261                dxgi_factory
262                    .CreateSwapChain(&direct3d.device, &desc, &mut swap_chain)
263                    .ok()?;
264
265                swap_chain.unwrap()
266            };
267
268            let back_buffer = unsafe { swap_chain.GetBuffer::<ID3D11Texture2D>(0)? };
269            let render_target_view = unsafe {
270                let mut render_target_view = None;
271                direct3d.device.CreateRenderTargetView(
272                    &back_buffer,
273                    None,
274                    Some(&mut render_target_view),
275                )?;
276
277                render_target_view.unwrap()
278            };
279
280            unsafe {
281                direct3d
282                    .context
283                    .OMSetRenderTargets(Some(&[Some(render_target_view.clone())]), None);
284            }
285
286            unsafe {
287                let mut vp = D3D11_VIEWPORT::default();
288                vp.Width = size.width as f32;
289                vp.Height = size.height as f32;
290                vp.MinDepth = 0.0;
291                vp.MaxDepth = 1.0;
292
293                direct3d.context.RSSetViewports(Some(&[vp]));
294            }
295
296            Ok(Self {
297                video_processor: None,
298                render_target_view,
299                swap_chain,
300                direct3d,
301            })
302        }
303
304        /// Draw this pixel buffer to the configured SurfaceTexture.
305        pub fn submit(&mut self, texture: Texture) -> Result<(), Dx11GraphicsError> {
306            unsafe {
307                self.direct3d
308                    .context
309                    .ClearRenderTargetView(&self.render_target_view, &[0.0, 0.0, 0.0, 1.0]);
310            }
311
312            if self.video_processor.is_none() {
313                let size = texture.size();
314                let format = match texture {
315                    Texture::Nv12(_) => DXGI_FORMAT_NV12,
316                    Texture::Rgba(_) => DXGI_FORMAT_R8G8B8A8_UNORM,
317                    _ => unimplemented!("not supports texture format"),
318                };
319
320                self.video_processor
321                    .replace(VideoResampler::new(VideoResamplerDescriptor {
322                        direct3d: self.direct3d.clone(),
323                        input: Resource::Default(format, size),
324                        output: Resource::Texture(unsafe {
325                            self.swap_chain.GetBuffer::<ID3D11Texture2D>(0)?
326                        }),
327                    })?);
328            }
329
330            if let Some(processor) = self.video_processor.as_mut() {
331                let texture = match texture {
332                    Texture::Rgba(texture) | Texture::Nv12(texture) => texture,
333                    _ => unimplemented!("not supports texture format"),
334                };
335
336                let view = match texture {
337                    Texture2DResource::Texture(texture) => match texture {
338                        Texture2DRaw::ID3D11Texture2D(texture, index) => {
339                            Some(processor.create_input_view(&texture, index)?)
340                        }
341                    },
342                    Texture2DResource::Buffer(texture) => {
343                        processor.update_input_from_buffer(
344                            texture.buffers[0].as_ptr(),
345                            texture.size.width,
346                        )?;
347
348                        None
349                    }
350                };
351
352                processor.process(view)?;
353            }
354
355            unsafe {
356                self.swap_chain.Present(0, DXGI_PRESENT(0)).ok()?;
357            }
358
359            Ok(())
360        }
361    }
362}