1#![deny(unsafe_code)]
6
7use std::cell::{Cell, RefCell, RefMut};
8use std::num::NonZeroU32;
9use std::rc::Rc;
10use std::sync::Arc;
11
12use dpi::PhysicalSize;
13use embedder_traits::RefreshDriver;
14use euclid::default::{Rect, Size2D as UntypedSize2D};
15use euclid::{Point2D, Size2D};
16use gleam::gl::{self, Gl};
17use glow::{HasContext, NativeFramebuffer};
18use image::RgbaImage;
19use log::{debug, trace, warn};
20use raw_window_handle::{DisplayHandle, WindowHandle};
21pub use surfman::Error;
22use surfman::chains::{PreserveBuffer, SwapChain};
23use surfman::{
24 Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
25 GLVersion, NativeContext, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture,
26 SurfaceType,
27};
28use webrender_api::units::{DeviceIntRect, DevicePixel};
29
30pub trait RenderingContext {
36 fn prepare_for_rendering(&self) {}
39 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage>;
47 fn size(&self) -> PhysicalSize<u32>;
49 fn size2d(&self) -> Size2D<u32, DevicePixel> {
51 let size = self.size();
52 Size2D::new(size.width, size.height)
53 }
54 fn resize(&self, size: PhysicalSize<u32>);
56 fn present(&self);
59 fn make_current(&self) -> Result<(), Error>;
63 fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
65 fn glow_gl_api(&self) -> Arc<glow::Context>;
67 fn create_texture(
70 &self,
71 _surface: Surface,
72 ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
73 None
74 }
75 fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> {
77 None
78 }
79 fn connection(&self) -> Option<Connection> {
81 None
82 }
83 fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
86 None
87 }
88}
89
90struct SurfmanRenderingContext {
99 gleam_gl: Rc<dyn Gl>,
100 glow_gl: Arc<glow::Context>,
101 device: RefCell<Device>,
102 context: RefCell<Context>,
103 refresh_driver: Option<Rc<dyn RefreshDriver>>,
104}
105
106impl Drop for SurfmanRenderingContext {
107 fn drop(&mut self) {
108 let device = &mut self.device.borrow_mut();
109 let context = &mut self.context.borrow_mut();
110 let _ = device.destroy_context(context);
111 }
112}
113
114impl SurfmanRenderingContext {
115 fn new(
116 connection: &Connection,
117 adapter: &Adapter,
118 refresh_driver: Option<Rc<dyn RefreshDriver>>,
119 ) -> Result<Self, Error> {
120 let device = connection.create_device(adapter)?;
121
122 let flags = ContextAttributeFlags::ALPHA |
123 ContextAttributeFlags::DEPTH |
124 ContextAttributeFlags::STENCIL;
125 let gl_api = connection.gl_api();
126 let version = match &gl_api {
127 GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 },
128 GLApi::GL => surfman::GLVersion { major: 3, minor: 2 },
129 };
130 let context_descriptor =
131 device.create_context_descriptor(&ContextAttributes { flags, version })?;
132
133 let context = device
134 .create_context(&context_descriptor, None)
135 .inspect_err(|_| {
136 print_diagnostics_information_on_context_creation_failure(&device, gl_api, version)
137 })?;
138
139 #[expect(unsafe_code)]
140 let gleam_gl = {
141 match gl_api {
142 GLApi::GL => unsafe {
143 gl::GlFns::load_with(|func_name| device.get_proc_address(&context, func_name))
144 },
145 GLApi::GLES => unsafe {
146 gl::GlesFns::load_with(|func_name| device.get_proc_address(&context, func_name))
147 },
148 }
149 };
150
151 #[expect(unsafe_code)]
152 let glow_gl = unsafe {
153 glow::Context::from_loader_function(|function_name| {
154 device.get_proc_address(&context, function_name)
155 })
156 };
157
158 Ok(SurfmanRenderingContext {
159 gleam_gl,
160 glow_gl: Arc::new(glow_gl),
161 device: RefCell::new(device),
162 context: RefCell::new(context),
163 refresh_driver,
164 })
165 }
166
167 fn create_surface(&self, surface_type: SurfaceType<NativeWidget>) -> Result<Surface, Error> {
168 let device = &mut self.device.borrow_mut();
169 let context = &self.context.borrow();
170 device.create_surface(context, SurfaceAccess::GPUOnly, surface_type)
171 }
172
173 fn bind_surface(&self, surface: Surface) -> Result<(), Error> {
174 let device = &self.device.borrow();
175 let context = &mut self.context.borrow_mut();
176 device
177 .bind_surface_to_context(context, surface)
178 .map_err(|(err, mut surface)| {
179 let _ = device.destroy_surface(context, &mut surface);
180 err
181 })?;
182 Ok(())
183 }
184
185 fn create_attached_swap_chain(&self) -> Result<SwapChain<Device>, Error> {
186 let device = &mut self.device.borrow_mut();
187 let context = &mut self.context.borrow_mut();
188 SwapChain::create_attached(device, context, SurfaceAccess::GPUOnly)
189 }
190
191 fn resize_bound_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> {
192 if size.width == 0 || size.height == 0 {
193 log::error!("Unable to resize to size under 1x1 ({size:?} provided)");
194 return Err(Error::Failed);
195 }
196
197 let size = Size2D::new(size.width as i32, size.height as i32);
198 self.device
199 .borrow()
200 .resize_bound_surface(&mut self.context.borrow_mut(), size)
201 }
202
203 fn present_bound_surface(&self) -> Result<(), Error> {
204 self.device
205 .borrow()
206 .present_bound_surface(&mut self.context.borrow_mut())
207 }
208
209 #[expect(dead_code)]
210 fn native_context(&self) -> NativeContext {
211 let device = &self.device.borrow();
212 let context = &self.context.borrow();
213 device.native_context(context)
214 }
215
216 fn framebuffer(&self) -> Option<NativeFramebuffer> {
217 let device = &self.device.borrow();
218 let context = &self.context.borrow();
219 device
220 .context_surface_info(context)
221 .unwrap_or(None)
222 .and_then(|info| info.framebuffer_object)
223 }
224
225 fn prepare_for_rendering(&self) {
226 let framebuffer_id = self
227 .framebuffer()
228 .map_or(0, |framebuffer| framebuffer.0.into());
229 self.gleam_gl
230 .bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_id);
231 }
232
233 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
234 let framebuffer_id = self
235 .framebuffer()
236 .map_or(0, |framebuffer| framebuffer.0.into());
237 Framebuffer::read_framebuffer_to_image(&self.gleam_gl, framebuffer_id, source_rectangle)
238 }
239
240 fn make_current(&self) -> Result<(), Error> {
241 let device = &self.device.borrow();
242 let context = &mut self.context.borrow();
243 device.make_context_current(context)
244 }
245
246 fn create_texture(
247 &self,
248 surface: Surface,
249 ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
250 let device = &self.device.borrow();
251 let context = &mut self.context.borrow_mut();
252 let SurfaceInfo {
253 id: front_buffer_id,
254 size,
255 ..
256 } = device.surface_info(&surface);
257 debug!("... getting texture for surface {:?}", front_buffer_id);
258 let surface_texture = device.create_surface_texture(context, surface).unwrap();
259 let gl_texture = device
260 .surface_texture_object(&surface_texture)
261 .map(|tex| tex.0.get())
262 .unwrap_or(0);
263 Some((surface_texture, gl_texture, size))
264 }
265
266 fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
267 let device = &self.device.borrow();
268 let context = &mut self.context.borrow_mut();
269 device
270 .destroy_surface_texture(context, surface_texture)
271 .map_err(|(error, _)| error)
272 .ok()
273 }
274
275 fn connection(&self) -> Option<Connection> {
276 Some(self.device.borrow().connection())
277 }
278
279 fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
280 self.refresh_driver.clone()
281 }
282}
283
284pub struct SoftwareRenderingContext {
290 size: Cell<PhysicalSize<u32>>,
291 surfman_rendering_info: SurfmanRenderingContext,
292 swap_chain: SwapChain<Device>,
293}
294
295impl SoftwareRenderingContext {
296 pub fn new(size: PhysicalSize<u32>) -> Result<Self, Error> {
297 if size.width == 0 || size.height == 0 {
298 log::error!(
299 "Unable to create SoftwareRenderingContext with size under 1x1 ({size:?} provided)"
300 );
301 return Err(Error::Failed);
302 }
303
304 let connection = Connection::new()?;
305 let adapter = connection.create_software_adapter()?;
306 let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter, None)?;
307
308 let surfman_size = Size2D::new(size.width as i32, size.height as i32);
309 let surface =
310 surfman_rendering_info.create_surface(SurfaceType::Generic { size: surfman_size })?;
311 surfman_rendering_info.bind_surface(surface)?;
312 surfman_rendering_info.make_current()?;
313
314 let swap_chain = surfman_rendering_info.create_attached_swap_chain()?;
315 Ok(SoftwareRenderingContext {
316 size: Cell::new(size),
317 surfman_rendering_info,
318 swap_chain,
319 })
320 }
321}
322
323impl Drop for SoftwareRenderingContext {
324 fn drop(&mut self) {
325 let device = &mut self.surfman_rendering_info.device.borrow_mut();
326 let context = &mut self.surfman_rendering_info.context.borrow_mut();
327 let _ = self.swap_chain.destroy(device, context);
328 }
329}
330
331impl RenderingContext for SoftwareRenderingContext {
332 fn prepare_for_rendering(&self) {
333 self.surfman_rendering_info.prepare_for_rendering();
334 }
335
336 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
337 self.surfman_rendering_info.read_to_image(source_rectangle)
338 }
339
340 fn size(&self) -> PhysicalSize<u32> {
341 self.size.get()
342 }
343
344 fn resize(&self, size: PhysicalSize<u32>) {
345 assert!(
346 size.width > 0 && size.height > 0,
347 "Dimensions must be at least 1x1, got {size:?}",
348 );
349
350 if self.size.get() == size {
351 return;
352 }
353
354 self.size.set(size);
355
356 let device = &mut self.surfman_rendering_info.device.borrow_mut();
357 let context = &mut self.surfman_rendering_info.context.borrow_mut();
358 let size = Size2D::new(size.width as i32, size.height as i32);
359 let _ = self.swap_chain.resize(device, context, size);
360 }
361
362 #[servo_tracing::instrument(skip_all, name = "SoftwareRenderingContext::present")]
363 fn present(&self) {
364 let device = &mut self.surfman_rendering_info.device.borrow_mut();
365 let context = &mut self.surfman_rendering_info.context.borrow_mut();
366 let _ = self
367 .swap_chain
368 .swap_buffers(device, context, PreserveBuffer::No);
369 }
370
371 fn make_current(&self) -> Result<(), Error> {
372 self.surfman_rendering_info.make_current()
373 }
374
375 fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
376 self.surfman_rendering_info.gleam_gl.clone()
377 }
378
379 fn glow_gl_api(&self) -> Arc<glow::Context> {
380 self.surfman_rendering_info.glow_gl.clone()
381 }
382
383 fn create_texture(
384 &self,
385 surface: Surface,
386 ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
387 self.surfman_rendering_info.create_texture(surface)
388 }
389
390 fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
391 self.surfman_rendering_info.destroy_texture(surface_texture)
392 }
393
394 fn connection(&self) -> Option<Connection> {
395 self.surfman_rendering_info.connection()
396 }
397}
398
399pub struct WindowRenderingContext {
408 size: Cell<PhysicalSize<u32>>,
410 surfman_context: SurfmanRenderingContext,
411}
412
413impl WindowRenderingContext {
414 pub fn new(
415 display_handle: DisplayHandle,
416 window_handle: WindowHandle,
417 size: PhysicalSize<u32>,
418 ) -> Result<Self, Error> {
419 Self::new_with_optional_refresh_driver(display_handle, window_handle, size, None)
420 }
421
422 pub fn new_with_refresh_driver(
423 display_handle: DisplayHandle,
424 window_handle: WindowHandle,
425 size: PhysicalSize<u32>,
426 refresh_driver: Rc<dyn RefreshDriver>,
427 ) -> Result<Self, Error> {
428 Self::new_with_optional_refresh_driver(
429 display_handle,
430 window_handle,
431 size,
432 Some(refresh_driver),
433 )
434 }
435
436 fn new_with_optional_refresh_driver(
437 display_handle: DisplayHandle,
438 window_handle: WindowHandle,
439 size: PhysicalSize<u32>,
440 refresh_driver: Option<Rc<dyn RefreshDriver>>,
441 ) -> Result<Self, Error> {
442 if size.width == 0 || size.height == 0 {
443 log::error!(
444 "Unable to create WindowRenderingContext with size under 1x1 ({size:?} provided)"
445 );
446 return Err(Error::Failed);
447 }
448
449 let connection = Connection::from_display_handle(display_handle)?;
450 let adapter = connection.create_adapter()?;
451 let surfman_context = SurfmanRenderingContext::new(&connection, &adapter, refresh_driver)?;
452
453 let native_widget = connection
454 .create_native_widget_from_window_handle(
455 window_handle,
456 Size2D::new(size.width as i32, size.height as i32),
457 )
458 .expect("Failed to create native widget");
459
460 let surface = surfman_context.create_surface(SurfaceType::Widget { native_widget })?;
461 surfman_context.bind_surface(surface)?;
462 surfman_context.make_current()?;
463
464 Ok(Self {
465 size: Cell::new(size),
466 surfman_context,
467 })
468 }
469
470 pub fn offscreen_context(
471 self: &Rc<Self>,
472 size: PhysicalSize<u32>,
473 ) -> OffscreenRenderingContext {
474 OffscreenRenderingContext::new(self.clone(), size)
475 }
476
477 pub fn take_window(&self) -> Result<(), Error> {
482 let device = self.surfman_context.device.borrow_mut();
483 let mut context = self.surfman_context.context.borrow_mut();
484 let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap();
485 device.destroy_surface(&mut context, &mut surface)?;
486 Ok(())
487 }
488
489 pub fn set_window(
494 &self,
495 window_handle: WindowHandle,
496 size: PhysicalSize<u32>,
497 ) -> Result<(), Error> {
498 let device = self.surfman_context.device.borrow_mut();
499 let mut context = self.surfman_context.context.borrow_mut();
500
501 let native_widget = device
502 .connection()
503 .create_native_widget_from_window_handle(
504 window_handle,
505 Size2D::new(size.width as i32, size.height as i32),
506 )
507 .expect("Failed to create native widget");
508
509 let surface_access = SurfaceAccess::GPUOnly;
510 let surface_type = SurfaceType::Widget { native_widget };
511 let surface = device.create_surface(&context, surface_access, surface_type)?;
512
513 device
514 .bind_surface_to_context(&mut context, surface)
515 .map_err(|(err, mut surface)| {
516 let _ = device.destroy_surface(&mut context, &mut surface);
517 err
518 })?;
519 device.make_context_current(&context)?;
520 Ok(())
521 }
522
523 pub fn surfman_details(&self) -> (RefMut<'_, Device>, RefMut<'_, Context>) {
524 (
525 self.surfman_context.device.borrow_mut(),
526 self.surfman_context.context.borrow_mut(),
527 )
528 }
529}
530
531impl RenderingContext for WindowRenderingContext {
532 fn prepare_for_rendering(&self) {
533 self.surfman_context.prepare_for_rendering();
534 }
535
536 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
537 self.surfman_context.read_to_image(source_rectangle)
538 }
539
540 fn size(&self) -> PhysicalSize<u32> {
541 self.size.get()
542 }
543
544 fn resize(&self, size: PhysicalSize<u32>) {
545 match self.surfman_context.resize_bound_surface(size) {
546 Ok(..) => self.size.set(size),
547 Err(error) => warn!("Error resizing surface: {error:?}"),
548 }
549 }
550
551 #[servo_tracing::instrument(skip_all, name = "WindowRenderingContext::present")]
552 fn present(&self) {
553 if let Err(error) = self.surfman_context.present_bound_surface() {
554 warn!("Error presenting surface: {error:?}");
555 }
556 }
557
558 fn make_current(&self) -> Result<(), Error> {
559 self.surfman_context.make_current()
560 }
561
562 fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
563 self.surfman_context.gleam_gl.clone()
564 }
565
566 fn glow_gl_api(&self) -> Arc<glow::Context> {
567 self.surfman_context.glow_gl.clone()
568 }
569
570 fn create_texture(
571 &self,
572 surface: Surface,
573 ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
574 self.surfman_context.create_texture(surface)
575 }
576
577 fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
578 self.surfman_context.destroy_texture(surface_texture)
579 }
580
581 fn connection(&self) -> Option<Connection> {
582 self.surfman_context.connection()
583 }
584
585 fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
586 self.surfman_context.refresh_driver()
587 }
588}
589
590struct Framebuffer {
591 gl: Rc<dyn Gl>,
592 framebuffer_id: gl::GLuint,
593 renderbuffer_id: gl::GLuint,
594 texture_id: gl::GLuint,
595}
596
597impl Framebuffer {
598 fn bind(&self) {
599 trace!("Binding FBO {}", self.framebuffer_id);
600 self.gl
601 .bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id)
602 }
603}
604
605impl Drop for Framebuffer {
606 fn drop(&mut self) {
607 self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
608 self.gl.delete_textures(&[self.texture_id]);
609 self.gl.delete_renderbuffers(&[self.renderbuffer_id]);
610 self.gl.delete_framebuffers(&[self.framebuffer_id]);
611 }
612}
613
614impl Framebuffer {
615 fn new(gl: Rc<dyn Gl>, size: PhysicalSize<u32>) -> Self {
616 let framebuffer_ids = gl.gen_framebuffers(1);
617 gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
618
619 let texture_ids = gl.gen_textures(1);
620 gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
621 gl.tex_image_2d(
622 gl::TEXTURE_2D,
623 0,
624 gl::RGBA as gl::GLint,
625 size.width as gl::GLsizei,
626 size.height as gl::GLsizei,
627 0,
628 gl::RGBA,
629 gl::UNSIGNED_BYTE,
630 None,
631 );
632 gl.tex_parameter_i(
633 gl::TEXTURE_2D,
634 gl::TEXTURE_MAG_FILTER,
635 gl::NEAREST as gl::GLint,
636 );
637 gl.tex_parameter_i(
638 gl::TEXTURE_2D,
639 gl::TEXTURE_MIN_FILTER,
640 gl::NEAREST as gl::GLint,
641 );
642
643 gl.framebuffer_texture_2d(
644 gl::FRAMEBUFFER,
645 gl::COLOR_ATTACHMENT0,
646 gl::TEXTURE_2D,
647 texture_ids[0],
648 0,
649 );
650
651 gl.bind_texture(gl::TEXTURE_2D, 0);
652
653 let renderbuffer_ids = gl.gen_renderbuffers(1);
654 let depth_rb = renderbuffer_ids[0];
655 gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
656 gl.renderbuffer_storage(
657 gl::RENDERBUFFER,
658 gl::DEPTH_COMPONENT24,
659 size.width as gl::GLsizei,
660 size.height as gl::GLsizei,
661 );
662 gl.framebuffer_renderbuffer(
663 gl::FRAMEBUFFER,
664 gl::DEPTH_ATTACHMENT,
665 gl::RENDERBUFFER,
666 depth_rb,
667 );
668
669 Self {
670 gl,
671 framebuffer_id: *framebuffer_ids
672 .first()
673 .expect("Guaranteed by GL operations"),
674 renderbuffer_id: *renderbuffer_ids
675 .first()
676 .expect("Guaranteed by GL operations"),
677 texture_id: *texture_ids.first().expect("Guaranteed by GL operations"),
678 }
679 }
680
681 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
682 Self::read_framebuffer_to_image(&self.gl, self.framebuffer_id, source_rectangle)
683 }
684
685 fn read_framebuffer_to_image(
686 gl: &Rc<dyn Gl>,
687 framebuffer_id: u32,
688 source_rectangle: DeviceIntRect,
689 ) -> Option<RgbaImage> {
690 gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
691
692 gl.bind_vertex_array(0);
699
700 let mut pixels = gl.read_pixels(
701 source_rectangle.min.x,
702 source_rectangle.min.y,
703 source_rectangle.width(),
704 source_rectangle.height(),
705 gl::RGBA,
706 gl::UNSIGNED_BYTE,
707 );
708 let gl_error = gl.get_error();
709 if gl_error != gl::NO_ERROR {
710 warn!("GL error code 0x{gl_error:x} set after read_pixels");
711 }
712
713 let source_rectangle = source_rectangle.to_usize();
715 let orig_pixels = pixels.clone();
716 let stride = source_rectangle.width() * 4;
717 for y in 0..source_rectangle.height() {
718 let dst_start = y * stride;
719 let src_start = (source_rectangle.height() - y - 1) * stride;
720 let src_slice = &orig_pixels[src_start..src_start + stride];
721 pixels[dst_start..dst_start + stride].clone_from_slice(&src_slice[..stride]);
722 }
723
724 RgbaImage::from_raw(
725 source_rectangle.width() as u32,
726 source_rectangle.height() as u32,
727 pixels,
728 )
729 }
730}
731
732pub struct OffscreenRenderingContext {
733 parent_context: Rc<WindowRenderingContext>,
734 size: Cell<PhysicalSize<u32>>,
735 framebuffer: RefCell<Framebuffer>,
736}
737
738type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>;
739
740impl OffscreenRenderingContext {
741 fn new(parent_context: Rc<WindowRenderingContext>, size: PhysicalSize<u32>) -> Self {
742 assert!(
743 size.width != 0 && size.height != 0,
744 "Dimensions must be at least 1x1, got {size:?}",
745 );
746
747 let framebuffer = RefCell::new(Framebuffer::new(parent_context.gleam_gl_api(), size));
748 Self {
749 parent_context,
750 size: Cell::new(size),
751 framebuffer,
752 }
753 }
754
755 pub fn parent_context(&self) -> &WindowRenderingContext {
756 &self.parent_context
757 }
758
759 pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> {
760 let front_framebuffer_id =
762 NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer)?;
763 let parent_context_framebuffer_id = self.parent_context.surfman_context.framebuffer();
764 let size = self.size.get();
765 let size = Size2D::new(size.width as i32, size.height as i32);
766 Some(Box::new(move |gl, target_rect| {
767 Self::blit_framebuffer(
768 gl,
769 Rect::new(Point2D::origin(), size.to_i32()),
770 front_framebuffer_id,
771 target_rect,
772 parent_context_framebuffer_id,
773 );
774 }))
775 }
776
777 #[expect(unsafe_code)]
778 fn blit_framebuffer(
779 gl: &glow::Context,
780 source_rect: Rect<i32>,
781 source_framebuffer_id: NativeFramebuffer,
782 target_rect: Rect<i32>,
783 target_framebuffer_id: Option<NativeFramebuffer>,
784 ) {
785 use glow::HasContext as _;
786 unsafe {
787 gl.clear_color(0.0, 0.0, 0.0, 0.0);
788 gl.scissor(
789 target_rect.origin.x,
790 target_rect.origin.y,
791 target_rect.width(),
792 target_rect.height(),
793 );
794 gl.enable(gl::SCISSOR_TEST);
795 gl.clear(gl::COLOR_BUFFER_BIT);
796 gl.disable(gl::SCISSOR_TEST);
797
798 gl.bind_framebuffer(gl::READ_FRAMEBUFFER, Some(source_framebuffer_id));
799 gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, target_framebuffer_id);
800
801 gl.blit_framebuffer(
802 source_rect.origin.x,
803 source_rect.origin.y,
804 source_rect.origin.x + source_rect.width(),
805 source_rect.origin.y + source_rect.height(),
806 target_rect.origin.x,
807 target_rect.origin.y,
808 target_rect.origin.x + target_rect.width(),
809 target_rect.origin.y + target_rect.height(),
810 gl::COLOR_BUFFER_BIT,
811 gl::NEAREST,
812 );
813 gl.bind_framebuffer(gl::FRAMEBUFFER, target_framebuffer_id);
814 }
815 }
816}
817
818impl RenderingContext for OffscreenRenderingContext {
819 fn size(&self) -> PhysicalSize<u32> {
820 self.size.get()
821 }
822
823 fn resize(&self, new_size: PhysicalSize<u32>) {
824 assert!(
825 new_size.width != 0 && new_size.height != 0,
826 "Dimensions must be at least 1x1, got {new_size:?}",
827 );
828
829 let old_size = self.size.get();
830 if old_size == new_size {
831 return;
832 }
833
834 let gl = self.parent_context.gleam_gl_api();
835 let new_framebuffer = Framebuffer::new(gl.clone(), new_size);
836
837 let old_framebuffer =
838 std::mem::replace(&mut *self.framebuffer.borrow_mut(), new_framebuffer);
839 self.size.set(new_size);
840
841 let blit_size = new_size.min(old_size);
842 let rect = Rect::new(
843 Point2D::origin(),
844 Size2D::new(blit_size.width, blit_size.height),
845 )
846 .to_i32();
847
848 let Some(old_framebuffer_id) =
849 NonZeroU32::new(old_framebuffer.framebuffer_id).map(NativeFramebuffer)
850 else {
851 return;
852 };
853 let new_framebuffer_id =
854 NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer);
855 Self::blit_framebuffer(
856 &self.glow_gl_api(),
857 rect,
858 old_framebuffer_id,
859 rect,
860 new_framebuffer_id,
861 );
862 }
863
864 fn prepare_for_rendering(&self) {
865 self.framebuffer.borrow().bind();
866 }
867
868 fn present(&self) {}
869
870 fn make_current(&self) -> Result<(), surfman::Error> {
871 self.parent_context.make_current()
872 }
873
874 fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
875 self.parent_context.gleam_gl_api()
876 }
877
878 fn glow_gl_api(&self) -> Arc<glow::Context> {
879 self.parent_context.glow_gl_api()
880 }
881
882 fn create_texture(
883 &self,
884 surface: Surface,
885 ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
886 self.parent_context.create_texture(surface)
887 }
888
889 fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
890 self.parent_context.destroy_texture(surface_texture)
891 }
892
893 fn connection(&self) -> Option<Connection> {
894 self.parent_context.connection()
895 }
896
897 fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
898 self.framebuffer.borrow().read_to_image(source_rectangle)
899 }
900
901 fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
902 self.parent_context().refresh_driver()
903 }
904}
905
906fn print_diagnostics_information_on_context_creation_failure(
907 device: &Device,
908 desired_api: GLApi,
909 desired_version: GLVersion,
910) {
911 println!("===============================================================");
912 println!(
913 "Could not create a {desired_api:?} {:?}.{:?} context when starting Servo.",
914 desired_version.major, desired_version.minor
915 );
916
917 let version = surfman::GLVersion { major: 1, minor: 0 };
918 match device
919 .create_context_descriptor(&ContextAttributes {
920 flags: ContextAttributeFlags::empty(),
921 version,
922 })
923 .and_then(|context_descriptor| device.create_context(&context_descriptor, None))
924 {
925 Ok(mut context) => {
926 #[expect(unsafe_code)]
927 let glow_gl = unsafe {
928 glow::Context::from_loader_function(|function_name| {
929 device.get_proc_address(&context, function_name)
930 })
931 };
932
933 println!(
934 "It's likely that your version of OpenGL ({:?}.{:?}) is too old.",
935 glow_gl.version().major,
936 glow_gl.version().minor
937 );
938 println!("If not, please file a bug at https://github.com/servo/servo/issues.");
939 let _ = device.destroy_context(&mut context);
940 },
941 Err(_) => {
942 println!("Could not create any {desired_api:?} context.");
943 println!("Ensure that OpenGL is working on your system.");
944 println!("If it is, please file a bug at https://github.com/servo/servo/issues.");
945 },
946 }
947 println!("===============================================================\n");
948}
949
950#[cfg(test)]
951mod test {
952 use dpi::PhysicalSize;
953 use euclid::{Box2D, Point2D, Size2D};
954 use gleam::gl;
955 use image::Rgba;
956 use surfman::{Connection, ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion};
957
958 use super::Framebuffer;
959 use crate::rendering_context::SoftwareRenderingContext;
960
961 #[test]
962 #[expect(unsafe_code)]
963 fn test_read_pixels() -> Result<(), Error> {
964 let connection = Connection::new()?;
965 let adapter = connection.create_software_adapter()?;
966 let device = connection.create_device(&adapter)?;
967 let context_descriptor = device.create_context_descriptor(&ContextAttributes {
968 version: GLVersion::new(3, 0),
969 flags: ContextAttributeFlags::empty(),
970 })?;
971 let mut context = device.create_context(&context_descriptor, None)?;
972
973 let gl = match connection.gl_api() {
974 GLApi::GL => unsafe { gl::GlFns::load_with(|s| device.get_proc_address(&context, s)) },
975 GLApi::GLES => unsafe {
976 gl::GlesFns::load_with(|s| device.get_proc_address(&context, s))
977 },
978 };
979
980 device.make_context_current(&context)?;
981
982 {
983 const SIZE: u32 = 16;
984 let framebuffer = Framebuffer::new(gl, PhysicalSize::new(SIZE, SIZE));
985 framebuffer.bind();
986 framebuffer
987 .gl
988 .clear_color(12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0, 78.0 / 255.0);
989 framebuffer.gl.clear(gl::COLOR_BUFFER_BIT);
990
991 let rect = Box2D::from_origin_and_size(Point2D::zero(), Size2D::new(SIZE, SIZE));
992 let img = framebuffer
993 .read_to_image(rect.to_i32())
994 .expect("Should have been able to read back image.");
995 assert_eq!(img.width(), SIZE);
996 assert_eq!(img.height(), SIZE);
997
998 let expected_pixel: Rgba<u8> = Rgba([12, 34, 56, 78]);
999 assert!(img.pixels().all(|&p| p == expected_pixel));
1000 }
1001
1002 device.destroy_context(&mut context)?;
1003
1004 Ok(())
1005 }
1006
1007 #[test]
1008 fn test_minimum_size_error() {
1009 let result = SoftwareRenderingContext::new(PhysicalSize {
1010 width: 0,
1011 height: 1,
1012 });
1013 match result {
1014 Err(surfman::Error::Failed) => (),
1015 _ => panic!("Expected {:?}", surfman::Error::Failed),
1016 }
1017 }
1018}