| extern crate khronos_egl as egl; |
| |
| use egl::API as egl; |
| use gl::types::{GLboolean, GLchar, GLenum, GLint, GLuint, GLvoid}; |
| use std::ffi::CStr; |
| use std::ptr; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::Arc; |
| |
| use wayland_client::{ |
| protocol::{wl_compositor::WlCompositor, wl_surface::WlSurface}, |
| DispatchData, Display, EventQueue, Main, |
| }; |
| |
| use wayland_protocols::xdg_shell::client::{ |
| xdg_surface::{self, XdgSurface}, |
| xdg_wm_base::{self, XdgWmBase}, |
| }; |
| |
| fn process_xdg_event(xdg: Main<XdgWmBase>, event: xdg_wm_base::Event, _dd: DispatchData) { |
| use xdg_wm_base::Event::*; |
| |
| match event { |
| Ping { serial } => xdg.pong(serial), |
| _ => (), |
| } |
| } |
| |
| struct DisplayConnection { |
| display: Display, |
| event_queue: EventQueue, |
| compositor: Main<WlCompositor>, |
| xdg: Main<XdgWmBase>, |
| } |
| |
| fn setup_wayland() -> DisplayConnection { |
| let display = |
| wayland_client::Display::connect_to_env().expect("unable to connect to the wayland server"); |
| let mut event_queue = display.create_event_queue(); |
| let attached_display = display.clone().attach(event_queue.token()); |
| |
| let globals = wayland_client::GlobalManager::new(&attached_display); |
| |
| // Roundtrip to retrieve the globals list |
| event_queue |
| .sync_roundtrip(&mut (), |_, _, _| unreachable!()) |
| .unwrap(); |
| |
| // Get the compositor. |
| let compositor: Main<WlCompositor> = globals.instantiate_exact(1).unwrap(); |
| |
| // Xdg protocol. |
| let xdg: Main<XdgWmBase> = globals.instantiate_exact(1).unwrap(); |
| xdg.quick_assign(process_xdg_event); |
| |
| DisplayConnection { |
| display, |
| event_queue, |
| compositor, |
| xdg, |
| } |
| } |
| |
| fn setup_egl(display: &Display) -> egl::Display { |
| let egl_display = unsafe { |
| egl.get_display(display.get_display_ptr() as *mut std::ffi::c_void) |
| .unwrap() |
| }; |
| |
| egl.initialize(egl_display).unwrap(); |
| egl_display |
| } |
| |
| fn create_context(display: egl::Display) -> (egl::Context, egl::Config) { |
| let attributes = [ |
| egl::RED_SIZE, |
| 8, |
| egl::GREEN_SIZE, |
| 8, |
| egl::BLUE_SIZE, |
| 8, |
| egl::NONE, |
| ]; |
| |
| let config = egl |
| .choose_first_config(display, &attributes) |
| .expect("unable to choose an EGL configuration") |
| .expect("no EGL configuration found"); |
| |
| let context_attributes = [ |
| egl::CONTEXT_MAJOR_VERSION, |
| 4, |
| egl::CONTEXT_MINOR_VERSION, |
| 0, |
| egl::CONTEXT_OPENGL_PROFILE_MASK, |
| egl::CONTEXT_OPENGL_CORE_PROFILE_BIT, |
| egl::NONE, |
| ]; |
| |
| let context = egl |
| .create_context(display, config, None, &context_attributes) |
| .expect("unable to create an EGL context"); |
| |
| (context, config) |
| } |
| |
| struct Surface { |
| handle: Main<WlSurface>, |
| initialized: AtomicBool, |
| } |
| |
| fn create_surface( |
| ctx: &DisplayConnection, |
| egl_display: egl::Display, |
| egl_context: egl::Context, |
| egl_config: egl::Config, |
| width: i32, |
| height: i32, |
| ) -> Arc<Surface> { |
| let wl_surface = ctx.compositor.create_surface(); |
| let xdg_surface = ctx.xdg.get_xdg_surface(&wl_surface); |
| |
| let xdg_toplevel = xdg_surface.get_toplevel(); |
| xdg_toplevel.set_app_id("khronos-egl-test".to_string()); |
| xdg_toplevel.set_title("Test".to_string()); |
| |
| wl_surface.commit(); |
| ctx.display.flush().unwrap(); |
| |
| let surface = Arc::new(Surface { |
| handle: wl_surface, |
| initialized: AtomicBool::new(false), |
| }); |
| |
| let weak_surface = Arc::downgrade(&surface); |
| |
| xdg_surface.quick_assign( |
| move |xdg_surface: Main<XdgSurface>, event: xdg_surface::Event, _dd: DispatchData| { |
| use xdg_surface::Event::*; |
| |
| match event { |
| Configure { serial } => { |
| if let Some(surface) = weak_surface.upgrade() { |
| if !surface.initialized.swap(true, Ordering::Relaxed) { |
| let wl_egl_surface = |
| wayland_egl::WlEglSurface::new(&surface.handle, width, height); |
| |
| let egl_surface = unsafe { |
| egl.create_window_surface( |
| egl_display, |
| egl_config, |
| wl_egl_surface.ptr() as egl::NativeWindowType, |
| None, |
| ) |
| .expect("unable to create an EGL surface") |
| }; |
| |
| egl.make_current( |
| egl_display, |
| Some(egl_surface), |
| Some(egl_surface), |
| Some(egl_context), |
| ) |
| .expect("unable to bind the context"); |
| |
| render(); |
| |
| egl.swap_buffers(egl_display, egl_surface) |
| .expect("unable to post the surface content"); |
| |
| xdg_surface.ack_configure(serial); |
| } |
| } |
| } |
| _ => (), |
| } |
| }, |
| ); |
| |
| surface |
| } |
| |
| fn main() { |
| // Setup Open GL. |
| egl.bind_api(egl::OPENGL_API) |
| .expect("unable to select OpenGL API"); |
| gl::load_with(|name| egl.get_proc_address(name).unwrap() as *const std::ffi::c_void); |
| |
| // Setup the Wayland client. |
| let mut ctx = setup_wayland(); |
| |
| // Setup EGL. |
| let egl_display = setup_egl(&ctx.display); |
| let (egl_context, egl_config) = create_context(egl_display); |
| |
| // Create a surface. |
| // Note that it must be kept alive to the end of execution. |
| let _surface = create_surface(&ctx, egl_display, egl_context, egl_config, 800, 600); |
| |
| loop { |
| ctx.event_queue |
| .dispatch(&mut (), |_, _, _| { /* we ignore unfiltered messages */ }) |
| .unwrap(); |
| } |
| } |
| |
| const VERTEX: &'static [GLint; 8] = &[-1, -1, 1, -1, 1, 1, -1, 1]; |
| |
| const INDEXES: &'static [GLuint; 4] = &[0, 1, 2, 3]; |
| |
| const VERTEX_SHADER: &[u8] = b"#version 400 |
| in vec2 position; |
| |
| void main() { |
| gl_Position = vec4(position, 0.0f, 1.0f); |
| } |
| \0"; |
| |
| const FRAGMENT_SHADER: &[u8] = b"#version 400 |
| out vec4 color; |
| |
| void main() { |
| color = vec4(1.0f, 0.0f, 0.0f, 1.0f); |
| } |
| \0"; |
| |
| fn render() { |
| unsafe { |
| let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER); |
| check_gl_errors(); |
| let src = CStr::from_bytes_with_nul_unchecked(VERTEX_SHADER).as_ptr(); |
| gl::ShaderSource(vertex_shader, 1, (&[src]).as_ptr(), ptr::null()); |
| check_gl_errors(); |
| gl::CompileShader(vertex_shader); |
| check_shader_status(vertex_shader); |
| |
| let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER); |
| check_gl_errors(); |
| let src = CStr::from_bytes_with_nul_unchecked(FRAGMENT_SHADER).as_ptr(); |
| gl::ShaderSource(fragment_shader, 1, (&[src]).as_ptr(), ptr::null()); |
| check_gl_errors(); |
| gl::CompileShader(fragment_shader); |
| check_shader_status(fragment_shader); |
| |
| let program = gl::CreateProgram(); |
| check_gl_errors(); |
| gl::AttachShader(program, vertex_shader); |
| check_gl_errors(); |
| gl::AttachShader(program, fragment_shader); |
| check_gl_errors(); |
| gl::LinkProgram(program); |
| check_gl_errors(); |
| gl::UseProgram(program); |
| check_gl_errors(); |
| |
| let mut buffer = 0; |
| gl::GenBuffers(1, &mut buffer); |
| check_gl_errors(); |
| gl::BindBuffer(gl::ARRAY_BUFFER, buffer); |
| check_gl_errors(); |
| gl::BufferData( |
| gl::ARRAY_BUFFER, |
| 8 * 4, |
| VERTEX.as_ptr() as *const std::ffi::c_void, |
| gl::STATIC_DRAW, |
| ); |
| check_gl_errors(); |
| |
| let mut vertex_input = 0; |
| gl::GenVertexArrays(1, &mut vertex_input); |
| check_gl_errors(); |
| gl::BindVertexArray(vertex_input); |
| check_gl_errors(); |
| gl::EnableVertexAttribArray(0); |
| check_gl_errors(); |
| gl::VertexAttribPointer(0, 2, gl::INT, gl::FALSE as GLboolean, 0, 0 as *const GLvoid); |
| check_gl_errors(); |
| |
| let mut indexes = 0; |
| gl::GenBuffers(1, &mut indexes); |
| check_gl_errors(); |
| gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, indexes); |
| check_gl_errors(); |
| gl::BufferData( |
| gl::ELEMENT_ARRAY_BUFFER, |
| 4 * 4, |
| INDEXES.as_ptr() as *const std::ffi::c_void, |
| gl::STATIC_DRAW, |
| ); |
| check_gl_errors(); |
| |
| gl::DrawElements(gl::TRIANGLE_FAN, 4, gl::UNSIGNED_INT, std::ptr::null()); |
| check_gl_errors(); |
| } |
| } |
| |
| fn format_error(e: GLenum) -> &'static str { |
| match e { |
| gl::NO_ERROR => "No error", |
| gl::INVALID_ENUM => "Invalid enum", |
| gl::INVALID_VALUE => "Invalid value", |
| gl::INVALID_OPERATION => "Invalid operation", |
| gl::INVALID_FRAMEBUFFER_OPERATION => "Invalid framebuffer operation", |
| gl::OUT_OF_MEMORY => "Out of memory", |
| gl::STACK_UNDERFLOW => "Stack underflow", |
| gl::STACK_OVERFLOW => "Stack overflow", |
| _ => "Unknown error", |
| } |
| } |
| |
| pub fn check_gl_errors() { |
| unsafe { |
| match gl::GetError() { |
| gl::NO_ERROR => (), |
| e => { |
| panic!("OpenGL error: {}", format_error(e)) |
| } |
| } |
| } |
| } |
| |
| unsafe fn check_shader_status(shader: GLuint) { |
| let mut status = gl::FALSE as GLint; |
| gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status); |
| if status != (gl::TRUE as GLint) { |
| let mut len = 0; |
| gl::GetProgramiv(shader, gl::INFO_LOG_LENGTH, &mut len); |
| if len > 0 { |
| let mut buf = Vec::with_capacity(len as usize); |
| buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character |
| gl::GetProgramInfoLog( |
| shader, |
| len, |
| ptr::null_mut(), |
| buf.as_mut_ptr() as *mut GLchar, |
| ); |
| |
| let log = String::from_utf8(buf).unwrap(); |
| eprintln!("shader compilation log:\n{}", log); |
| } |
| |
| panic!("shader compilation failed"); |
| } |
| } |