wgpu-hal 29.0.1

Hardware abstraction layer for wgpu, the cross-platform, safe, pure-rust graphics API
Documentation
//! This example shows interop with raw GLES contexts -
//! the ability to hook up wgpu-hal to an existing context and draw into it.
//!
//! Emscripten build:
//! 1. install emsdk
//! 2. build this example with cargo:
//!    EMCC_CFLAGS="-g -s ERROR_ON_UNDEFINED_SYMBOLS=0 --no-entry -s FULL_ES3=1" cargo build --example raw-gles --target wasm32-unknown-emscripten
//! 3. copy raw-gles.em.html into target directory and open it in browser:
//!    cp wgpu-hal/examples/raw-gles.em.html target/wasm32-unknown-emscripten/debug/examples

extern crate wgpu_hal as hal;

#[cfg(not(any(
    target_arch = "wasm32",
    target_os = "ios",
    target_os = "visionos",
    target_env = "ohos"
)))]
fn main() {
    use std::ffi::CString;

    use glutin::{
        config::GlConfig as _,
        context::{NotCurrentGlContext as _, PossiblyCurrentGlContext as _, Version},
        display::{GetGlDisplay as _, GlDisplay as _},
        surface::GlSurface as _,
    };
    use glutin_winit::GlWindow as _;
    use raw_window_handle::HasWindowHandle as _;
    use winit::{
        application::ApplicationHandler,
        event::{KeyEvent, WindowEvent},
        event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
        keyboard::{Key, NamedKey},
        window::Window,
    };

    env_logger::init();
    println!("Initializing external GL context");

    /// Find the config with the maximum number of samples, so our triangle will be
    /// smooth.
    fn gl_config_picker(
        configs: Box<dyn Iterator<Item = glutin::config::Config> + '_>,
    ) -> glutin::config::Config {
        configs
            .reduce(|accum, config| {
                if config.num_samples() > accum.num_samples() {
                    config
                } else {
                    accum
                }
            })
            .expect("Failed to find a matching config")
    }

    struct App {
        gl_config: Option<glutin::config::Config>,
        not_current_gl_context: Option<glutin::context::NotCurrentContext>,
        state: Option<(
            glutin::context::PossiblyCurrentContext,
            glutin::surface::Surface<glutin::surface::WindowSurface>,
            Window,
        )>,
        exposed: Option<hal::ExposedAdapter<hal::api::Gles>>,
    }

    impl App {
        fn new() -> Self {
            Self {
                gl_config: None,
                not_current_gl_context: None,
                state: None,
                exposed: None,
            }
        }
    }

    impl ApplicationHandler for App {
        fn resumed(&mut self, event_loop: &ActiveEventLoop) {
            // First call: create display, pick config, create GL context.
            if self.gl_config.is_none() {
                // Only Windows requires the window to be present before creating the display.
                // Other platforms don't really need one.
                let window_attributes = cfg!(windows).then(|| {
                    Window::default_attributes()
                        .with_title("wgpu raw GLES example (press Escape to exit)")
                });

                let template = glutin::config::ConfigTemplateBuilder::new();

                let display_builder =
                    glutin_winit::DisplayBuilder::new().with_window_attributes(window_attributes);

                let (window, gl_config) = display_builder
                    .build(event_loop, template, gl_config_picker)
                    .expect("Failed to build window and config from display");

                println!("Picked a config with {} samples", gl_config.num_samples());

                let raw_window_handle = window
                    .as_ref()
                    .and_then(|window| window.window_handle().ok())
                    .map(|handle| handle.as_raw());

                let gl_display = gl_config.display();

                // Glutin tries to create an OpenGL context by default.
                // Force it to use any version of GLES.
                let context_attributes = glutin::context::ContextAttributesBuilder::new()
                    // wgpu expects GLES 3.0+.
                    .with_context_api(glutin::context::ContextApi::Gles(Some(Version::new(3, 0))))
                    .build(raw_window_handle);

                let gl_context = unsafe {
                    gl_display
                        .create_context(&gl_config, &context_attributes)
                        .expect("failed to create context")
                };

                self.not_current_gl_context = Some(gl_context);
                self.gl_config = Some(gl_config);

                // On Windows, the window was already created by DisplayBuilder.
                if let Some(window) = window {
                    self.create_surface(event_loop, window);
                    return;
                }
            }

            // Create window + surface (non-Windows first call, or Android re-resume).
            if self.state.is_none() {
                let gl_config = self.gl_config.as_ref().unwrap();
                let window = glutin_winit::finalize_window(
                    event_loop,
                    Window::default_attributes()
                        .with_title("wgpu raw GLES example (press Escape to exit)"),
                    gl_config,
                )
                .unwrap();
                self.create_surface(event_loop, window);
            }
        }

        fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
            // This event is only raised on Android, where the backing NativeWindow for a GL
            // Surface can appear and disappear at any moment.
            println!("Android window removed");

            // Destroy the GL Surface and un-current the GL Context before ndk-glue releases
            // the window back to the system.
            if let Some((gl_context, ..)) = self.state.take() {
                assert!(self
                    .not_current_gl_context
                    .replace(gl_context.make_not_current().unwrap())
                    .is_none());
            }
        }

        fn window_event(
            &mut self,
            event_loop: &ActiveEventLoop,
            _window_id: winit::window::WindowId,
            event: WindowEvent,
        ) {
            event_loop.set_control_flow(ControlFlow::Wait);

            match event {
                WindowEvent::CloseRequested
                | WindowEvent::KeyboardInput {
                    event:
                        KeyEvent {
                            logical_key: Key::Named(NamedKey::Escape),
                            ..
                        },
                    ..
                } => event_loop.exit(),
                WindowEvent::Resized(size) => {
                    if size.width != 0 && size.height != 0 {
                        // Some platforms like EGL require resizing GL surface to update the size.
                        // Notable platforms here are Wayland and macOS, other don't require it
                        // and the function is no-op, but it's wise to resize it for portability
                        // reasons.
                        if let Some((gl_context, gl_surface, window)) = &self.state {
                            window.resize_surface(gl_surface, gl_context);
                        }
                    }
                }
                WindowEvent::RedrawRequested => {
                    if let (Some(exposed), Some((gl_context, gl_surface, window))) =
                        (&self.exposed, &self.state)
                    {
                        let inner_size = window.inner_size();

                        fill_screen(exposed, inner_size.width, inner_size.height);

                        println!("Showing the window");
                        gl_surface
                            .swap_buffers(gl_context)
                            .expect("Failed to swap buffers");
                    }
                }
                _ => (),
            }
        }
    }

    impl App {
        fn create_surface(&mut self, _event_loop: &ActiveEventLoop, window: Window) {
            let gl_config = self.gl_config.as_ref().unwrap();

            let attrs = window
                .build_surface_attributes(Default::default())
                .expect("Failed to build surface attributes");
            let gl_surface = unsafe {
                gl_config
                    .display()
                    .create_window_surface(gl_config, &attrs)
                    .expect("Cannot create GL WindowSurface")
            };

            // Make it current.
            let gl_context = self
                .not_current_gl_context
                .take()
                .unwrap()
                .make_current(&gl_surface)
                .expect("GL context cannot be made current with WindowSurface");

            // The context needs to be current for the Renderer to set up shaders and
            // buffers. It also performs function loading, which needs a current context on
            // WGL.
            println!("Hooking up to wgpu-hal");
            self.exposed.get_or_insert_with(|| {
                unsafe {
                    <hal::api::Gles as hal::Api>::Adapter::new_external(
                        |name| {
                            // XXX: On WGL this should only be called after the context was
                            // made current
                            gl_config
                                .display()
                                .get_proc_address(&CString::new(name).expect(name))
                        },
                        wgpu_types::GlBackendOptions::default(),
                    )
                }
                .expect("GL adapter can't be initialized")
            });

            window.request_redraw();
            self.state = Some((gl_context, gl_surface, window));
        }
    }

    let event_loop = EventLoop::new().unwrap();
    let mut app = App::new();
    event_loop
        .run_app(&mut app)
        .expect("Couldn't run event loop");
}

#[cfg(target_os = "emscripten")]
fn main() {
    env_logger::init();

    println!("Initializing external GL context");
    let egl = khronos_egl::Instance::new(khronos_egl::Static);
    let display = unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.unwrap();
    egl.initialize(display)
        .expect("unable to initialize display");

    let attributes = [
        khronos_egl::RED_SIZE,
        8,
        khronos_egl::GREEN_SIZE,
        8,
        khronos_egl::BLUE_SIZE,
        8,
        khronos_egl::NONE,
    ];

    let config = egl
        .choose_first_config(display, &attributes)
        .unwrap()
        .expect("unable to choose config");
    let surface = unsafe {
        let window = std::ptr::null_mut::<std::ffi::c_void>();
        egl.create_window_surface(display, config, window, None)
    }
    .expect("unable to create surface");

    let context_attributes = [khronos_egl::CONTEXT_CLIENT_VERSION, 3, khronos_egl::NONE];

    let gl_context = egl
        .create_context(display, config, None, &context_attributes)
        .expect("unable to create context");
    egl.make_current(display, Some(surface), Some(surface), Some(gl_context))
        .expect("can't make context current");

    println!("Hooking up to wgpu-hal");
    let exposed = unsafe {
        <hal::api::Gles as hal::Api>::Adapter::new_external(|name| {
            egl.get_proc_address(name)
                .map_or(std::ptr::null(), |p| p as *const _)
        })
    }
    .expect("GL adapter can't be initialized");

    fill_screen(&exposed, 640, 400);
}

#[cfg(any(
    all(target_arch = "wasm32", not(target_os = "emscripten")),
    target_os = "ios",
    target_os = "visionos",
    target_env = "ohos"
))]
fn main() {
    eprintln!("This example is not supported on this platform")
}

#[cfg(not(any(
    all(target_arch = "wasm32", not(target_os = "emscripten")),
    target_os = "ios",
    target_os = "visionos"
)))]
fn fill_screen(exposed: &hal::ExposedAdapter<hal::api::Gles>, width: u32, height: u32) {
    use hal::{Adapter as _, CommandEncoder as _, Device as _, Queue as _};

    let od = unsafe {
        exposed.adapter.open(
            wgpu_types::Features::empty(),
            &wgpu_types::Limits::downlevel_defaults(),
            &wgpu_types::MemoryHints::default(),
        )
    }
    .unwrap();

    let format = wgpu_types::TextureFormat::Rgba8UnormSrgb;
    let texture = <hal::api::Gles as hal::Api>::Texture::default_framebuffer(format);
    let view = unsafe {
        od.device
            .create_texture_view(
                &texture,
                &hal::TextureViewDescriptor {
                    label: None,
                    format,
                    dimension: wgpu_types::TextureViewDimension::D2,
                    usage: wgpu_types::TextureUses::COLOR_TARGET,
                    range: wgpu_types::ImageSubresourceRange::default(),
                },
            )
            .unwrap()
    };

    println!("Filling the screen");
    let mut encoder = unsafe {
        od.device
            .create_command_encoder(&hal::CommandEncoderDescriptor {
                label: None,
                queue: &od.queue,
            })
            .unwrap()
    };
    let mut fence = unsafe { od.device.create_fence().unwrap() };
    let rp_desc = hal::RenderPassDescriptor {
        label: None,
        extent: wgpu_types::Extent3d {
            width,
            height,
            depth_or_array_layers: 1,
        },
        sample_count: 1,
        color_attachments: &[Some(hal::ColorAttachment {
            target: hal::Attachment {
                view: &view,
                usage: wgpu_types::TextureUses::COLOR_TARGET,
            },
            depth_slice: None,
            resolve_target: None,
            ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
            clear_value: wgpu_types::Color::BLUE,
        })],
        depth_stencil_attachment: None,
        multiview_mask: None,
        timestamp_writes: None,
        occlusion_query_set: None,
    };
    unsafe {
        encoder.begin_encoding(None).unwrap();
        encoder.begin_render_pass(&rp_desc).unwrap();
        encoder.end_render_pass();
        let cmd_buf = encoder.end_encoding().unwrap();
        od.queue.submit(&[&cmd_buf], &[], (&mut fence, 0)).unwrap();
    }
}