hylarana_renderer/
lib.rs

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