├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── rust-toolchain.toml └── src ├── main.rs ├── main_glutin.rs ├── main_gtk.rs └── support.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lepton" 3 | version = "0.1.0" 4 | authors = ["Josh Matthews "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | euclid = "0.22" 11 | env_logger = "*" 12 | gio = { version = "^0", optional = true } 13 | gtk = { version = "0.18", features = ["v3_24"], optional = true } 14 | libservo = { git = "https://github.com/servo/servo", default-features = false } 15 | glutin = { version = "0.29", optional = true, default-features = false } 16 | surfman = { git = "https://github.com/servo/surfman", default-features = false, features = ["sm-angle-default", "sm-raw-window-handle-05"], rev = "300789ddbda45c89e9165c31118bf1c4c07f89f6" } 17 | raw-window-handle = "0.5" 18 | rustls = { version = "0.23", default-features = false, features = ["ring"] } 19 | takeable-option = { version = "*", optional = true } 20 | cgl = "0.3" 21 | gleam = "0.12" 22 | glib = { version = "0.20", optional = true } 23 | 24 | [build-dependencies] 25 | gl_generator = "0.14" 26 | 27 | [features] 28 | gtk_window = ["gio", "gtk", "glib"] 29 | glutin_window = ["glutin", "takeable-option"] 30 | 31 | [patch.crates-io] 32 | # If you need to temporarily test Servo with a local fork of some upstream 33 | # crate, add that here. Use the form: 34 | # 35 | # = { path = "/path/to/local/checkout" } 36 | # 37 | # Or for a git dependency: 38 | # 39 | # [patch."https://github.com/servo/"] 40 | # = { path = "/path/to/local/checkout" } 41 | 42 | # This is here to dedupe winapi since mio 0.6 is still using winapi 0.2. 43 | #mio = { git = "https://github.com/servo/mio.git", branch = "servo-mio-0.6.22" } 44 | 45 | #[patch."https://github.com/jrmuizel/raqote"] 46 | #raqote = { git = "https://github.com/jdm/raqote", branch = "fkup" } 47 | 48 | # https://github.com/servo/servo/issues/27515#issuecomment-671474054 49 | #[patch."https://github.com/servo/webrender"] 50 | #webrender = { git = "https://github.com/jdm/webrender", branch = "crash-backtrace" } 51 | #webrender_api = { git = "https://github.com/jdm/webrender", branch = "crash-backtrace" } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple example of embedding Servo in a a non-browser application using OpenGL. 2 | 3 | Status: 4 | * [x] Simple glutin-based application can render Servo content (no interaction yet) 5 | * [x] GTK-based application can render Servo in a GLArea integration (no interaction yet) 6 | 7 | Tested platforms: 8 | * [x] macOS 9 | * [ ] windows 10 | * [ ] linux 11 | 12 | Prerequisites for embedding Servo: 13 | * a Cargo.toml that overrides webrender, webrender_api, mio, and raqote (https://github.com/servo/servo/blob/c661cc87bac22c20f2d59659ef705267aee397a3/Cargo.toml#L29-L38) 14 | * a rust-toolchain that matches the rust-toolchain in your servo clone 15 | * a build environment that matches https://github.com/servo/servo/#setting-up-your-environment 16 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "glutin_window")] 2 | fn main() { 3 | use gl_generator::{Api, Fallbacks, Profile, Registry}; 4 | use std::env; 5 | use std::fs::File; 6 | use std::path::PathBuf; 7 | 8 | let dest = PathBuf::from(&env::var("OUT_DIR").unwrap()); 9 | 10 | println!("cargo:rerun-if-changed=build.rs"); 11 | 12 | let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap(); 13 | Registry::new(Api::Gles2, (3, 3), Profile::Core, Fallbacks::All, ["KHR_debug"]) 14 | .write_bindings(gl_generator::StructGenerator, &mut file) 15 | .unwrap(); 16 | } 17 | 18 | #[cfg(feature = "gtk_window")] 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # Be sure to update shell.nix and support/crown/rust-toolchain.toml when bumping this! 3 | channel = "1.83.0" 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "gtk_window")] 2 | include!("main_gtk.rs"); 3 | 4 | #[cfg(feature = "glutin_window")] 5 | include!("main_glutin.rs"); 6 | -------------------------------------------------------------------------------- /src/main_glutin.rs: -------------------------------------------------------------------------------- 1 | use euclid::{Point2D, Size2D, Scale}; 2 | use glutin::dpi::{PhysicalPosition, PhysicalSize}; 3 | use glutin::event::{Event, WindowEvent, MouseButton, ElementState}; 4 | use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy}; 5 | use glutin::window::{CursorIcon, WindowBuilder}; 6 | use glutin::ContextBuilder; 7 | //use glutin::WindowedContext; 8 | use glutin::platform::ContextTraitExt; 9 | use raw_window_handle::{/*HasRawWindowHandle,*/ HasRawDisplayHandle}; 10 | use servo::*; 11 | //use servo::config::prefs::PrefValue; 12 | use servo::base::id::WebViewId; 13 | use servo::compositing::CompositeTarget; 14 | use servo::compositing::windowing::EmbedderCoordinates; 15 | use servo::compositing::windowing::AnimationState; 16 | use servo::compositing::windowing::{EmbedderMethods, EmbedderEvent, WindowMethods, MouseWindowEvent}; 17 | use servo::config::opts::Opts; 18 | use servo::config::prefs::Preferences; 19 | use servo::{EventLoopWaker, EmbedderMsg, CompositorEventVariant, Cursor}; 20 | use servo::MouseButton as ServoMouseButton; 21 | use servo::webrender_api::units::{DeviceIntRect, DeviceIntPoint}; 22 | use servo::webrender_traits::rendering_context::SurfmanRenderingContext; 23 | use servo::webrender_api::units::DevicePixel; 24 | use servo::servo_url::ServoUrl; 25 | use surfman::Connection; 26 | use std::cell::{Cell, RefCell}; 27 | use std::rc::Rc; 28 | 29 | mod support; 30 | 31 | fn glutin_size_to_euclid_size(size: PhysicalSize) -> Size2D { 32 | Size2D::new(size.width, size.height) 33 | } 34 | 35 | fn glutin_position_to_euclid_point(position: PhysicalPosition) -> Point2D { 36 | Point2D::new(position.x, position.y) 37 | } 38 | 39 | fn main() { 40 | //env_logger::init(); 41 | rustls::crypto::ring::default_provider() 42 | .install_default() 43 | .expect("Error initializing crypto provider"); 44 | 45 | let el = EventLoop::new(); 46 | let proxy = el.create_proxy(); 47 | let wb = WindowBuilder::new().with_title("A fantastic window!"); 48 | 49 | let windowed_context = 50 | ContextBuilder::new().build_windowed(wb, &el).unwrap(); 51 | 52 | let windowed_context = unsafe { windowed_context.make_current().unwrap() }; 53 | let window = windowed_context.window(); 54 | //let size = window.inner_size().cast::(); 55 | 56 | println!( 57 | "Pixel format of the window's GL context: {:?}", 58 | windowed_context.get_pixel_format() 59 | ); 60 | 61 | let gl = support::load(&windowed_context.context()); 62 | 63 | struct Waker(EventLoopProxy<()>); 64 | impl EventLoopWaker for Waker { 65 | fn clone_box(&self) -> Box { 66 | Box::new(Waker(self.0.clone())) 67 | } 68 | fn wake(&self) { 69 | let _ = self.0.send_event(()); 70 | } 71 | } 72 | struct Embedder { 73 | waker: Waker, 74 | } 75 | impl EmbedderMethods for Embedder { 76 | fn create_event_loop_waker(&mut self) -> Box { 77 | self.waker.clone_box() 78 | } 79 | } 80 | 81 | struct Window { 82 | coordinates: RefCell, 83 | animating: Cell, 84 | } 85 | impl WindowMethods for Window { 86 | fn get_coordinates(&self) -> EmbedderCoordinates { 87 | self.coordinates.borrow().clone() 88 | } 89 | fn set_animation_state(&self, state: AnimationState) { 90 | self.animating.set(state == AnimationState::Animating); 91 | //println!("animation state: {:?}", _state); 92 | } 93 | } 94 | 95 | // Initialize surfman 96 | let display_handle = window 97 | .raw_display_handle(); 98 | let connection = 99 | Connection::from_raw_display_handle(display_handle).expect("Failed to create connection"); 100 | let adapter = connection 101 | .create_adapter() 102 | .expect("Failed to create adapter"); 103 | 104 | let inner_size = window.inner_size(); 105 | let surface_size = glutin_size_to_euclid_size(inner_size).to_i32().to_untyped(); 106 | let rendering_context = SurfmanRenderingContext::create(&connection, &adapter, Some(surface_size)) 107 | .expect("Failed to create WR surfman"); 108 | 109 | let viewport_origin = DeviceIntPoint::zero(); // bottom left 110 | let viewport_size = glutin_size_to_euclid_size(window.inner_size()).to_f32(); 111 | let viewport = DeviceIntRect::from_origin_and_size(viewport_origin, viewport_size.to_i32()); 112 | 113 | let app_window = Rc::new(Window { 114 | animating: Cell::new(false), 115 | coordinates: RefCell::new(EmbedderCoordinates { 116 | hidpi_factor: Scale::new(window.scale_factor() as f32), 117 | screen_size: viewport.size().cast_unit(), 118 | available_screen_size: viewport.size().cast_unit(), 119 | window_rect: viewport.cast_unit(), 120 | framebuffer: viewport.size(), 121 | viewport, 122 | }), 123 | }); 124 | let opts = Opts::default(); 125 | let prefs = Preferences::default(); 126 | let rendering_context = Rc::new(rendering_context); 127 | let mut servo = Servo::new( 128 | opts, 129 | prefs, 130 | rendering_context.clone(), 131 | Box::new(Embedder { 132 | waker: Waker(proxy), 133 | }), 134 | app_window.clone(), 135 | None, 136 | CompositeTarget::Window, 137 | ); 138 | let browser_id = WebViewId::new(); 139 | servo.setup_logging(); 140 | servo.handle_events(vec![EmbedderEvent::NewWebView( 141 | ServoUrl::parse("http://neverssl.com").unwrap(), 142 | browser_id, 143 | )]); 144 | 145 | let mut wrapped_context = unsafe { 146 | rendering_context.device().create_context_from_native_context( 147 | surfman::NativeContext(windowed_context.context().raw_handle()), 148 | ).unwrap() 149 | }; 150 | 151 | let windowed_context = RefCell::new(Some(windowed_context)); 152 | 153 | let mut servo = Some(servo); 154 | let mut cursor_pos = Point2D::zero(); 155 | 156 | el.run(move |event, _, control_flow| { 157 | //println!("{:?}", event); 158 | *control_flow = if app_window.animating.get() { ControlFlow::Poll } else { ControlFlow::Wait }; 159 | let mut events = vec![]; 160 | match event { 161 | Event::LoopDestroyed => { 162 | return; 163 | }, 164 | Event::WindowEvent { event, .. } => { 165 | match event { 166 | WindowEvent::Resized(physical_size) => { 167 | if physical_size.width as i32 > 0 && physical_size.height as i32 > 0 { 168 | let new_size = Size2D::new(physical_size.width, physical_size.height).to_i32(); 169 | let viewport = DeviceIntRect::from_origin_and_size(viewport_origin, new_size.to_i32()); 170 | let mut coordinates = app_window.coordinates.borrow_mut(); 171 | coordinates.window_rect = viewport.cast_unit(); 172 | coordinates.viewport = viewport; 173 | coordinates.framebuffer = viewport.size(); 174 | rendering_context.resize(new_size.to_untyped()).unwrap(); 175 | events.push(EmbedderEvent::MoveResizeWebView(browser_id, viewport.to_f32())); 176 | events.push(EmbedderEvent::WindowResize); 177 | let windowed_context2 = windowed_context.borrow_mut().take().unwrap(); 178 | windowed_context2.resize(physical_size); 179 | *windowed_context.borrow_mut() = Some(windowed_context2); 180 | } 181 | } 182 | WindowEvent::CloseRequested => { 183 | *control_flow = ControlFlow::Exit 184 | } 185 | WindowEvent::CursorMoved { position, .. } => { 186 | let position = glutin_position_to_euclid_point(position); 187 | cursor_pos = position; 188 | events.push(EmbedderEvent::MouseWindowMoveEventClass(position.to_f32())); 189 | } 190 | WindowEvent::MouseInput { state, button, .. } => { 191 | if button == MouseButton::Left { 192 | match state { 193 | ElementState::Pressed => { 194 | events.push( 195 | EmbedderEvent::MouseWindowEventClass( 196 | MouseWindowEvent::MouseDown( 197 | ServoMouseButton::Left, 198 | cursor_pos.to_f32(), 199 | ) 200 | ) 201 | ); 202 | } 203 | ElementState::Released => { 204 | events.push( 205 | EmbedderEvent::MouseWindowEventClass( 206 | MouseWindowEvent::MouseUp( 207 | ServoMouseButton::Left, 208 | cursor_pos.to_f32(), 209 | ) 210 | ) 211 | ); 212 | 213 | events.push( 214 | EmbedderEvent::MouseWindowEventClass( 215 | MouseWindowEvent::Click( 216 | ServoMouseButton::Left, 217 | cursor_pos.to_f32(), 218 | ) 219 | ) 220 | ); 221 | } 222 | } 223 | } 224 | } 225 | _ => (), 226 | } 227 | }, 228 | Event::RedrawRequested(_) => { 229 | servo.as_mut().unwrap().present(); 230 | let windowed_context2 = windowed_context.borrow_mut().take().unwrap(); 231 | let size = windowed_context2.window().inner_size().cast::(); 232 | let windowed_context2 = unsafe { windowed_context2.make_current() }.unwrap(); 233 | rendering_context.with_front_buffer(|device, surface| { 234 | let info = device.surface_info(&surface); 235 | let texture = device.create_surface_texture(&mut wrapped_context, surface).unwrap(); 236 | let texture_id = device.surface_texture_object(&texture); 237 | gl.draw_texture(device, texture_id, info.size, Size2D::new(size.width, size.height)); 238 | let surface = device.destroy_surface_texture(&mut wrapped_context, texture).unwrap(); 239 | surface 240 | }); 241 | windowed_context2.swap_buffers().unwrap(); 242 | *windowed_context.borrow_mut() = Some(windowed_context2); 243 | 244 | } 245 | Event::UserEvent(()) => { 246 | events.push(EmbedderEvent::Idle); 247 | windowed_context.borrow().as_ref().unwrap().window().request_redraw(); 248 | } 249 | _ => (), 250 | } 251 | 252 | let mut need_present = app_window.animating.get(); 253 | 254 | loop { 255 | if servo.is_none() { 256 | break; 257 | } 258 | let mut shutting_down = false; 259 | need_present |= servo.as_mut().unwrap().handle_events(events.drain(..)); 260 | 261 | let servo_events = servo.as_mut().unwrap().get_events(); 262 | if servo_events.len() == 0 { 263 | break; 264 | } 265 | for (webview_id, event) in servo_events { 266 | if !matches!(event, EmbedderMsg::EventDelivered(CompositorEventVariant::MouseMoveEvent)) { 267 | println!("{:?}", (webview_id, &event)); 268 | } 269 | if let EmbedderMsg::ReadyToPresent(_) = event { 270 | need_present |= true; 271 | windowed_context.borrow().as_ref().unwrap().window().request_redraw(); 272 | } 273 | if let EmbedderMsg::Shutdown = event { 274 | shutting_down = true; 275 | break; 276 | } 277 | if let EmbedderMsg::AllowNavigationRequest(id, ..) = event { 278 | events.push(EmbedderEvent::AllowNavigationResponse(id, true)); 279 | } 280 | if let EmbedderMsg::SetCursor(cursor) = event { 281 | let windowed_context2 = windowed_context.borrow_mut().take().unwrap(); 282 | let window = windowed_context2.window(); 283 | if let Some(cursor) = match cursor { 284 | Cursor::None => Some(CursorIcon::Default), 285 | Cursor::Pointer => Some(CursorIcon::Hand), 286 | Cursor::Text => Some(CursorIcon::Text), 287 | _ => None, 288 | } { 289 | window.set_cursor_icon(cursor); 290 | } 291 | *windowed_context.borrow_mut() = Some(windowed_context2); 292 | } 293 | if let EmbedderMsg::WebViewOpened(new_webview_id) = event { 294 | let rect = app_window.get_coordinates().get_viewport().to_f32(); 295 | events.push(EmbedderEvent::FocusWebView(new_webview_id)); 296 | events.push(EmbedderEvent::MoveResizeWebView(new_webview_id, rect)); 297 | events.push(EmbedderEvent::RaiseWebViewToTop(new_webview_id, true)); 298 | } 299 | } 300 | 301 | if shutting_down { 302 | let servo = servo.take().unwrap(); 303 | servo.deinit(); 304 | control_flow.set_exit(); 305 | break; 306 | } 307 | } 308 | 309 | if need_present { 310 | servo.as_mut().unwrap().present(); 311 | } 312 | }); 313 | } 314 | -------------------------------------------------------------------------------- /src/main_gtk.rs: -------------------------------------------------------------------------------- 1 | use euclid::default::Size2D; 2 | use gio::prelude::*; 3 | use gtk::prelude::*; 4 | use gleam::gl::{self, Gl}; 5 | use simpleservo::*; 6 | use std::cell::{Cell, RefCell}; 7 | use std::env::args; 8 | use std::rc::Rc; 9 | 10 | struct Waker(()); 11 | impl EventLoopWaker for Waker { 12 | fn clone_box(&self) -> Box { 13 | Box::new(Waker(self.0.clone())) 14 | } 15 | fn wake(&self) { 16 | //let _ = self.0.send_event(()); 17 | } 18 | } 19 | struct Callbacks; 20 | impl HostTrait for Callbacks { 21 | fn prompt_alert(&self, _msg: String, _trusted: bool) {} 22 | fn prompt_yes_no(&self, _msg: String, _trusted: bool) -> PromptResult { PromptResult::Primary } 23 | fn prompt_ok_cancel(&self, _msg: String, _trusted: bool) -> PromptResult { PromptResult::Primary } 24 | fn prompt_input(&self, _msg: String, _default: String, _trusted: bool) -> Option { None } 25 | fn show_context_menu(&self, _title: Option, _items: Vec) {} 26 | fn on_load_started(&self) {} 27 | fn on_load_ended(&self) {} 28 | fn on_title_changed(&self, _title: Option) {} 29 | fn on_allow_navigation(&self, _url: String) -> bool { true } 30 | fn on_url_changed(&self, _url: String) {} 31 | fn on_history_changed(&self, _can_go_back: bool, _can_go_forward: bool) {} 32 | /// Page animation state has changed. If animating, it's recommended 33 | /// that the embedder doesn't wait for the wake function to be called 34 | /// to call perform_updates. Usually, it means doing: 35 | /// while true { servo.perform_updates() }. This will end up calling flush 36 | /// which will call swap_buffer which will be blocking long enough to limit 37 | /// drawing at 60 FPS. 38 | /// If not animating, call perform_updates only when needed (when the embedder 39 | /// has events for Servo, or Servo has woken up the embedder event loop via 40 | /// EventLoopWaker). 41 | fn on_animating_changed(&self, _animating: bool) {} 42 | fn on_shutdown_complete(&self) { 43 | deinit(); 44 | } 45 | fn on_ime_show(&self, _input_type: InputMethodType, _text: Option, _bounds: DeviceIntRect) {} 46 | fn on_ime_hide(&self) {} 47 | fn get_clipboard_contents(&self) -> Option { None } 48 | fn set_clipboard_contents(&self, _contents: String) {} 49 | fn on_media_session_metadata(&self, _title: String, _artist: String, _album: String) {} 50 | fn on_media_session_playback_state_change(&self, _state: MediaSessionPlaybackState) {} 51 | fn on_media_session_set_position_state(&self, _duration: f64, _position: f64, _playback_rate: f64) {} 52 | fn on_devtools_started(&self, _port: Result, _token: String) {} 53 | fn on_panic(&self, _reason: String, _backtrace: Option) {} 54 | } 55 | 56 | fn call(f: F) -> T 57 | where 58 | F: FnOnce(&mut ServoGlue) -> Result, 59 | { 60 | match SERVO.with(|s| match s.borrow_mut().as_mut() { 61 | Some(ref mut s) => (f)(s), 62 | None => Err("Servo not available in this thread"), 63 | }) { 64 | Err(e) => panic!(e), 65 | Ok(r) => r, 66 | } 67 | } 68 | 69 | fn build_ui(application: >k::Application) { 70 | let window = gtk::ApplicationWindow::new(application); 71 | 72 | window.set_title("First GTK+ Program"); 73 | window.set_border_width(10); 74 | window.set_position(gtk::WindowPosition::Center); 75 | window.set_default_size(1024, 768); 76 | 77 | //let button = gtk::Button::with_label("Click me!"); 78 | 79 | //window.add(&button); 80 | 81 | let glarea = gtk::GLArea::new(); 82 | let gl = Rc::new(RefCell::new(None)); 83 | let gl2 = gl.clone(); 84 | let gtk_context = Rc::new(RefCell::new(None)); 85 | let gtk_context2 = gtk_context.clone(); 86 | let gtk_context3 = gtk_context.clone(); 87 | let fb = Rc::new(RefCell::new(0)); 88 | let fb2 = fb.clone(); 89 | 90 | glarea.connect_realize(move |widget| { 91 | if widget.get_realized() { 92 | widget.make_current(); 93 | } 94 | 95 | let context = unsafe { cgl::CGLGetCurrentContext() }; 96 | let context = surfman::NativeContext(context); 97 | 98 | let allocation = widget.get_allocation(); 99 | 100 | let gl = gl_glue::gl::init().unwrap(); 101 | *gl2.borrow_mut() = Some(gl.clone()); 102 | 103 | *fb.borrow_mut() = gl.gen_framebuffers(1)[0]; 104 | 105 | let mut prefs = std::collections::HashMap::new(); 106 | if let Some(arg) = std::env::args().nth(1) { 107 | prefs.insert("shell.homepage".to_owned(), PrefValue::Str(arg)); 108 | } 109 | 110 | let opts = InitOptions { 111 | args: vec![], 112 | coordinates: Coordinates::new( 113 | 0, 0, 114 | allocation.width, allocation.height, 115 | allocation.width, allocation.height, 116 | ), 117 | density: 2.0, //FIXME 118 | prefs: Some(prefs), 119 | xr_discovery: None, 120 | surfman_integration: SurfmanIntegration::Surface, 121 | 122 | // only used for media hardware acceleration 123 | gl_context_pointer: None, 124 | native_display_pointer: None, 125 | }; 126 | 127 | init(opts, gl, Box::new(Waker(())), Box::new(Callbacks)).unwrap(); 128 | 129 | call(|s| unsafe { 130 | let context = s.surfman().device().create_context_from_native_context(context).unwrap(); 131 | *gtk_context.borrow_mut() = Some(context); 132 | Ok(()) 133 | }); 134 | }); 135 | 136 | /*glarea.connect_resize(|_widget, width, height| { 137 | call(|s| { 138 | s.resize(Coordinates::new( 139 | 0, 0, 140 | width, height, 141 | width, height, 142 | )) 143 | }); 144 | });*/ 145 | 146 | let gl2 = gl.clone(); 147 | glarea.connect_render(move |widget, _gl_context| { 148 | let gl2 = gl2.borrow(); 149 | let gl = gl2.as_ref().unwrap(); 150 | let _ = gl.get_error(); 151 | 152 | let allocation = widget.get_allocation(); 153 | let mut wrapped_context = gtk_context2.borrow_mut(); 154 | let wrapped_context = wrapped_context.as_mut().unwrap(); 155 | assert_eq!(gl.get_error(), gl::NO_ERROR); 156 | 157 | call(|s| { 158 | s.surfman().with_front_buffer(|device, surface| { 159 | assert_eq!(gl.get_error(), gl::NO_ERROR); 160 | let info = device.surface_info(&surface); 161 | let texture = device.create_surface_texture(wrapped_context, surface).unwrap(); 162 | assert_eq!(gl.get_error(), gl::NO_ERROR); 163 | let texture_id = device.surface_texture_object(&texture); 164 | draw_texture( 165 | &**gl, 166 | device, 167 | *fb2.borrow(), 168 | texture_id, 169 | info.size, 170 | Size2D::new(allocation.width, allocation.height) 171 | ); 172 | let surface = device.destroy_surface_texture(wrapped_context, texture).unwrap(); 173 | surface 174 | }); 175 | Ok(()) 176 | }); 177 | 178 | Inhibit(false) 179 | }); 180 | 181 | let shutting_down = Rc::new(Cell::new(false)); 182 | let shutting_down2 = shutting_down.clone(); 183 | application.connect_shutdown(move |_| { 184 | let mut wrapped_context = gtk_context3.borrow_mut(); 185 | call(|s| { 186 | s.surfman().device().destroy_context(&mut wrapped_context.take().unwrap()).unwrap(); 187 | shutting_down.set(true); 188 | s.request_shutdown() 189 | }); 190 | }); 191 | 192 | glib::idle_add_local(move || { 193 | if !shutting_down2.get() { 194 | call(|s| s.perform_updates()); 195 | } 196 | Continue(!shutting_down2.get()) 197 | }); 198 | 199 | window.add(&glarea); 200 | 201 | window.show_all(); 202 | } 203 | 204 | fn draw_texture( 205 | gl: &dyn Gl, 206 | device: &surfman::Device, 207 | texture: u32, 208 | fb: u32, 209 | size: Size2D, 210 | dest: Size2D, 211 | ) { 212 | gl.bind_framebuffer(gl::READ_FRAMEBUFFER, fb); 213 | assert_eq!(gl.get_error(), gl::NO_ERROR); 214 | gl.framebuffer_texture_2d( 215 | gl::READ_FRAMEBUFFER, 216 | gl::COLOR_ATTACHMENT0, 217 | device.surface_gl_texture_target(), 218 | texture, 219 | 0, 220 | ); 221 | assert_eq!(gl.get_error(), gl::NO_ERROR); 222 | gl.blit_framebuffer( 223 | 0, 0, size.width, size.height, 224 | 0, 0, dest.width, dest.height, 225 | gl::COLOR_BUFFER_BIT, 226 | gl::LINEAR, 227 | ); 228 | assert_eq!(gl.get_error(), gl::NO_ERROR); 229 | gl.bind_framebuffer(gl::READ_FRAMEBUFFER, 0); 230 | assert_eq!(gl.get_error(), gl::NO_ERROR); 231 | gl.flush(); 232 | } 233 | 234 | fn main() { 235 | env_logger::init(); 236 | 237 | let application = 238 | gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default()) 239 | .expect("Initialization failed..."); 240 | 241 | application.connect_activate(|app| { 242 | build_ui(app); 243 | }); 244 | 245 | application.run(&args().collect::>()); 246 | } 247 | -------------------------------------------------------------------------------- /src/support.rs: -------------------------------------------------------------------------------- 1 | use euclid::default::Size2D; 2 | use glutin::{self, PossiblyCurrent}; 3 | 4 | use std::ffi::CStr; 5 | 6 | pub mod gl { 7 | pub use self::Gles2 as Gl; 8 | include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); 9 | } 10 | 11 | pub struct Gl { 12 | pub gl: gl::Gl, 13 | fb: u32, 14 | } 15 | 16 | pub fn load(gl_context: &glutin::Context) -> Gl { 17 | let gl = 18 | gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _); 19 | 20 | let version = unsafe { 21 | let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _) 22 | .to_bytes() 23 | .to_vec(); 24 | String::from_utf8(data).unwrap() 25 | }; 26 | 27 | println!("OpenGL version {}", version); 28 | 29 | let mut fb = 0; 30 | unsafe { 31 | gl.GenFramebuffers(1, &mut fb); 32 | } 33 | 34 | Gl { gl, fb } 35 | } 36 | 37 | impl Gl { 38 | #[track_caller] 39 | pub fn assert_no_error(&self) { 40 | unsafe { 41 | assert_eq!(self.gl.GetError(), gl::NO_ERROR); 42 | } 43 | } 44 | 45 | pub fn draw_texture( 46 | &self, 47 | device: &surfman::Device, 48 | texture: u32, 49 | size: Size2D, 50 | dest: Size2D, 51 | ) { 52 | unsafe { 53 | self.gl.BindFramebuffer(gl::READ_FRAMEBUFFER, self.fb); 54 | self.assert_no_error(); 55 | self.gl.FramebufferTexture2D( 56 | gl::READ_FRAMEBUFFER, 57 | gl::COLOR_ATTACHMENT0, 58 | device.surface_gl_texture_target(), 59 | //gl::TEXTURE_2D, 60 | texture, 61 | 0, 62 | ); 63 | self.assert_no_error(); 64 | self.gl.BlitFramebuffer( 65 | 0, 0, size.width, size.height, 66 | 0, 0, dest.width, dest.height, 67 | gl::COLOR_BUFFER_BIT, 68 | gl::LINEAR, 69 | ); 70 | self.assert_no_error(); 71 | self.gl.BindFramebuffer(gl::READ_FRAMEBUFFER, 0); 72 | self.assert_no_error(); 73 | self.gl.Flush(); 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------