/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![allow(clippy::missing_safety_doc)] #![allow(clippy::not_unsafe_ptr_arg_deref)] use gleam::gl; use std::cell::RefCell; #[cfg(not(target_os = "macos"))] use std::ffi::OsString; use std::ffi::{CStr, CString}; use std::io::Cursor; use std::marker::PhantomData; use std::ops::Range; #[cfg(target_os = "android")] use std::os::raw::c_int; use std::os::raw::{c_char, c_float, c_void}; #[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::os::unix::ffi::OsStringExt; #[cfg(target_os = "windows")] use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; use std::{env, mem, ptr, slice}; use thin_vec::ThinVec; use euclid::SideOffsets2D; use moz2d_renderer::Moz2dBlobImageHandler; use nsstring::nsAString; use num_cpus; use program_cache::{remove_disk_cache, WrProgramCache}; use rayon; use tracy_rs::register_thread_with_profiler; use webrender::sw_compositor::SwCompositor; use webrender::{ api::units::*, api::*, create_webrender_instance, render_api::*, set_profiler_hooks, AsyncPropertySampler, AsyncScreenshotHandle, Compositor, CompositorCapabilities, CompositorConfig, CompositorSurfaceTransform, DebugFlags, Device, MappableCompositor, MappedTileInfo, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, PartialPresentCompositor, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererStats, SWGLCompositeSurfaceInfo, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, SharedShaders, TextureCacheConfig, UploadMethod, WebRenderOptions, WindowVisibility, ONE_TIME_USAGE_HINT, }; use wr_malloc_size_of::MallocSizeOfOps; extern "C" { #[cfg(target_os = "android")] fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int; } /// The unique id for WR resource identification. static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1); /// Special value handled in this wrapper layer to signify a redundant clip chain. pub const ROOT_CLIP_CHAIN: u64 = !0; fn next_namespace_id() -> IdNamespace { IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32) } /// Whether a border should be antialiased. #[repr(C)] #[derive(Eq, PartialEq, Copy, Clone)] pub enum AntialiasBorder { No = 0, Yes, } /// Used to indicate if an image is opaque, or has an alpha channel. #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OpacityType { Opaque = 0, HasAlphaChannel = 1, } /// cbindgen:field-names=[mHandle] /// cbindgen:derive-lt=true /// cbindgen:derive-lte=true /// cbindgen:derive-neq=true type WrEpoch = Epoch; /// cbindgen:field-names=[mHandle] /// cbindgen:derive-lt=true /// cbindgen:derive-lte=true /// cbindgen:derive-neq=true pub type WrIdNamespace = IdNamespace; /// cbindgen:field-names=[mNamespace, mHandle] type WrDocumentId = DocumentId; /// cbindgen:field-names=[mNamespace, mHandle] type WrPipelineId = PipelineId; /// cbindgen:field-names=[mNamespace, mHandle] /// cbindgen:derive-neq=true type WrImageKey = ImageKey; /// cbindgen:field-names=[mNamespace, mHandle] pub type WrFontKey = FontKey; /// cbindgen:field-names=[mNamespace, mHandle] pub type WrFontInstanceKey = FontInstanceKey; /// cbindgen:field-names=[mNamespace, mHandle] type WrYuvColorSpace = YuvColorSpace; /// cbindgen:field-names=[mNamespace, mHandle] type WrColorDepth = ColorDepth; /// cbindgen:field-names=[mNamespace, mHandle] type WrColorRange = ColorRange; #[inline] fn clip_chain_id_to_webrender(id: u64, pipeline_id: WrPipelineId) -> ClipChainId { if id == ROOT_CLIP_CHAIN { ClipChainId::INVALID } else { ClipChainId(id, pipeline_id) } } #[repr(C)] pub struct WrSpaceAndClipChain { space: WrSpatialId, clip_chain: u64, } impl WrSpaceAndClipChain { fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo { //Warning: special case here to support dummy clip chain SpaceAndClipInfo { spatial_id: self.space.to_webrender(pipeline_id), clip_chain_id: clip_chain_id_to_webrender(self.clip_chain, pipeline_id), } } } #[repr(C)] pub enum WrStackingContextClip { None, ClipChain(u64), } impl WrStackingContextClip { fn to_webrender(&self, pipeline_id: WrPipelineId) -> Option { match *self { WrStackingContextClip::None => None, WrStackingContextClip::ClipChain(id) => { if id == ROOT_CLIP_CHAIN { None } else { Some(ClipChainId(id, pipeline_id)) } }, } } } unsafe fn make_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] { if ptr.is_null() { &[] } else { slice::from_raw_parts(ptr, len) } } unsafe fn make_slice_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T] { if ptr.is_null() { &mut [] } else { slice::from_raw_parts_mut(ptr, len) } } pub struct DocumentHandle { api: RenderApi, document_id: DocumentId, // One of the two options below is Some and the other None at all times. // It would be nice to model with an enum, however it is tricky to express // moving a variant's content into another variant without moving the // containing enum. hit_tester_request: Option, hit_tester: Option>, } impl DocumentHandle { pub fn new( api: RenderApi, hit_tester: Option>, size: DeviceIntSize, id: u32, ) -> DocumentHandle { let doc = api.add_document_with_id(size, id); let hit_tester_request = if hit_tester.is_none() { // Request the hit tester early to reduce the likelihood of blocking on the // first hit testing query. Some(api.request_hit_tester(doc)) } else { None }; DocumentHandle { api, document_id: doc, hit_tester_request, hit_tester, } } fn ensure_hit_tester(&mut self) -> &Arc { if let Some(ref ht) = self.hit_tester { return ht; } self.hit_tester = Some(self.hit_tester_request.take().unwrap().resolve()); self.hit_tester.as_ref().unwrap() } } #[repr(C)] pub struct WrVecU8 { data: *mut u8, length: usize, capacity: usize, } impl WrVecU8 { fn into_vec(self) -> Vec { unsafe { Vec::from_raw_parts(self.data, self.length, self.capacity) } } // Equivalent to `into_vec` but clears self instead of consuming the value. fn flush_into_vec(&mut self) -> Vec { self.convert_into_vec::() } // Like flush_into_vec, but also does an unsafe conversion to the desired type. fn convert_into_vec(&mut self) -> Vec { let vec = unsafe { Vec::from_raw_parts( self.data as *mut T, self.length / mem::size_of::(), self.capacity / mem::size_of::(), ) }; self.data = ptr::null_mut(); self.length = 0; self.capacity = 0; vec } fn from_vec(mut v: Vec) -> WrVecU8 { let w = WrVecU8 { data: v.as_mut_ptr(), length: v.len(), capacity: v.capacity(), }; mem::forget(v); w } fn reserve(&mut self, len: usize) { let mut vec = self.flush_into_vec(); vec.reserve(len); *self = Self::from_vec(vec); } fn push_bytes(&mut self, bytes: &[u8]) { let mut vec = self.flush_into_vec(); vec.extend_from_slice(bytes); *self = Self::from_vec(vec); } } #[no_mangle] pub extern "C" fn wr_vec_u8_push_bytes(v: &mut WrVecU8, bytes: ByteSlice) { v.push_bytes(bytes.as_slice()); } #[no_mangle] pub extern "C" fn wr_vec_u8_reserve(v: &mut WrVecU8, len: usize) { v.reserve(len); } #[no_mangle] pub extern "C" fn wr_vec_u8_free(v: WrVecU8) { v.into_vec(); } #[repr(C)] pub struct ByteSlice<'a> { buffer: *const u8, len: usize, _phantom: PhantomData<&'a ()>, } impl<'a> ByteSlice<'a> { pub fn new(slice: &'a [u8]) -> ByteSlice<'a> { ByteSlice { buffer: slice.as_ptr(), len: slice.len(), _phantom: PhantomData, } } pub fn as_slice(&self) -> &'a [u8] { unsafe { make_slice(self.buffer, self.len) } } } #[repr(C)] pub struct MutByteSlice<'a> { buffer: *mut u8, len: usize, _phantom: PhantomData<&'a ()>, } impl<'a> MutByteSlice<'a> { pub fn new(slice: &'a mut [u8]) -> MutByteSlice<'a> { let len = slice.len(); MutByteSlice { buffer: slice.as_mut_ptr(), len, _phantom: PhantomData, } } pub fn as_mut_slice(&mut self) -> &'a mut [u8] { unsafe { make_slice_mut(self.buffer, self.len) } } } #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct WrImageDescriptor { pub format: ImageFormat, pub width: i32, pub height: i32, pub stride: i32, pub opacity: OpacityType, // TODO(gw): Remove this flag (use prim flags instead). pub prefer_compositor_surface: bool, } impl<'a> From<&'a WrImageDescriptor> for ImageDescriptor { fn from(desc: &'a WrImageDescriptor) -> ImageDescriptor { let mut flags = ImageDescriptorFlags::empty(); if desc.opacity == OpacityType::Opaque { flags |= ImageDescriptorFlags::IS_OPAQUE; } ImageDescriptor { size: DeviceIntSize::new(desc.width, desc.height), stride: if desc.stride != 0 { Some(desc.stride) } else { None }, format: desc.format, offset: 0, flags, } } } #[repr(u32)] #[allow(dead_code)] enum WrExternalImageType { RawData, NativeTexture, Invalid, } #[repr(C)] struct WrExternalImage { image_type: WrExternalImageType, // external texture handle handle: u32, // external texture coordinate u0: f32, v0: f32, u1: f32, v1: f32, // external image buffer buff: *const u8, size: usize, } extern "C" { fn wr_renderer_lock_external_image( renderer: *mut c_void, external_image_id: ExternalImageId, channel_index: u8, ) -> WrExternalImage; fn wr_renderer_unlock_external_image(renderer: *mut c_void, external_image_id: ExternalImageId, channel_index: u8); } #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct WrExternalImageHandler { external_image_obj: *mut c_void, } impl ExternalImageHandler for WrExternalImageHandler { fn lock(&mut self, id: ExternalImageId, channel_index: u8) -> ExternalImage { let image = unsafe { wr_renderer_lock_external_image(self.external_image_obj, id, channel_index) }; ExternalImage { uv: TexelRect::new(image.u0, image.v0, image.u1, image.v1), source: match image.image_type { WrExternalImageType::NativeTexture => ExternalImageSource::NativeTexture(image.handle), WrExternalImageType::RawData => { ExternalImageSource::RawData(unsafe { make_slice(image.buff, image.size) }) }, WrExternalImageType::Invalid => ExternalImageSource::Invalid, }, } } fn unlock(&mut self, id: ExternalImageId, channel_index: u8) { unsafe { wr_renderer_unlock_external_image(self.external_image_obj, id, channel_index); } } } #[repr(C)] #[derive(Clone, Copy)] // Used for ComponentTransfer only pub struct WrFilterData { funcR_type: ComponentTransferFuncType, R_values: *mut c_float, R_values_count: usize, funcG_type: ComponentTransferFuncType, G_values: *mut c_float, G_values_count: usize, funcB_type: ComponentTransferFuncType, B_values: *mut c_float, B_values_count: usize, funcA_type: ComponentTransferFuncType, A_values: *mut c_float, A_values_count: usize, } #[repr(u32)] #[derive(Debug)] pub enum WrAnimationType { Transform = 0, Opacity = 1, BackgroundColor = 2, } #[repr(C)] pub struct WrAnimationProperty { effect_type: WrAnimationType, id: u64, key: SpatialTreeItemKey, } /// cbindgen:derive-eq=false #[repr(C)] #[derive(Debug)] pub struct WrAnimationPropertyValue { pub id: u64, pub value: T, } pub type WrTransformProperty = WrAnimationPropertyValue; pub type WrOpacityProperty = WrAnimationPropertyValue; pub type WrColorProperty = WrAnimationPropertyValue; /// cbindgen:field-names=[mHandle] /// cbindgen:derive-lt=true /// cbindgen:derive-lte=true #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct WrWindowId(u64); #[repr(C)] #[derive(Debug)] pub struct WrComputedTransformData { pub scale_from: LayoutSize, pub vertical_flip: bool, pub rotation: WrRotation, pub key: SpatialTreeItemKey, } #[repr(C)] pub struct WrTransformInfo { pub transform: LayoutTransform, pub key: SpatialTreeItemKey, } fn get_proc_address(glcontext_ptr: *mut c_void, name: &str) -> *const c_void { extern "C" { fn get_proc_address_from_glcontext(glcontext_ptr: *mut c_void, procname: *const c_char) -> *const c_void; } let symbol_name = CString::new(name).unwrap(); let symbol = unsafe { get_proc_address_from_glcontext(glcontext_ptr, symbol_name.as_ptr()) }; symbol as *const _ } #[repr(C)] pub enum TelemetryProbe { SceneBuildTime = 0, SceneSwapTime = 1, FrameBuildTime = 2, } extern "C" { fn is_in_compositor_thread() -> bool; fn is_in_render_thread() -> bool; fn is_in_main_thread() -> bool; fn is_glcontext_gles(glcontext_ptr: *mut c_void) -> bool; fn is_glcontext_angle(glcontext_ptr: *mut c_void) -> bool; fn gfx_wr_resource_path_override() -> *const c_char; fn gfx_wr_use_optimized_shaders() -> bool; // TODO: make gfx_critical_error() work. // We still have problem to pass the error message from render/render_backend // thread to main thread now. #[allow(dead_code)] fn gfx_critical_error(msg: *const c_char); fn gfx_critical_note(msg: *const c_char); fn gfx_wr_set_crash_annotation(annotation: CrashAnnotation, value: *const c_char); fn gfx_wr_clear_crash_annotation(annotation: CrashAnnotation); } struct CppNotifier { window_id: WrWindowId, } unsafe impl Send for CppNotifier {} extern "C" { fn wr_notifier_wake_up(window_id: WrWindowId, composite_needed: bool); fn wr_notifier_new_frame_ready(window_id: WrWindowId, composite_needed: bool, publish_id: FramePublishId); fn wr_notifier_external_event(window_id: WrWindowId, raw_event: usize); fn wr_schedule_render(window_id: WrWindowId, reasons: RenderReasons); // NOTE: This moves away from pipeline_info. fn wr_finished_scene_build(window_id: WrWindowId, pipeline_info: &mut WrPipelineInfo); fn wr_transaction_notification_notified(handler: usize, when: Checkpoint); } impl RenderNotifier for CppNotifier { fn clone(&self) -> Box { Box::new(CppNotifier { window_id: self.window_id, }) } fn wake_up(&self, composite_needed: bool) { unsafe { wr_notifier_wake_up(self.window_id, composite_needed); } } fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool, publish_id: FramePublishId) { unsafe { wr_notifier_new_frame_ready(self.window_id, composite_needed, publish_id); } } fn external_event(&self, event: ExternalEvent) { unsafe { wr_notifier_external_event(self.window_id, event.unwrap()); } } } struct MozCrashAnnotator; unsafe impl Send for MozCrashAnnotator {} impl CrashAnnotator for MozCrashAnnotator { fn set(&self, annotation: CrashAnnotation, value: &std::ffi::CStr) { unsafe { gfx_wr_set_crash_annotation(annotation, value.as_ptr()); } } fn clear(&self, annotation: CrashAnnotation) { unsafe { gfx_wr_clear_crash_annotation(annotation); } } fn box_clone(&self) -> Box { Box::new(MozCrashAnnotator) } } #[no_mangle] pub extern "C" fn wr_renderer_set_clear_color(renderer: &mut Renderer, color: ColorF) { renderer.set_clear_color(color); } #[no_mangle] pub extern "C" fn wr_renderer_set_external_image_handler( renderer: &mut Renderer, external_image_handler: &mut WrExternalImageHandler, ) { renderer.set_external_image_handler(Box::new(*external_image_handler)); } #[no_mangle] pub extern "C" fn wr_renderer_update(renderer: &mut Renderer) { renderer.update(); } #[no_mangle] pub extern "C" fn wr_renderer_set_target_frame_publish_id(renderer: &mut Renderer, publish_id: FramePublishId) { renderer.set_target_frame_publish_id(publish_id); } #[no_mangle] pub extern "C" fn wr_renderer_render( renderer: &mut Renderer, width: i32, height: i32, buffer_age: usize, out_stats: &mut RendererStats, out_dirty_rects: &mut ThinVec, ) -> bool { match renderer.render(DeviceIntSize::new(width, height), buffer_age) { Ok(results) => { *out_stats = results.stats; out_dirty_rects.extend(results.dirty_rects); true }, Err(errors) => { for e in errors { warn!(" Failed to render: {:?}", e); let msg = CString::new(format!("wr_renderer_render: {:?}", e)).unwrap(); unsafe { gfx_critical_note(msg.as_ptr()); } } false }, } } #[no_mangle] pub extern "C" fn wr_renderer_force_redraw(renderer: &mut Renderer) { renderer.force_redraw(); } #[no_mangle] pub extern "C" fn wr_renderer_record_frame( renderer: &mut Renderer, image_format: ImageFormat, out_handle: &mut RecordedFrameHandle, out_width: &mut i32, out_height: &mut i32, ) -> bool { if let Some((handle, size)) = renderer.record_frame(image_format) { *out_handle = handle; *out_width = size.width; *out_height = size.height; true } else { false } } #[no_mangle] pub extern "C" fn wr_renderer_map_recorded_frame( renderer: &mut Renderer, handle: RecordedFrameHandle, dst_buffer: *mut u8, dst_buffer_len: usize, dst_stride: usize, ) -> bool { renderer.map_recorded_frame( handle, unsafe { make_slice_mut(dst_buffer, dst_buffer_len) }, dst_stride, ) } #[no_mangle] pub extern "C" fn wr_renderer_release_composition_recorder_structures(renderer: &mut Renderer) { renderer.release_composition_recorder_structures(); } #[no_mangle] pub extern "C" fn wr_renderer_get_screenshot_async( renderer: &mut Renderer, window_x: i32, window_y: i32, window_width: i32, window_height: i32, buffer_width: i32, buffer_height: i32, image_format: ImageFormat, screenshot_width: *mut i32, screenshot_height: *mut i32, ) -> AsyncScreenshotHandle { assert!(!screenshot_width.is_null()); assert!(!screenshot_height.is_null()); let (handle, size) = renderer.get_screenshot_async( DeviceIntRect::from_origin_and_size( DeviceIntPoint::new(window_x, window_y), DeviceIntSize::new(window_width, window_height), ), DeviceIntSize::new(buffer_width, buffer_height), image_format, ); unsafe { *screenshot_width = size.width; *screenshot_height = size.height; } handle } #[no_mangle] pub extern "C" fn wr_renderer_map_and_recycle_screenshot( renderer: &mut Renderer, handle: AsyncScreenshotHandle, dst_buffer: *mut u8, dst_buffer_len: usize, dst_stride: usize, ) -> bool { renderer.map_and_recycle_screenshot( handle, unsafe { make_slice_mut(dst_buffer, dst_buffer_len) }, dst_stride, ) } #[no_mangle] pub extern "C" fn wr_renderer_release_profiler_structures(renderer: &mut Renderer) { renderer.release_profiler_structures(); } // Call wr_renderer_render() before calling this function. #[no_mangle] pub unsafe extern "C" fn wr_renderer_readback( renderer: &mut Renderer, width: i32, height: i32, format: ImageFormat, dst_buffer: *mut u8, buffer_size: usize, ) { assert!(is_in_render_thread()); let mut slice = make_slice_mut(dst_buffer, buffer_size); renderer.read_pixels_into(FramebufferIntSize::new(width, height).into(), format, &mut slice); } #[no_mangle] pub unsafe extern "C" fn wr_renderer_set_profiler_ui(renderer: &mut Renderer, ui_str: *const u8, ui_str_len: usize) { let slice = std::slice::from_raw_parts(ui_str, ui_str_len); if let Ok(ui_str) = std::str::from_utf8(slice) { renderer.set_profiler_ui(ui_str); } } #[no_mangle] pub unsafe extern "C" fn wr_renderer_delete(renderer: *mut Renderer) { let renderer = Box::from_raw(renderer); renderer.deinit(); // let renderer go out of scope and get dropped } #[no_mangle] pub unsafe extern "C" fn wr_renderer_accumulate_memory_report( renderer: &mut Renderer, report: &mut MemoryReport, swgl: *mut c_void, ) { *report += renderer.report_memory(swgl); } // cbindgen doesn't support tuples, so we have a little struct instead, with // an Into implementation to convert from the tuple to the struct. #[repr(C)] pub struct WrPipelineEpoch { pipeline_id: WrPipelineId, document_id: WrDocumentId, epoch: WrEpoch, } impl<'a> From<(&'a (WrPipelineId, WrDocumentId), &'a WrEpoch)> for WrPipelineEpoch { fn from(tuple: (&(WrPipelineId, WrDocumentId), &WrEpoch)) -> WrPipelineEpoch { WrPipelineEpoch { pipeline_id: (tuple.0).0, document_id: (tuple.0).1, epoch: *tuple.1, } } } #[repr(C)] pub struct WrPipelineIdAndEpoch { pipeline_id: WrPipelineId, epoch: WrEpoch, } impl<'a> From<(&WrPipelineId, &WrEpoch)> for WrPipelineIdAndEpoch { fn from(tuple: (&WrPipelineId, &WrEpoch)) -> WrPipelineIdAndEpoch { WrPipelineIdAndEpoch { pipeline_id: *tuple.0, epoch: *tuple.1, } } } #[repr(C)] pub struct WrRemovedPipeline { pipeline_id: WrPipelineId, document_id: WrDocumentId, } impl<'a> From<&'a (WrPipelineId, WrDocumentId)> for WrRemovedPipeline { fn from(tuple: &(WrPipelineId, WrDocumentId)) -> WrRemovedPipeline { WrRemovedPipeline { pipeline_id: tuple.0, document_id: tuple.1, } } } #[repr(C)] pub struct WrPipelineInfo { /// This contains an entry for each pipeline that was rendered, along with /// the epoch at which it was rendered. Rendered pipelines include the root /// pipeline and any other pipelines that were reachable via IFrame display /// items from the root pipeline. epochs: ThinVec, /// This contains an entry for each pipeline that was removed during the /// last transaction. These pipelines would have been explicitly removed by /// calling remove_pipeline on the transaction object; the pipeline showing /// up in this array means that the data structures have been torn down on /// the webrender side, and so any remaining data structures on the caller /// side can now be torn down also. removed_pipelines: ThinVec, } impl WrPipelineInfo { fn new(info: &PipelineInfo) -> Self { WrPipelineInfo { epochs: info.epochs.iter().map(WrPipelineEpoch::from).collect(), removed_pipelines: info.removed_pipelines.iter().map(WrRemovedPipeline::from).collect(), } } } #[no_mangle] pub unsafe extern "C" fn wr_renderer_flush_pipeline_info(renderer: &mut Renderer, out: &mut WrPipelineInfo) { let info = renderer.flush_pipeline_info(); *out = WrPipelineInfo::new(&info); } extern "C" { pub fn gecko_profiler_thread_is_being_profiled() -> bool; } pub fn gecko_profiler_start_marker(name: &str) { use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime, Tracing}; gecko_profiler::add_marker( name, gecko_profiler_category!(Graphics), MarkerOptions { timing: MarkerTiming::interval_start(ProfilerTime::now()), ..Default::default() }, Tracing("Webrender".to_string()), ); } pub fn gecko_profiler_end_marker(name: &str) { use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime, Tracing}; gecko_profiler::add_marker( name, gecko_profiler_category!(Graphics), MarkerOptions { timing: MarkerTiming::interval_end(ProfilerTime::now()), ..Default::default() }, Tracing("Webrender".to_string()), ); } pub fn gecko_profiler_event_marker(name: &str) { use gecko_profiler::{gecko_profiler_category, Tracing}; gecko_profiler::add_marker( name, gecko_profiler_category!(Graphics), Default::default(), Tracing("Webrender".to_string()), ); } pub fn gecko_profiler_add_text_marker(name: &str, text: &str, microseconds: f64) { use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime}; if !gecko_profiler::can_accept_markers() { return; } let now = ProfilerTime::now(); let start = now.clone().subtract_microseconds(microseconds); gecko_profiler::add_text_marker( name, gecko_profiler_category!(Graphics), MarkerOptions { timing: MarkerTiming::interval(start, now), ..Default::default() }, text, ); } /// Simple implementation of the WR ProfilerHooks trait to allow profile /// markers to be seen in the Gecko profiler. struct GeckoProfilerHooks; impl ProfilerHooks for GeckoProfilerHooks { fn register_thread(&self, thread_name: &str) { gecko_profiler::register_thread(thread_name); } fn unregister_thread(&self) { gecko_profiler::unregister_thread(); } fn begin_marker(&self, label: &str) { gecko_profiler_start_marker(label); } fn end_marker(&self, label: &str) { gecko_profiler_end_marker(label); } fn event_marker(&self, label: &str) { gecko_profiler_event_marker(label); } fn add_text_marker(&self, label: &str, text: &str, duration: Duration) { let micros = duration.as_micros() as f64; gecko_profiler_add_text_marker(label, text, micros); } fn thread_is_being_profiled(&self) -> bool { unsafe { gecko_profiler_thread_is_being_profiled() } } } static PROFILER_HOOKS: GeckoProfilerHooks = GeckoProfilerHooks {}; #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &mut Transaction to an extern function extern "C" { // These callbacks are invoked from the scene builder thread (aka the APZ // updater thread) fn apz_register_updater(window_id: WrWindowId); fn apz_pre_scene_swap(window_id: WrWindowId); fn apz_post_scene_swap(window_id: WrWindowId, pipeline_info: &WrPipelineInfo); fn apz_run_updater(window_id: WrWindowId); fn apz_deregister_updater(window_id: WrWindowId); // These callbacks are invoked from the render backend thread (aka the APZ // sampler thread) fn apz_register_sampler(window_id: WrWindowId); fn apz_sample_transforms(window_id: WrWindowId, generated_frame_id: *const u64, transaction: &mut Transaction); fn apz_deregister_sampler(window_id: WrWindowId); fn omta_register_sampler(window_id: WrWindowId); fn omta_sample(window_id: WrWindowId, transaction: &mut Transaction); fn omta_deregister_sampler(window_id: WrWindowId); } struct APZCallbacks { window_id: WrWindowId, } impl APZCallbacks { pub fn new(window_id: WrWindowId) -> Self { APZCallbacks { window_id } } } impl SceneBuilderHooks for APZCallbacks { fn register(&self) { unsafe { apz_register_updater(self.window_id) } } fn pre_scene_build(&self) { gecko_profiler_start_marker("SceneBuilding"); } fn pre_scene_swap(&self) { unsafe { apz_pre_scene_swap(self.window_id); } } fn post_scene_swap(&self, _document_ids: &Vec, info: PipelineInfo) { let mut info = WrPipelineInfo::new(&info); unsafe { apz_post_scene_swap(self.window_id, &info); } // After a scene swap we should schedule a render for the next vsync, // otherwise there's no guarantee that the new scene will get rendered // anytime soon unsafe { wr_finished_scene_build(self.window_id, &mut info) } gecko_profiler_end_marker("SceneBuilding"); } fn post_resource_update(&self, _document_ids: &Vec) { unsafe { wr_schedule_render(self.window_id, RenderReasons::POST_RESOURCE_UPDATES_HOOK) } gecko_profiler_end_marker("SceneBuilding"); } fn post_empty_scene_build(&self) { gecko_profiler_end_marker("SceneBuilding"); } fn poke(&self) { unsafe { apz_run_updater(self.window_id) } } fn deregister(&self) { unsafe { apz_deregister_updater(self.window_id) } } } struct SamplerCallback { window_id: WrWindowId, } impl SamplerCallback { pub fn new(window_id: WrWindowId) -> Self { SamplerCallback { window_id } } } impl AsyncPropertySampler for SamplerCallback { fn register(&self) { unsafe { apz_register_sampler(self.window_id); omta_register_sampler(self.window_id); } } fn sample(&self, _document_id: DocumentId, generated_frame_id: Option) -> Vec { let generated_frame_id_value; let generated_frame_id: *const u64 = match generated_frame_id { Some(id) => { generated_frame_id_value = id; &generated_frame_id_value }, None => ptr::null_mut(), }; let mut transaction = Transaction::new(); // Reset the pending properties first because omta_sample and apz_sample_transforms // may be failed to reset them due to null samplers. transaction.reset_dynamic_properties(); unsafe { apz_sample_transforms(self.window_id, generated_frame_id, &mut transaction); omta_sample(self.window_id, &mut transaction); }; transaction.get_frame_ops() } fn deregister(&self) { unsafe { apz_deregister_sampler(self.window_id); omta_deregister_sampler(self.window_id); } } } extern "C" { fn wr_register_thread_local_arena(); } pub struct WrThreadPool(Arc); #[no_mangle] pub extern "C" fn wr_thread_pool_new(low_priority: bool) -> *mut WrThreadPool { // Clamp the number of workers between 1 and 4/8. We get diminishing returns // with high worker counts and extra overhead because of rayon and font // management. // We clamp to 4 high priority threads because contention and memory usage // make it not worth going higher let max = if low_priority { 8 } else { 4 }; let num_threads = num_cpus::get().min(max); let priority_tag = if low_priority { "LP" } else { "" }; let worker = rayon::ThreadPoolBuilder::new() .thread_name(move |idx| format!("WRWorker{}#{}", priority_tag, idx)) .num_threads(num_threads) .start_handler(move |idx| { unsafe { wr_register_thread_local_arena(); } let name = format!("WRWorker{}#{}", priority_tag, idx); register_thread_with_profiler(name.clone()); gecko_profiler::register_thread(&name); }) .exit_handler(|_idx| { gecko_profiler::unregister_thread(); }) .build(); let workers = Arc::new(worker.unwrap()); Box::into_raw(Box::new(WrThreadPool(workers))) } #[no_mangle] pub unsafe extern "C" fn wr_thread_pool_delete(thread_pool: *mut WrThreadPool) { mem::drop(Box::from_raw(thread_pool)); } #[no_mangle] pub unsafe extern "C" fn wr_program_cache_new( prof_path: &nsAString, thread_pool: *mut WrThreadPool, ) -> *mut WrProgramCache { let workers = &(*thread_pool).0; let program_cache = WrProgramCache::new(prof_path, workers); Box::into_raw(Box::new(program_cache)) } #[no_mangle] pub unsafe extern "C" fn wr_program_cache_delete(program_cache: *mut WrProgramCache) { mem::drop(Box::from_raw(program_cache)); } #[no_mangle] pub unsafe extern "C" fn wr_try_load_startup_shaders_from_disk(program_cache: *mut WrProgramCache) { (*program_cache).try_load_startup_shaders_from_disk(); } #[no_mangle] pub unsafe extern "C" fn remove_program_binary_disk_cache(prof_path: &nsAString) -> bool { match remove_disk_cache(prof_path) { Ok(_) => true, Err(_) => { error!("Failed to remove program binary disk cache"); false }, } } // This matches IsEnvSet in gfxEnv.h fn env_var_to_bool(key: &'static str) -> bool { env::var(key).ok().map_or(false, |v| !v.is_empty()) } // Call MakeCurrent before this. fn wr_device_new(gl_context: *mut c_void, pc: Option<&mut WrProgramCache>) -> Device { assert!(unsafe { is_in_render_thread() }); let gl; if unsafe { is_glcontext_gles(gl_context) } { gl = unsafe { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) }; } else { gl = unsafe { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) }; } let version = gl.get_string(gl::VERSION); info!("WebRender - OpenGL version new {}", version); let upload_method = if unsafe { is_glcontext_angle(gl_context) } { UploadMethod::Immediate } else { UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT) }; let resource_override_path = unsafe { let override_charptr = gfx_wr_resource_path_override(); if override_charptr.is_null() { None } else { match CStr::from_ptr(override_charptr).to_str() { Ok(override_str) => Some(PathBuf::from(override_str)), _ => None, } } }; let use_optimized_shaders = unsafe { gfx_wr_use_optimized_shaders() }; let cached_programs = pc.map(|cached_programs| Rc::clone(cached_programs.rc_get())); Device::new( gl, Some(Box::new(MozCrashAnnotator)), resource_override_path, use_optimized_shaders, upload_method, 512 * 512, cached_programs, true, true, None, false, false, ) } extern "C" { fn wr_compositor_create_surface( compositor: *mut c_void, id: NativeSurfaceId, virtual_offset: DeviceIntPoint, tile_size: DeviceIntSize, is_opaque: bool, ); fn wr_compositor_create_external_surface(compositor: *mut c_void, id: NativeSurfaceId, is_opaque: bool); fn wr_compositor_create_backdrop_surface(compositor: *mut c_void, id: NativeSurfaceId, color: ColorF); fn wr_compositor_destroy_surface(compositor: *mut c_void, id: NativeSurfaceId); fn wr_compositor_create_tile(compositor: *mut c_void, id: NativeSurfaceId, x: i32, y: i32); fn wr_compositor_destroy_tile(compositor: *mut c_void, id: NativeSurfaceId, x: i32, y: i32); fn wr_compositor_attach_external_image( compositor: *mut c_void, id: NativeSurfaceId, external_image: ExternalImageId, ); fn wr_compositor_bind( compositor: *mut c_void, id: NativeTileId, offset: &mut DeviceIntPoint, fbo_id: &mut u32, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect, ); fn wr_compositor_unbind(compositor: *mut c_void); fn wr_compositor_begin_frame(compositor: *mut c_void); fn wr_compositor_add_surface( compositor: *mut c_void, id: NativeSurfaceId, transform: &CompositorSurfaceTransform, clip_rect: DeviceIntRect, image_rendering: ImageRendering, ); fn wr_compositor_start_compositing( compositor: *mut c_void, clear_color: ColorF, dirty_rects: *const DeviceIntRect, num_dirty_rects: usize, opaque_rects: *const DeviceIntRect, num_opaque_rects: usize, ); fn wr_compositor_end_frame(compositor: *mut c_void); fn wr_compositor_enable_native_compositor(compositor: *mut c_void, enable: bool); fn wr_compositor_deinit(compositor: *mut c_void); fn wr_compositor_get_capabilities(compositor: *mut c_void, caps: *mut CompositorCapabilities); fn wr_compositor_get_window_visibility(compositor: *mut c_void, caps: *mut WindowVisibility); fn wr_compositor_map_tile( compositor: *mut c_void, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect, data: &mut *mut c_void, stride: &mut i32, ); fn wr_compositor_unmap_tile(compositor: *mut c_void); fn wr_partial_present_compositor_set_buffer_damage_region( compositor: *mut c_void, rects: *const DeviceIntRect, n_rects: usize, ); } pub struct WrCompositor(*mut c_void); impl Compositor for WrCompositor { fn create_surface( &mut self, _device: &mut Device, id: NativeSurfaceId, virtual_offset: DeviceIntPoint, tile_size: DeviceIntSize, is_opaque: bool, ) { unsafe { wr_compositor_create_surface(self.0, id, virtual_offset, tile_size, is_opaque); } } fn create_external_surface(&mut self, _device: &mut Device, id: NativeSurfaceId, is_opaque: bool) { unsafe { wr_compositor_create_external_surface(self.0, id, is_opaque); } } fn create_backdrop_surface(&mut self, _device: &mut Device, id: NativeSurfaceId, color: ColorF) { unsafe { wr_compositor_create_backdrop_surface(self.0, id, color); } } fn destroy_surface(&mut self, _device: &mut Device, id: NativeSurfaceId) { unsafe { wr_compositor_destroy_surface(self.0, id); } } fn create_tile(&mut self, _device: &mut Device, id: NativeTileId) { unsafe { wr_compositor_create_tile(self.0, id.surface_id, id.x, id.y); } } fn destroy_tile(&mut self, _device: &mut Device, id: NativeTileId) { unsafe { wr_compositor_destroy_tile(self.0, id.surface_id, id.x, id.y); } } fn attach_external_image(&mut self, _device: &mut Device, id: NativeSurfaceId, external_image: ExternalImageId) { unsafe { wr_compositor_attach_external_image(self.0, id, external_image); } } fn bind( &mut self, _device: &mut Device, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect, ) -> NativeSurfaceInfo { let mut surface_info = NativeSurfaceInfo { origin: DeviceIntPoint::zero(), fbo_id: 0, }; unsafe { wr_compositor_bind( self.0, id, &mut surface_info.origin, &mut surface_info.fbo_id, dirty_rect, valid_rect, ); } surface_info } fn unbind(&mut self, _device: &mut Device) { unsafe { wr_compositor_unbind(self.0); } } fn begin_frame(&mut self, _device: &mut Device) { unsafe { wr_compositor_begin_frame(self.0); } } fn add_surface( &mut self, _device: &mut Device, id: NativeSurfaceId, transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect, image_rendering: ImageRendering, ) { unsafe { wr_compositor_add_surface(self.0, id, &transform, clip_rect, image_rendering); } } fn start_compositing( &mut self, _device: &mut Device, clear_color: ColorF, dirty_rects: &[DeviceIntRect], opaque_rects: &[DeviceIntRect], ) { unsafe { wr_compositor_start_compositing( self.0, clear_color, dirty_rects.as_ptr(), dirty_rects.len(), opaque_rects.as_ptr(), opaque_rects.len(), ); } } fn end_frame(&mut self, _device: &mut Device) { unsafe { wr_compositor_end_frame(self.0); } } fn enable_native_compositor(&mut self, _device: &mut Device, enable: bool) { unsafe { wr_compositor_enable_native_compositor(self.0, enable); } } fn deinit(&mut self, _device: &mut Device) { unsafe { wr_compositor_deinit(self.0); } } fn get_capabilities(&self, _device: &mut Device) -> CompositorCapabilities { unsafe { let mut caps: CompositorCapabilities = Default::default(); wr_compositor_get_capabilities(self.0, &mut caps); caps } } fn get_window_visibility(&self, _device: &mut Device) -> WindowVisibility { unsafe { let mut visibility: WindowVisibility = Default::default(); wr_compositor_get_window_visibility(self.0, &mut visibility); visibility } } } extern "C" { fn wr_swgl_lock_composite_surface( ctx: *mut c_void, external_image_id: ExternalImageId, composite_info: *mut SWGLCompositeSurfaceInfo, ) -> bool; fn wr_swgl_unlock_composite_surface(ctx: *mut c_void, external_image_id: ExternalImageId); } impl MappableCompositor for WrCompositor { /// Map a tile's underlying buffer so it can be used as the backing for /// a SWGL framebuffer. This is intended to be a replacement for 'bind' /// in any compositors that intend to directly interoperate with SWGL /// while supporting some form of native layers. fn map_tile( &mut self, _device: &mut Device, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect, ) -> Option { let mut tile_info = MappedTileInfo { data: ptr::null_mut(), stride: 0, }; unsafe { wr_compositor_map_tile( self.0, id, dirty_rect, valid_rect, &mut tile_info.data, &mut tile_info.stride, ); } if !tile_info.data.is_null() && tile_info.stride != 0 { Some(tile_info) } else { None } } /// Unmap a tile that was was previously mapped via map_tile to signal /// that SWGL is done rendering to the buffer. fn unmap_tile(&mut self, _device: &mut Device) { unsafe { wr_compositor_unmap_tile(self.0); } } fn lock_composite_surface( &mut self, _device: &mut Device, ctx: *mut c_void, external_image_id: ExternalImageId, composite_info: *mut SWGLCompositeSurfaceInfo, ) -> bool { unsafe { wr_swgl_lock_composite_surface(ctx, external_image_id, composite_info) } } fn unlock_composite_surface(&mut self, _device: &mut Device, ctx: *mut c_void, external_image_id: ExternalImageId) { unsafe { wr_swgl_unlock_composite_surface(ctx, external_image_id) } } } pub struct WrPartialPresentCompositor(*mut c_void); impl PartialPresentCompositor for WrPartialPresentCompositor { fn set_buffer_damage_region(&mut self, rects: &[DeviceIntRect]) { unsafe { wr_partial_present_compositor_set_buffer_damage_region(self.0, rects.as_ptr(), rects.len()); } } } /// A wrapper around a strong reference to a Shaders object. pub struct WrShaders(SharedShaders); // Call MakeCurrent before this. #[no_mangle] pub extern "C" fn wr_window_new( window_id: WrWindowId, window_width: i32, window_height: i32, is_main_window: bool, support_low_priority_transactions: bool, support_low_priority_threadpool: bool, allow_texture_swizzling: bool, allow_scissored_cache_clears: bool, swgl_context: *mut c_void, gl_context: *mut c_void, surface_origin_is_top_left: bool, program_cache: Option<&mut WrProgramCache>, shaders: Option<&mut WrShaders>, thread_pool: *mut WrThreadPool, thread_pool_low_priority: *mut WrThreadPool, size_of_op: VoidPtrToSizeFn, enclosing_size_of_op: VoidPtrToSizeFn, document_id: u32, compositor: *mut c_void, use_native_compositor: bool, use_partial_present: bool, max_partial_present_rects: usize, draw_previous_partial_present_regions: bool, out_handle: &mut *mut DocumentHandle, out_renderer: &mut *mut Renderer, out_max_texture_size: *mut i32, out_err: &mut *mut c_char, enable_gpu_markers: bool, panic_on_gl_error: bool, picture_tile_width: i32, picture_tile_height: i32, reject_software_rasterizer: bool, low_quality_pinch_zoom: bool, max_shared_surface_size: i32, enable_subpixel_aa: bool, ) -> bool { assert!(unsafe { is_in_render_thread() }); // Ensure the WR profiler callbacks are hooked up to the Gecko profiler. set_profiler_hooks(Some(&PROFILER_HOOKS)); let software = !swgl_context.is_null(); let (gl, sw_gl) = if software { let ctx = swgl::Context::from(swgl_context); ctx.make_current(); (Rc::new(ctx) as Rc, Some(ctx)) } else { let gl = unsafe { if gl_context.is_null() { panic!("Native GL context required when not using SWGL!"); } else if is_glcontext_gles(gl_context) { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) } else { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) } }; (gl, None) }; let version = gl.get_string(gl::VERSION); info!("WebRender - OpenGL version new {}", version); let workers = unsafe { Arc::clone(&(*thread_pool).0) }; let workers_low_priority = unsafe { if support_low_priority_threadpool { Arc::clone(&(*thread_pool_low_priority).0) } else { Arc::clone(&(*thread_pool).0) } }; let upload_method = if !gl_context.is_null() && unsafe { is_glcontext_angle(gl_context) } { UploadMethod::Immediate } else { UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT) }; let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") { ShaderPrecacheFlags::FULL_COMPILE } else { ShaderPrecacheFlags::empty() }; let cached_programs = program_cache.map(|program_cache| Rc::clone(&program_cache.rc_get())); let color = if cfg!(target_os = "android") { // The color is for avoiding black flash before receiving display list. ColorF::new(1.0, 1.0, 1.0, 1.0) } else { ColorF::new(0.0, 0.0, 0.0, 0.0) }; let compositor_config = if software { CompositorConfig::Native { compositor: Box::new(SwCompositor::new( sw_gl.unwrap(), Box::new(WrCompositor(compositor)), use_native_compositor, )), } } else if use_native_compositor { CompositorConfig::Native { compositor: Box::new(WrCompositor(compositor)), } } else { CompositorConfig::Draw { max_partial_present_rects, draw_previous_partial_present_regions, partial_present: if use_partial_present { Some(Box::new(WrPartialPresentCompositor(compositor))) } else { None }, } }; let picture_tile_size = if picture_tile_width > 0 && picture_tile_height > 0 { Some(DeviceIntSize::new(picture_tile_width, picture_tile_height)) } else { None }; let texture_cache_config = if is_main_window { TextureCacheConfig::DEFAULT } else { TextureCacheConfig { color8_linear_texture_size: 512, color8_nearest_texture_size: 512, color8_glyph_texture_size: 512, alpha8_texture_size: 512, alpha8_glyph_texture_size: 512, alpha16_texture_size: 512, } }; let opts = WebRenderOptions { enable_aa: true, enable_subpixel_aa, support_low_priority_transactions, allow_texture_swizzling, blob_image_handler: Some(Box::new(Moz2dBlobImageHandler::new( workers.clone(), workers_low_priority, ))), crash_annotator: Some(Box::new(MozCrashAnnotator)), workers: Some(workers), size_of_op: Some(size_of_op), enclosing_size_of_op: Some(enclosing_size_of_op), cached_programs, resource_override_path: unsafe { let override_charptr = gfx_wr_resource_path_override(); if override_charptr.is_null() { None } else { match CStr::from_ptr(override_charptr).to_str() { Ok(override_str) => Some(PathBuf::from(override_str)), _ => None, } } }, use_optimized_shaders: unsafe { gfx_wr_use_optimized_shaders() }, renderer_id: Some(window_id.0), upload_method, scene_builder_hooks: Some(Box::new(APZCallbacks::new(window_id))), sampler: Some(Box::new(SamplerCallback::new(window_id))), max_internal_texture_size: Some(8192), // We want to tile if larger than this clear_color: color, precache_flags, namespace_alloc_by_client: true, // Font namespace must be allocated by the client shared_font_namespace: Some(next_namespace_id()), // SWGL doesn't support the GL_ALWAYS depth comparison function used by // `clear_caches_with_quads`, but scissored clears work well. clear_caches_with_quads: !software && !allow_scissored_cache_clears, // SWGL supports KHR_blend_equation_advanced safely, but we haven't yet // tested other HW platforms determine if it is safe to allow them. allow_advanced_blend_equation: software, surface_origin_is_top_left, compositor_config, enable_gpu_markers, panic_on_gl_error, picture_tile_size, texture_cache_config, reject_software_rasterizer, low_quality_pinch_zoom, max_shared_surface_size, ..Default::default() }; let window_size = DeviceIntSize::new(window_width, window_height); let notifier = Box::new(CppNotifier { window_id }); let (renderer, sender) = match create_webrender_instance(gl, notifier, opts, shaders.map(|sh| &sh.0)) { Ok((renderer, sender)) => (renderer, sender), Err(e) => { warn!(" Failed to create a Renderer: {:?}", e); let msg = CString::new(format!("wr_window_new: {:?}", e)).unwrap(); unsafe { gfx_critical_note(msg.as_ptr()); } *out_err = msg.into_raw(); return false; }, }; unsafe { *out_max_texture_size = renderer.get_max_texture_size(); } *out_handle = Box::into_raw(Box::new(DocumentHandle::new( sender.create_api_by_client(next_namespace_id()), None, window_size, document_id, ))); *out_renderer = Box::into_raw(Box::new(renderer)); true } #[no_mangle] pub unsafe extern "C" fn wr_api_free_error_msg(msg: *mut c_char) { if !msg.is_null() { drop(CString::from_raw(msg)); } } #[no_mangle] pub unsafe extern "C" fn wr_api_delete_document(dh: &mut DocumentHandle) { dh.api.delete_document(dh.document_id); } #[no_mangle] pub extern "C" fn wr_api_clone(dh: &mut DocumentHandle, out_handle: &mut *mut DocumentHandle) { assert!(unsafe { is_in_compositor_thread() }); let hit_tester = dh.ensure_hit_tester().clone(); let handle = DocumentHandle { api: dh.api.create_sender().create_api_by_client(next_namespace_id()), document_id: dh.document_id, hit_tester: Some(hit_tester), hit_tester_request: None, }; *out_handle = Box::into_raw(Box::new(handle)); } #[no_mangle] pub unsafe extern "C" fn wr_api_delete(dh: *mut DocumentHandle) { let _ = Box::from_raw(dh); } #[no_mangle] pub unsafe extern "C" fn wr_api_stop_render_backend(dh: &mut DocumentHandle) { dh.api.stop_render_backend(); } #[no_mangle] pub unsafe extern "C" fn wr_api_shut_down(dh: &mut DocumentHandle) { dh.api.shut_down(true); } #[no_mangle] pub unsafe extern "C" fn wr_api_notify_memory_pressure(dh: &mut DocumentHandle) { dh.api.notify_memory_pressure(); } #[no_mangle] pub extern "C" fn wr_api_set_debug_flags(dh: &mut DocumentHandle, flags: DebugFlags) { dh.api.set_debug_flags(flags); } #[no_mangle] pub extern "C" fn wr_api_set_bool(dh: &mut DocumentHandle, param_name: BoolParameter, val: bool) { dh.api.set_parameter(Parameter::Bool(param_name, val)); } #[no_mangle] pub extern "C" fn wr_api_set_int(dh: &mut DocumentHandle, param_name: IntParameter, val: i32) { dh.api.set_parameter(Parameter::Int(param_name, val)); } #[no_mangle] pub unsafe extern "C" fn wr_api_accumulate_memory_report( dh: &mut DocumentHandle, report: &mut MemoryReport, // we manually expand VoidPtrToSizeFn here because cbindgen otherwise fails to fold the Option // https://github.com/eqrion/cbindgen/issues/552 size_of_op: unsafe extern "C" fn(ptr: *const c_void) -> usize, enclosing_size_of_op: Option usize>, ) { let ops = MallocSizeOfOps::new(size_of_op, enclosing_size_of_op); *report += dh.api.report_memory(ops); } #[no_mangle] pub unsafe extern "C" fn wr_api_clear_all_caches(dh: &mut DocumentHandle) { dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all())); } #[no_mangle] pub unsafe extern "C" fn wr_api_enable_native_compositor(dh: &mut DocumentHandle, enable: bool) { dh.api.send_debug_cmd(DebugCommand::EnableNativeCompositor(enable)); } #[no_mangle] pub unsafe extern "C" fn wr_api_set_batching_lookback(dh: &mut DocumentHandle, count: u32) { dh.api.send_debug_cmd(DebugCommand::SetBatchingLookback(count)); } fn make_transaction(do_async: bool) -> Transaction { let mut transaction = Transaction::new(); // Ensure that we either use async scene building or not based on the // gecko pref, regardless of what the default is. We can remove this once // the scene builder thread is enabled everywhere and working well. if do_async { transaction.use_scene_builder_thread(); } else { transaction.skip_scene_builder(); } transaction } #[no_mangle] pub extern "C" fn wr_transaction_new(do_async: bool) -> *mut Transaction { Box::into_raw(Box::new(make_transaction(do_async))) } #[no_mangle] pub extern "C" fn wr_transaction_delete(txn: *mut Transaction) { unsafe { let _ = Box::from_raw(txn); } } #[no_mangle] pub extern "C" fn wr_transaction_set_low_priority(txn: &mut Transaction, low_priority: bool) { txn.set_low_priority(low_priority); } #[no_mangle] pub extern "C" fn wr_transaction_is_empty(txn: &Transaction) -> bool { txn.is_empty() } #[no_mangle] pub extern "C" fn wr_transaction_resource_updates_is_empty(txn: &Transaction) -> bool { txn.resource_updates.is_empty() } #[no_mangle] pub extern "C" fn wr_transaction_is_rendered_frame_invalidated(txn: &Transaction) -> bool { txn.invalidate_rendered_frame } #[no_mangle] pub extern "C" fn wr_transaction_notify(txn: &mut Transaction, when: Checkpoint, event: usize) { struct GeckoNotification(usize); impl NotificationHandler for GeckoNotification { fn notify(&self, when: Checkpoint) { unsafe { wr_transaction_notification_notified(self.0, when); } } } let handler = Box::new(GeckoNotification(event)); txn.notify(NotificationRequest::new(when, handler)); } #[no_mangle] pub extern "C" fn wr_transaction_update_epoch(txn: &mut Transaction, pipeline_id: WrPipelineId, epoch: WrEpoch) { txn.update_epoch(pipeline_id, epoch); } #[no_mangle] pub extern "C" fn wr_transaction_set_root_pipeline(txn: &mut Transaction, pipeline_id: WrPipelineId) { txn.set_root_pipeline(pipeline_id); } #[no_mangle] pub extern "C" fn wr_transaction_remove_pipeline(txn: &mut Transaction, pipeline_id: WrPipelineId) { txn.remove_pipeline(pipeline_id); } #[no_mangle] pub extern "C" fn wr_transaction_set_display_list( txn: &mut Transaction, epoch: WrEpoch, pipeline_id: WrPipelineId, dl_descriptor: BuiltDisplayListDescriptor, dl_items_data: &mut WrVecU8, dl_cache_data: &mut WrVecU8, dl_spatial_tree_data: &mut WrVecU8, ) { let payload = DisplayListPayload { items_data: dl_items_data.flush_into_vec(), cache_data: dl_cache_data.flush_into_vec(), spatial_tree: dl_spatial_tree_data.flush_into_vec(), }; let dl = BuiltDisplayList::from_data(payload, dl_descriptor); txn.set_display_list(epoch, (pipeline_id, dl)); } #[no_mangle] pub extern "C" fn wr_transaction_set_document_view(txn: &mut Transaction, doc_rect: &DeviceIntRect) { txn.set_document_view(*doc_rect); } #[no_mangle] pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction, id: u64, reasons: RenderReasons) { txn.generate_frame(id, reasons); } #[no_mangle] pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction, reasons: RenderReasons) { txn.invalidate_rendered_frame(reasons); } fn wr_animation_properties_into_vec( animation_array: *const WrAnimationPropertyValue, array_count: usize, vec: &mut Vec>, ) where T: Copy, { if array_count > 0 { debug_assert!( vec.capacity() - vec.len() >= array_count, "The Vec should have fufficient free capacity" ); let slice = unsafe { make_slice(animation_array, array_count) }; for element in slice.iter() { let prop = PropertyValue { key: PropertyBindingKey::new(element.id), value: element.value, }; vec.push(prop); } } } #[no_mangle] pub extern "C" fn wr_transaction_append_dynamic_properties( txn: &mut Transaction, opacity_array: *const WrOpacityProperty, opacity_count: usize, transform_array: *const WrTransformProperty, transform_count: usize, color_array: *const WrColorProperty, color_count: usize, ) { if opacity_count == 0 && transform_count == 0 && color_count == 0 { return; } let mut properties = DynamicProperties { transforms: Vec::with_capacity(transform_count), floats: Vec::with_capacity(opacity_count), colors: Vec::with_capacity(color_count), }; wr_animation_properties_into_vec(transform_array, transform_count, &mut properties.transforms); wr_animation_properties_into_vec(opacity_array, opacity_count, &mut properties.floats); wr_animation_properties_into_vec(color_array, color_count, &mut properties.colors); txn.append_dynamic_properties(properties); } #[no_mangle] pub extern "C" fn wr_transaction_append_transform_properties( txn: &mut Transaction, transform_array: *const WrTransformProperty, transform_count: usize, ) { if transform_count == 0 { return; } let mut transforms = Vec::with_capacity(transform_count); wr_animation_properties_into_vec(transform_array, transform_count, &mut transforms); txn.append_dynamic_transform_properties(transforms); } #[no_mangle] pub extern "C" fn wr_transaction_scroll_layer( txn: &mut Transaction, scroll_id: ExternalScrollId, sampled_scroll_offsets: &ThinVec, ) { txn.set_scroll_offsets(scroll_id, sampled_scroll_offsets.to_vec()); } #[no_mangle] pub extern "C" fn wr_transaction_set_is_transform_async_zooming( txn: &mut Transaction, animation_id: u64, is_zooming: bool, ) { txn.set_is_transform_async_zooming(is_zooming, PropertyBindingId::new(animation_id)); } #[no_mangle] pub extern "C" fn wr_transaction_add_minimap_data( txn: &mut Transaction, scroll_id: ExternalScrollId, minimap_data: MinimapData, ) { txn.set_minimap_data(scroll_id, minimap_data); } #[no_mangle] pub extern "C" fn wr_transaction_set_quality_settings(txn: &mut Transaction, force_subpixel_aa_where_possible: bool) { txn.set_quality_settings(QualitySettings { force_subpixel_aa_where_possible, }); } #[no_mangle] pub extern "C" fn wr_resource_updates_add_image( txn: &mut Transaction, image_key: WrImageKey, descriptor: &WrImageDescriptor, bytes: &mut WrVecU8, ) { txn.add_image( image_key, descriptor.into(), ImageData::new(bytes.flush_into_vec()), None, ); } #[no_mangle] pub extern "C" fn wr_resource_updates_add_blob_image( txn: &mut Transaction, image_key: BlobImageKey, descriptor: &WrImageDescriptor, tile_size: u16, bytes: &mut WrVecU8, visible_rect: DeviceIntRect, ) { // If we're at risk of generating an excessive number of tiles, try making // them larger so as to reduce the total number. This helps avoid swamping // the Moz2dBlobRasterizer with too many parallel requests. const TILE_COUNT_LIMIT: i64 = 8192; const TILE_SIZE_LIMIT: u16 = 2048; let mut adjusted = tile_size; // Rather than some tricky computation involving the image dimensions, just // keep doubling tile_size until the estimated count is reasonable, or size // gets too big. The size limit means this loop won't execute more than a // handful of times even in extreme cases. while adjusted < TILE_SIZE_LIMIT && ((descriptor.height / adjusted as i32 + 1) as i64 * (descriptor.width / adjusted as i32 + 1) as i64) > TILE_COUNT_LIMIT { adjusted = adjusted * 2; } txn.add_blob_image( image_key, descriptor.into(), Arc::new(bytes.flush_into_vec()), visible_rect, if descriptor.format == ImageFormat::BGRA8 || adjusted > tile_size { Some(adjusted) } else { None }, ); } #[no_mangle] pub extern "C" fn wr_resource_updates_add_external_image( txn: &mut Transaction, image_key: WrImageKey, descriptor: &WrImageDescriptor, external_image_id: ExternalImageId, image_type: &ExternalImageType, channel_index: u8, ) { txn.add_image( image_key, descriptor.into(), ImageData::External(ExternalImageData { id: external_image_id, channel_index, image_type: *image_type, }), None, ); } #[no_mangle] pub extern "C" fn wr_resource_updates_update_image( txn: &mut Transaction, key: WrImageKey, descriptor: &WrImageDescriptor, bytes: &mut WrVecU8, ) { txn.update_image( key, descriptor.into(), ImageData::new(bytes.flush_into_vec()), &DirtyRect::All, ); } #[no_mangle] pub extern "C" fn wr_resource_updates_set_blob_image_visible_area( txn: &mut Transaction, key: BlobImageKey, area: &DeviceIntRect, ) { txn.set_blob_image_visible_area(key, *area); } #[no_mangle] pub extern "C" fn wr_resource_updates_update_external_image( txn: &mut Transaction, key: WrImageKey, descriptor: &WrImageDescriptor, external_image_id: ExternalImageId, image_type: &ExternalImageType, channel_index: u8, ) { txn.update_image( key, descriptor.into(), ImageData::External(ExternalImageData { id: external_image_id, channel_index, image_type: *image_type, }), &DirtyRect::All, ); } #[no_mangle] pub extern "C" fn wr_resource_updates_update_external_image_with_dirty_rect( txn: &mut Transaction, key: WrImageKey, descriptor: &WrImageDescriptor, external_image_id: ExternalImageId, image_type: &ExternalImageType, channel_index: u8, dirty_rect: DeviceIntRect, ) { txn.update_image( key, descriptor.into(), ImageData::External(ExternalImageData { id: external_image_id, channel_index, image_type: *image_type, }), &DirtyRect::Partial(dirty_rect), ); } #[no_mangle] pub extern "C" fn wr_resource_updates_update_blob_image( txn: &mut Transaction, image_key: BlobImageKey, descriptor: &WrImageDescriptor, bytes: &mut WrVecU8, visible_rect: DeviceIntRect, dirty_rect: LayoutIntRect, ) { txn.update_blob_image( image_key, descriptor.into(), Arc::new(bytes.flush_into_vec()), visible_rect, &DirtyRect::Partial(dirty_rect), ); } #[no_mangle] pub extern "C" fn wr_resource_updates_delete_image(txn: &mut Transaction, key: WrImageKey) { txn.delete_image(key); } #[no_mangle] pub extern "C" fn wr_resource_updates_delete_blob_image(txn: &mut Transaction, key: BlobImageKey) { txn.delete_blob_image(key); } #[no_mangle] pub extern "C" fn wr_api_send_transaction(dh: &mut DocumentHandle, transaction: &mut Transaction, is_async: bool) { if transaction.is_empty() { return; } let new_txn = make_transaction(is_async); let txn = mem::replace(transaction, new_txn); dh.api.send_transaction(dh.document_id, txn); } #[no_mangle] pub unsafe extern "C" fn wr_transaction_clear_display_list( txn: &mut Transaction, epoch: WrEpoch, pipeline_id: WrPipelineId, ) { let mut frame_builder = WebRenderFrameBuilder::new(pipeline_id); frame_builder.dl_builder.begin(); txn.set_display_list(epoch, frame_builder.dl_builder.end()); } #[no_mangle] pub extern "C" fn wr_api_send_external_event(dh: &mut DocumentHandle, evt: usize) { assert!(unsafe { !is_in_render_thread() }); dh.api.send_external_event(ExternalEvent::from_raw(evt)); } #[no_mangle] pub extern "C" fn wr_resource_updates_add_raw_font( txn: &mut Transaction, key: WrFontKey, bytes: &mut WrVecU8, index: u32, ) { txn.add_raw_font(key, bytes.flush_into_vec(), index); } fn generate_capture_path(path: *const c_char) -> Option { use std::fs::{create_dir_all, File}; use std::io::Write; let cstr = unsafe { CStr::from_ptr(path) }; let local_dir = PathBuf::from(&*cstr.to_string_lossy()); // On Android we need to write into a particular folder on external // storage so that (a) it can be written without requiring permissions // and (b) it can be pulled off via `adb pull`. This env var is set // in GeckoLoader.java. // When running in Firefox CI, the MOZ_UPLOAD_DIR variable is set to a path // that taskcluster will export artifacts from, so let's put it there. let mut path = if let Ok(storage_path) = env::var("PUBLIC_STORAGE") { PathBuf::from(storage_path).join(local_dir) } else if let Ok(storage_path) = env::var("MOZ_UPLOAD_DIR") { PathBuf::from(storage_path).join(local_dir) } else if let Some(storage_path) = dirs::home_dir() { storage_path.join(local_dir) } else { local_dir }; // Increment the extension until we find a fresh path while path.is_dir() { let count: u32 = path .extension() .and_then(|x| x.to_str()) .and_then(|x| x.parse().ok()) .unwrap_or(0); path.set_extension((count + 1).to_string()); } // Use warn! so that it gets emitted to logcat on android as well let border = "--------------------------\n"; warn!("{} Capturing WR state to: {:?}\n{}", &border, &path, &border); let _ = create_dir_all(&path); match File::create(path.join("wr.txt")) { Ok(mut file) => { // The Gecko HG revision is available at compile time if let Some(moz_revision) = option_env!("GECKO_HEAD_REV") { writeln!(file, "mozilla-central {}", moz_revision).unwrap(); } Some(path) }, Err(e) => { warn!("Unable to create path '{:?}' for capture: {:?}", path, e); None }, } } #[no_mangle] pub extern "C" fn wr_api_capture(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) { if let Some(path) = generate_capture_path(path) { let bits = CaptureBits::from_bits(bits_raw as _).unwrap(); dh.api.save_capture(path, bits); } } #[no_mangle] pub extern "C" fn wr_api_start_capture_sequence(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) { if let Some(path) = generate_capture_path(path) { let bits = CaptureBits::from_bits(bits_raw as _).unwrap(); dh.api.start_capture_sequence(path, bits); } } #[no_mangle] pub extern "C" fn wr_api_stop_capture_sequence(dh: &mut DocumentHandle) { let border = "--------------------------\n"; warn!("{} Stop capturing WR state\n{}", &border, &border); dh.api.stop_capture_sequence(); } #[cfg(target_os = "windows")] fn read_font_descriptor(bytes: &mut WrVecU8, index: u32) -> NativeFontHandle { let wchars = bytes.convert_into_vec::(); NativeFontHandle { path: PathBuf::from(OsString::from_wide(&wchars)), index, } } #[cfg(target_os = "macos")] fn read_font_descriptor(bytes: &mut WrVecU8, index: u32) -> NativeFontHandle { // On macOS, the descriptor string is a concatenation of the PostScript name // and the font file path (to disambiguate cases where there are multiple // faces with the same psname present). The index is the length of the psname // portion of the descriptor (= starting offset of the path). // Here, we split the descriptor into its two components for further use. let chars = bytes.flush_into_vec(); NativeFontHandle { name: String::from_utf8(chars[..index as usize].to_vec()).unwrap_or("".to_string()), path: String::from_utf8(chars[index as usize..].to_vec()).unwrap_or("".to_string()), } } #[cfg(not(any(target_os = "macos", target_os = "windows")))] fn read_font_descriptor(bytes: &mut WrVecU8, index: u32) -> NativeFontHandle { let chars = bytes.flush_into_vec(); NativeFontHandle { path: PathBuf::from(OsString::from_vec(chars)), index, } } #[no_mangle] pub extern "C" fn wr_resource_updates_add_font_descriptor( txn: &mut Transaction, key: WrFontKey, bytes: &mut WrVecU8, index: u32, ) { let native_font_handle = read_font_descriptor(bytes, index); txn.add_native_font(key, native_font_handle); } #[no_mangle] pub extern "C" fn wr_resource_updates_delete_font(txn: &mut Transaction, key: WrFontKey) { txn.delete_font(key); } #[no_mangle] pub extern "C" fn wr_resource_updates_add_font_instance( txn: &mut Transaction, key: WrFontInstanceKey, font_key: WrFontKey, glyph_size: f32, options: *const FontInstanceOptions, platform_options: *const FontInstancePlatformOptions, variations: &mut WrVecU8, ) { txn.add_font_instance( key, font_key, glyph_size, unsafe { options.as_ref().cloned() }, unsafe { platform_options.as_ref().cloned() }, variations.convert_into_vec::(), ); } #[no_mangle] pub extern "C" fn wr_resource_updates_delete_font_instance(txn: &mut Transaction, key: WrFontInstanceKey) { txn.delete_font_instance(key); } #[no_mangle] pub extern "C" fn wr_resource_updates_clear(txn: &mut Transaction) { txn.resource_updates.clear(); } #[no_mangle] pub unsafe extern "C" fn wr_api_get_namespace(dh: &mut DocumentHandle) -> WrIdNamespace { dh.api.get_namespace_id() } #[no_mangle] pub unsafe extern "C" fn wr_api_wake_scene_builder(dh: &mut DocumentHandle) { dh.api.wake_scene_builder(); } #[no_mangle] pub unsafe extern "C" fn wr_api_flush_scene_builder(dh: &mut DocumentHandle) { dh.api.flush_scene_builder(); } // RenderThread WIP notes: // In order to separate the compositor thread (or ipc receiver) and the render // thread, some of the logic below needs to be rewritten. In particular // the WrWindowState and Notifier implementations aren't designed to work with // a separate render thread. // As part of that I am moving the bindings closer to WebRender's API boundary, // and moving more of the logic in C++ land. // This work is tracked by bug 1328602. // // See RenderThread.h for some notes about how the pieces fit together. pub struct WebRenderFrameBuilder { pub root_pipeline_id: WrPipelineId, pub dl_builder: DisplayListBuilder, } impl WebRenderFrameBuilder { pub fn new(root_pipeline_id: WrPipelineId) -> WebRenderFrameBuilder { WebRenderFrameBuilder { root_pipeline_id, dl_builder: DisplayListBuilder::new(root_pipeline_id), } } } pub struct WrState { pipeline_id: WrPipelineId, frame_builder: WebRenderFrameBuilder, } #[no_mangle] pub extern "C" fn wr_state_new(pipeline_id: WrPipelineId) -> *mut WrState { assert!(unsafe { !is_in_render_thread() }); let state = Box::new(WrState { pipeline_id, frame_builder: WebRenderFrameBuilder::new(pipeline_id), }); Box::into_raw(state) } #[no_mangle] pub extern "C" fn wr_state_delete(state: *mut WrState) { assert!(unsafe { !is_in_render_thread() }); unsafe { mem::drop(Box::from_raw(state)); } } #[no_mangle] pub extern "C" fn wr_dp_save(state: &mut WrState) { state.frame_builder.dl_builder.save(); } #[no_mangle] pub extern "C" fn wr_dp_restore(state: &mut WrState) { state.frame_builder.dl_builder.restore(); } #[no_mangle] pub extern "C" fn wr_dp_clear_save(state: &mut WrState) { state.frame_builder.dl_builder.clear_save(); } #[repr(u8)] #[derive(PartialEq, Eq, Debug)] pub enum WrReferenceFrameKind { Transform, Perspective, } #[repr(u8)] #[derive(PartialEq, Eq, Debug)] pub enum WrRotation { Degree0, Degree90, Degree180, Degree270, } /// IMPORTANT: If you add fields to this struct, you need to also add initializers /// for those fields in WebRenderAPI.h. #[repr(C)] pub struct WrStackingContextParams { pub clip: WrStackingContextClip, pub animation: *const WrAnimationProperty, pub opacity: *const f32, pub computed_transform: *const WrComputedTransformData, pub transform_style: TransformStyle, pub reference_frame_kind: WrReferenceFrameKind, pub is_2d_scale_translation: bool, pub should_snap: bool, pub paired_with_perspective: bool, pub scrolling_relative_to: *const u64, pub prim_flags: PrimitiveFlags, pub mix_blend_mode: MixBlendMode, pub flags: StackingContextFlags, } #[no_mangle] pub extern "C" fn wr_dp_push_stacking_context( state: &mut WrState, bounds: LayoutRect, spatial_id: WrSpatialId, params: &WrStackingContextParams, transform: *const WrTransformInfo, filters: *const FilterOp, filter_count: usize, filter_datas: *const WrFilterData, filter_datas_count: usize, glyph_raster_space: RasterSpace, ) -> WrSpatialId { debug_assert!(unsafe { !is_in_render_thread() }); let c_filters = unsafe { make_slice(filters, filter_count) }; let mut filters: Vec = c_filters.iter().copied().collect(); let c_filter_datas = unsafe { make_slice(filter_datas, filter_datas_count) }; let r_filter_datas: Vec = c_filter_datas .iter() .map(|c_filter_data| FilterData { func_r_type: c_filter_data.funcR_type, r_values: unsafe { make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec() }, func_g_type: c_filter_data.funcG_type, g_values: unsafe { make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec() }, func_b_type: c_filter_data.funcB_type, b_values: unsafe { make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec() }, func_a_type: c_filter_data.funcA_type, a_values: unsafe { make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec() }, }) .collect(); let transform_ref = unsafe { transform.as_ref() }; let mut transform_binding = transform_ref.map(|info| (PropertyBinding::Value(info.transform), info.key)); let computed_ref = unsafe { params.computed_transform.as_ref() }; let opacity_ref = unsafe { params.opacity.as_ref() }; let mut has_opacity_animation = false; let anim = unsafe { params.animation.as_ref() }; if let Some(anim) = anim { debug_assert!(anim.id > 0); match anim.effect_type { WrAnimationType::Opacity => { filters.push(FilterOp::Opacity( PropertyBinding::Binding( PropertyBindingKey::new(anim.id), // We have to set the static opacity value as // the value for the case where the animation is // in not in-effect (e.g. in the delay phase // with no corresponding fill mode). opacity_ref.cloned().unwrap_or(1.0), ), 1.0, )); has_opacity_animation = true; }, WrAnimationType::Transform => { transform_binding = Some(( PropertyBinding::Binding( PropertyBindingKey::new(anim.id), // Same as above opacity case. transform_ref .map(|info| info.transform) .unwrap_or_else(LayoutTransform::identity), ), anim.key, )); }, _ => unreachable!("{:?} should not create a stacking context", anim.effect_type), } } if let Some(opacity) = opacity_ref { if !has_opacity_animation && *opacity < 1.0 { filters.push(FilterOp::Opacity(PropertyBinding::Value(*opacity), *opacity)); } } let mut wr_spatial_id = spatial_id.to_webrender(state.pipeline_id); let wr_clip_id = params.clip.to_webrender(state.pipeline_id); let mut origin = bounds.min; // Note: 0 has special meaning in WR land, standing for ROOT_REFERENCE_FRAME. // However, it is never returned by `push_reference_frame`, and we need to return // an option here across FFI, so we take that 0 value for the None semantics. // This is resolved into proper `Maybe` inside `WebRenderAPI::PushStackingContext`. let mut result = WrSpatialId { id: 0 }; if let Some(transform_binding) = transform_binding { let scrolling_relative_to = match unsafe { params.scrolling_relative_to.as_ref() } { Some(scroll_id) => { debug_assert_eq!(params.reference_frame_kind, WrReferenceFrameKind::Perspective); Some(ExternalScrollId(*scroll_id, state.pipeline_id)) }, None => None, }; let reference_frame_kind = match params.reference_frame_kind { WrReferenceFrameKind::Transform => ReferenceFrameKind::Transform { is_2d_scale_translation: params.is_2d_scale_translation, should_snap: params.should_snap, paired_with_perspective: params.paired_with_perspective, }, WrReferenceFrameKind::Perspective => ReferenceFrameKind::Perspective { scrolling_relative_to }, }; wr_spatial_id = state.frame_builder.dl_builder.push_reference_frame( origin, wr_spatial_id, params.transform_style, transform_binding.0, reference_frame_kind, transform_binding.1, ); origin = LayoutPoint::zero(); result.id = wr_spatial_id.0; assert_ne!(wr_spatial_id.0, 0); } else if let Some(data) = computed_ref { let rotation = match data.rotation { WrRotation::Degree0 => Rotation::Degree0, WrRotation::Degree90 => Rotation::Degree90, WrRotation::Degree180 => Rotation::Degree180, WrRotation::Degree270 => Rotation::Degree270, }; wr_spatial_id = state.frame_builder.dl_builder.push_computed_frame( origin, wr_spatial_id, Some(data.scale_from), data.vertical_flip, rotation, data.key, ); origin = LayoutPoint::zero(); result.id = wr_spatial_id.0; assert_ne!(wr_spatial_id.0, 0); } state.frame_builder.dl_builder.push_stacking_context( origin, wr_spatial_id, params.prim_flags, wr_clip_id, params.transform_style, params.mix_blend_mode, &filters, &r_filter_datas, &[], glyph_raster_space, params.flags, ); result } #[no_mangle] pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState, is_reference_frame: bool) { debug_assert!(unsafe { !is_in_render_thread() }); state.frame_builder.dl_builder.pop_stacking_context(); if is_reference_frame { state.frame_builder.dl_builder.pop_reference_frame(); } } #[no_mangle] pub extern "C" fn wr_dp_define_clipchain( state: &mut WrState, parent_clipchain_id: *const u64, clips: *const WrClipId, clips_count: usize, ) -> u64 { debug_assert!(unsafe { is_in_main_thread() }); let parent = unsafe { parent_clipchain_id.as_ref() }.map(|id| ClipChainId(*id, state.pipeline_id)); let pipeline_id = state.pipeline_id; let clips = unsafe { make_slice(clips, clips_count) } .iter() .map(|clip_id| clip_id.to_webrender(pipeline_id)); let clipchain_id = state.frame_builder.dl_builder.define_clip_chain(parent, clips); assert!(clipchain_id.1 == state.pipeline_id); clipchain_id.0 } #[no_mangle] pub extern "C" fn wr_dp_define_image_mask_clip_with_parent_clip_chain( state: &mut WrState, space: WrSpatialId, mask: ImageMask, points: *const LayoutPoint, point_count: usize, fill_rule: FillRule, ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let c_points = unsafe { make_slice(points, point_count) }; let points: Vec = c_points.iter().copied().collect(); let clip_id = state.frame_builder.dl_builder.define_clip_image_mask( space.to_webrender(state.pipeline_id), mask, &points, fill_rule, ); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_rounded_rect_clip( state: &mut WrState, space: WrSpatialId, complex: ComplexClipRegion, ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = state .frame_builder .dl_builder .define_clip_rounded_rect(space.to_webrender(state.pipeline_id), complex); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_rect_clip(state: &mut WrState, space: WrSpatialId, clip_rect: LayoutRect) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = state .frame_builder .dl_builder .define_clip_rect(space.to_webrender(state.pipeline_id), clip_rect); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_sticky_frame( state: &mut WrState, parent_spatial_id: WrSpatialId, content_rect: LayoutRect, top_margin: *const f32, right_margin: *const f32, bottom_margin: *const f32, left_margin: *const f32, vertical_bounds: StickyOffsetBounds, horizontal_bounds: StickyOffsetBounds, applied_offset: LayoutVector2D, key: SpatialTreeItemKey, ) -> WrSpatialId { assert!(unsafe { is_in_main_thread() }); let spatial_id = state.frame_builder.dl_builder.define_sticky_frame( parent_spatial_id.to_webrender(state.pipeline_id), content_rect, SideOffsets2D::new( unsafe { top_margin.as_ref() }.cloned(), unsafe { right_margin.as_ref() }.cloned(), unsafe { bottom_margin.as_ref() }.cloned(), unsafe { left_margin.as_ref() }.cloned(), ), vertical_bounds, horizontal_bounds, applied_offset, key, ); WrSpatialId { id: spatial_id.0 } } #[no_mangle] pub extern "C" fn wr_dp_define_scroll_layer( state: &mut WrState, external_scroll_id: u64, parent: &WrSpatialId, content_rect: LayoutRect, clip_rect: LayoutRect, scroll_offset: LayoutVector2D, scroll_offset_generation: APZScrollGeneration, has_scroll_linked_effect: HasScrollLinkedEffect, key: SpatialTreeItemKey, ) -> WrSpatialId { assert!(unsafe { is_in_main_thread() }); let space_and_clip = state.frame_builder.dl_builder.define_scroll_frame( parent.to_webrender(state.pipeline_id), ExternalScrollId(external_scroll_id, state.pipeline_id), content_rect, clip_rect, scroll_offset, scroll_offset_generation, has_scroll_linked_effect, key, ); WrSpatialId::from_webrender(space_and_clip) } #[no_mangle] pub extern "C" fn wr_dp_push_iframe( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, _is_backface_visible: bool, parent: &WrSpaceAndClipChain, pipeline_id: WrPipelineId, ignore_missing_pipeline: bool, ) { debug_assert!(unsafe { is_in_main_thread() }); state.frame_builder.dl_builder.push_iframe( rect, clip, &parent.to_webrender(state.pipeline_id), pipeline_id, ignore_missing_pipeline, ); } // A helper fn to construct a PrimitiveFlags fn prim_flags(is_backface_visible: bool, prefer_compositor_surface: bool) -> PrimitiveFlags { let mut flags = PrimitiveFlags::empty(); if is_backface_visible { flags |= PrimitiveFlags::IS_BACKFACE_VISIBLE; } if prefer_compositor_surface { flags |= PrimitiveFlags::PREFER_COMPOSITOR_SURFACE; } flags } fn prim_flags2( is_backface_visible: bool, prefer_compositor_surface: bool, supports_external_compositing: bool, ) -> PrimitiveFlags { let mut flags = PrimitiveFlags::empty(); if supports_external_compositing { flags |= PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE; } flags | prim_flags(is_backface_visible, prefer_compositor_surface) } fn common_item_properties_for_rect( state: &mut WrState, clip_rect: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, ) -> CommonItemProperties { let space_and_clip = parent.to_webrender(state.pipeline_id); CommonItemProperties { // NB: the damp-e10s talos-test will frequently crash on startup if we // early-return here for empty rects. I couldn't figure out why, but // it's pretty harmless to feed these through, so, uh, we do? clip_rect, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), } } #[no_mangle] pub extern "C" fn wr_dp_push_rect( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, force_antialiasing: bool, is_checkerboard: bool, parent: &WrSpaceAndClipChain, color: ColorF, ) { debug_assert!(unsafe { !is_in_render_thread() }); let mut prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent); if force_antialiasing { prim_info.flags |= PrimitiveFlags::ANTIALISED; } if is_checkerboard { prim_info.flags |= PrimitiveFlags::CHECKERBOARD_BACKGROUND; } state.frame_builder.dl_builder.push_rect(&prim_info, rect, color); } #[no_mangle] pub extern "C" fn wr_dp_push_rect_with_animation( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, color: ColorF, animation: *const WrAnimationProperty, ) { debug_assert!(unsafe { !is_in_render_thread() }); let prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent); let anim = unsafe { animation.as_ref() }; if let Some(anim) = anim { debug_assert!(anim.id > 0); match anim.effect_type { WrAnimationType::BackgroundColor => state.frame_builder.dl_builder.push_rect_with_animation( &prim_info, rect, PropertyBinding::Binding(PropertyBindingKey::new(anim.id), color), ), _ => unreachable!("Didn't expect {:?} animation", anim.effect_type), } } } #[no_mangle] pub extern "C" fn wr_dp_push_backdrop_filter( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, filters: *const FilterOp, filter_count: usize, filter_datas: *const WrFilterData, filter_datas_count: usize, ) { debug_assert!(unsafe { !is_in_render_thread() }); let c_filters = unsafe { make_slice(filters, filter_count) }; let filters: Vec = c_filters.iter().copied().collect(); let c_filter_datas = unsafe { make_slice(filter_datas, filter_datas_count) }; let filter_datas: Vec = c_filter_datas .iter() .map(|c_filter_data| FilterData { func_r_type: c_filter_data.funcR_type, r_values: unsafe { make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec() }, func_g_type: c_filter_data.funcG_type, g_values: unsafe { make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec() }, func_b_type: c_filter_data.funcB_type, b_values: unsafe { make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec() }, func_a_type: c_filter_data.funcA_type, a_values: unsafe { make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec() }, }) .collect(); let space_and_clip = parent.to_webrender(state.pipeline_id); let clip_rect = clip.intersection(&rect); if clip_rect.is_none() { return; } let prim_info = CommonItemProperties { clip_rect: clip_rect.unwrap(), clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_backdrop_filter(&prim_info, &filters, &filter_datas, &[]); } #[no_mangle] pub extern "C" fn wr_dp_push_clear_rect( state: &mut WrState, rect: LayoutRect, clip_rect: LayoutRect, parent: &WrSpaceAndClipChain, ) { debug_assert!(unsafe { !is_in_render_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(true, /* prefer_compositor_surface */ false), }; state.frame_builder.dl_builder.push_clear_rect(&prim_info, rect); } #[no_mangle] pub extern "C" fn wr_dp_push_hit_test( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, scroll_id: u64, hit_info: u16, ) { debug_assert!(unsafe { !is_in_render_thread() }); let clip_rect = clip.intersection(&rect); if clip_rect.is_none() { return; } let tag = (scroll_id, hit_info); let spatial_id = parent.space.to_webrender(state.pipeline_id); let clip_chain_id = if parent.clip_chain == ROOT_CLIP_CHAIN { ClipChainId::INVALID } else { ClipChainId(parent.clip_chain, state.pipeline_id) }; state.frame_builder.dl_builder.push_hit_test( clip_rect.unwrap(), clip_chain_id, spatial_id, prim_flags(is_backface_visible, false), tag, ); } #[no_mangle] pub extern "C" fn wr_dp_push_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, force_antialiasing: bool, parent: &WrSpaceAndClipChain, image_rendering: ImageRendering, key: WrImageKey, premultiplied_alpha: bool, color: ColorF, prefer_compositor_surface: bool, supports_external_compositing: bool, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let mut flags = prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ); if force_antialiasing { flags |= PrimitiveFlags::ANTIALISED; } let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags, }; let alpha_type = if premultiplied_alpha { AlphaType::PremultipliedAlpha } else { AlphaType::Alpha }; state .frame_builder .dl_builder .push_image(&prim_info, bounds, image_rendering, alpha_type, key, color); } #[no_mangle] pub extern "C" fn wr_dp_push_repeating_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, stretch_size: LayoutSize, tile_spacing: LayoutSize, image_rendering: ImageRendering, key: WrImageKey, premultiplied_alpha: bool, color: ColorF, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; let alpha_type = if premultiplied_alpha { AlphaType::PremultipliedAlpha } else { AlphaType::Alpha }; state.frame_builder.dl_builder.push_repeating_image( &prim_info, bounds, stretch_size, tile_spacing, image_rendering, alpha_type, key, color, ); } /// Push a 3 planar yuv image. #[no_mangle] pub extern "C" fn wr_dp_push_yuv_planar_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, image_key_0: WrImageKey, image_key_1: WrImageKey, image_key_2: WrImageKey, color_depth: WrColorDepth, color_space: WrYuvColorSpace, color_range: WrColorRange, image_rendering: ImageRendering, prefer_compositor_surface: bool, supports_external_compositing: bool, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ), }; state.frame_builder.dl_builder.push_yuv_image( &prim_info, bounds, YuvData::PlanarYCbCr(image_key_0, image_key_1, image_key_2), color_depth, color_space, color_range, image_rendering, ); } /// Push a 2 planar NV12 image. #[no_mangle] pub extern "C" fn wr_dp_push_yuv_NV12_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, image_key_0: WrImageKey, image_key_1: WrImageKey, color_depth: WrColorDepth, color_space: WrYuvColorSpace, color_range: WrColorRange, image_rendering: ImageRendering, prefer_compositor_surface: bool, supports_external_compositing: bool, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ), }; state.frame_builder.dl_builder.push_yuv_image( &prim_info, bounds, YuvData::NV12(image_key_0, image_key_1), color_depth, color_space, color_range, image_rendering, ); } /// Push a 2 planar P010 image. #[no_mangle] pub extern "C" fn wr_dp_push_yuv_P010_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, image_key_0: WrImageKey, image_key_1: WrImageKey, color_depth: WrColorDepth, color_space: WrYuvColorSpace, color_range: WrColorRange, image_rendering: ImageRendering, prefer_compositor_surface: bool, supports_external_compositing: bool, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ), }; state.frame_builder.dl_builder.push_yuv_image( &prim_info, bounds, YuvData::P010(image_key_0, image_key_1), color_depth, color_space, color_range, image_rendering, ); } /// Push a yuv interleaved image. #[no_mangle] pub extern "C" fn wr_dp_push_yuv_interleaved_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, image_key_0: WrImageKey, color_depth: WrColorDepth, color_space: WrYuvColorSpace, color_range: WrColorRange, image_rendering: ImageRendering, prefer_compositor_surface: bool, supports_external_compositing: bool, ) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ), }; state.frame_builder.dl_builder.push_yuv_image( &prim_info, bounds, YuvData::InterleavedYCbCr(image_key_0), color_depth, color_space, color_range, image_rendering, ); } #[no_mangle] pub extern "C" fn wr_dp_push_text( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, color: ColorF, font_key: WrFontInstanceKey, glyphs: *const GlyphInstance, glyph_count: u32, glyph_options: *const GlyphOptions, ) { debug_assert!(unsafe { is_in_main_thread() }); let glyph_slice = unsafe { make_slice(glyphs, glyph_count as usize) }; let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, spatial_id: space_and_clip.spatial_id, clip_chain_id: space_and_clip.clip_chain_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_text(&prim_info, bounds, &glyph_slice, font_key, color, unsafe { glyph_options.as_ref().cloned() }); } #[no_mangle] pub extern "C" fn wr_dp_push_shadow( state: &mut WrState, _bounds: LayoutRect, _clip: LayoutRect, _is_backface_visible: bool, parent: &WrSpaceAndClipChain, shadow: Shadow, should_inflate: bool, ) { debug_assert!(unsafe { is_in_main_thread() }); state .frame_builder .dl_builder .push_shadow(&parent.to_webrender(state.pipeline_id), shadow, should_inflate); } #[no_mangle] pub extern "C" fn wr_dp_pop_all_shadows(state: &mut WrState) { debug_assert!(unsafe { is_in_main_thread() }); state.frame_builder.dl_builder.pop_all_shadows(); } #[no_mangle] pub extern "C" fn wr_dp_push_line( state: &mut WrState, clip: &LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, bounds: &LayoutRect, wavy_line_thickness: f32, orientation: LineOrientation, color: &ColorF, style: LineStyle, ) { debug_assert!(unsafe { is_in_main_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: *clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_line(&prim_info, bounds, wavy_line_thickness, orientation, color, style); } #[no_mangle] pub extern "C" fn wr_dp_push_border( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, do_aa: AntialiasBorder, widths: LayoutSideOffsets, top: BorderSide, right: BorderSide, bottom: BorderSide, left: BorderSide, radius: BorderRadius, ) { debug_assert!(unsafe { is_in_main_thread() }); let border_details = BorderDetails::Normal(NormalBorder { left, right, top, bottom, radius, do_aa: do_aa == AntialiasBorder::Yes, }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_border(&prim_info, rect, widths, border_details); } #[repr(C)] pub struct WrBorderImage { widths: LayoutSideOffsets, image: WrImageKey, image_rendering: ImageRendering, width: i32, height: i32, fill: bool, slice: DeviceIntSideOffsets, repeat_horizontal: RepeatMode, repeat_vertical: RepeatMode, } #[no_mangle] pub extern "C" fn wr_dp_push_border_image( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, params: &WrBorderImage, ) { debug_assert!(unsafe { is_in_main_thread() }); let border_details = BorderDetails::NinePatch(NinePatchBorder { source: NinePatchBorderSource::Image(params.image, params.image_rendering), width: params.width, height: params.height, slice: params.slice, fill: params.fill, repeat_horizontal: params.repeat_horizontal, repeat_vertical: params.repeat_vertical, }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_border(&prim_info, rect, params.widths, border_details); } #[no_mangle] pub extern "C" fn wr_dp_push_border_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, widths: LayoutSideOffsets, width: i32, height: i32, fill: bool, slice: DeviceIntSideOffsets, start_point: LayoutPoint, end_point: LayoutPoint, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let gradient = state .frame_builder .dl_builder .create_gradient(start_point, end_point, stops_vector, extend_mode); let border_details = BorderDetails::NinePatch(NinePatchBorder { source: NinePatchBorderSource::Gradient(gradient), width, height, slice, fill, repeat_horizontal: RepeatMode::Stretch, repeat_vertical: RepeatMode::Stretch, }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_border(&prim_info, rect, widths, border_details); } #[no_mangle] pub extern "C" fn wr_dp_push_border_radial_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, widths: LayoutSideOffsets, fill: bool, center: LayoutPoint, radius: LayoutSize, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let slice = SideOffsets2D::new( widths.top as i32, widths.right as i32, widths.bottom as i32, widths.left as i32, ); let gradient = state .frame_builder .dl_builder .create_radial_gradient(center, radius, stops_vector, extend_mode); let border_details = BorderDetails::NinePatch(NinePatchBorder { source: NinePatchBorderSource::RadialGradient(gradient), width: rect.width() as i32, height: rect.height() as i32, slice, fill, repeat_horizontal: RepeatMode::Stretch, repeat_vertical: RepeatMode::Stretch, }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_border(&prim_info, rect, widths, border_details); } #[no_mangle] pub extern "C" fn wr_dp_push_border_conic_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, widths: LayoutSideOffsets, fill: bool, center: LayoutPoint, angle: f32, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let slice = SideOffsets2D::new( widths.top as i32, widths.right as i32, widths.bottom as i32, widths.left as i32, ); let gradient = state .frame_builder .dl_builder .create_conic_gradient(center, angle, stops_vector, extend_mode); let border_details = BorderDetails::NinePatch(NinePatchBorder { source: NinePatchBorderSource::ConicGradient(gradient), width: rect.width() as i32, height: rect.height() as i32, slice, fill, repeat_horizontal: RepeatMode::Stretch, repeat_vertical: RepeatMode::Stretch, }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_border(&prim_info, rect, widths, border_details); } #[no_mangle] pub extern "C" fn wr_dp_push_linear_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, start_point: LayoutPoint, end_point: LayoutPoint, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let gradient = state .frame_builder .dl_builder .create_gradient(start_point, end_point, stops_vector, extend_mode); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_gradient(&prim_info, rect, gradient, tile_size, tile_spacing); } #[no_mangle] pub extern "C" fn wr_dp_push_radial_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, center: LayoutPoint, radius: LayoutSize, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let gradient = state .frame_builder .dl_builder .create_radial_gradient(center, radius, stops_vector, extend_mode); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_radial_gradient(&prim_info, rect, gradient, tile_size, tile_spacing); } #[no_mangle] pub extern "C" fn wr_dp_push_conic_gradient( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, center: LayoutPoint, angle: f32, stops: *const GradientStop, stops_count: usize, extend_mode: ExtendMode, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { debug_assert!(unsafe { is_in_main_thread() }); let stops_slice = unsafe { make_slice(stops, stops_count) }; let stops_vector = stops_slice.to_owned(); let gradient = state .frame_builder .dl_builder .create_conic_gradient(center, angle, stops_vector, extend_mode); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state .frame_builder .dl_builder .push_conic_gradient(&prim_info, rect, gradient, tile_size, tile_spacing); } #[no_mangle] pub extern "C" fn wr_dp_push_box_shadow( state: &mut WrState, _rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClipChain, box_bounds: LayoutRect, offset: LayoutVector2D, color: ColorF, blur_radius: f32, spread_radius: f32, border_radius: BorderRadius, clip_mode: BoxShadowClipMode, ) { debug_assert!(unsafe { is_in_main_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect: clip, clip_chain_id: space_and_clip.clip_chain_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state.frame_builder.dl_builder.push_box_shadow( &prim_info, box_bounds, offset, color, blur_radius, spread_radius, border_radius, clip_mode, ); } #[no_mangle] pub extern "C" fn wr_dp_start_item_group(state: &mut WrState) { state.frame_builder.dl_builder.start_item_group(); } #[no_mangle] pub extern "C" fn wr_dp_cancel_item_group(state: &mut WrState, discard: bool) { state.frame_builder.dl_builder.cancel_item_group(discard); } #[no_mangle] pub extern "C" fn wr_dp_finish_item_group(state: &mut WrState, key: ItemKey) -> bool { state.frame_builder.dl_builder.finish_item_group(key) } #[no_mangle] pub extern "C" fn wr_dp_push_reuse_items(state: &mut WrState, key: ItemKey) { state.frame_builder.dl_builder.push_reuse_items(key); } #[no_mangle] pub extern "C" fn wr_dp_set_cache_size(state: &mut WrState, cache_size: usize) { state.frame_builder.dl_builder.set_cache_size(cache_size); } #[no_mangle] pub extern "C" fn wr_dump_display_list( state: &mut WrState, indent: usize, start: *const usize, end: *const usize, ) -> usize { let start = unsafe { start.as_ref().cloned() }; let end = unsafe { end.as_ref().cloned() }; let range = Range { start, end }; let mut sink = Cursor::new(Vec::new()); let index = state .frame_builder .dl_builder .emit_display_list(indent, range, &mut sink); // For Android, dump to logcat instead of stderr. This is the same as // what printf_stderr does on the C++ side. #[cfg(target_os = "android")] unsafe { let gecko = CString::new("Gecko").unwrap(); let sink = CString::new(sink.into_inner()).unwrap(); __android_log_write(4 /* info */, gecko.as_ptr(), sink.as_ptr()); } #[cfg(not(target_os = "android"))] eprint!("{}", String::from_utf8(sink.into_inner()).unwrap()); index } #[no_mangle] pub extern "C" fn wr_dump_serialized_display_list(state: &mut WrState) { state.frame_builder.dl_builder.dump_serialized_display_list(); } #[no_mangle] pub unsafe extern "C" fn wr_api_begin_builder(state: &mut WrState) { state.frame_builder.dl_builder.begin(); } #[no_mangle] pub unsafe extern "C" fn wr_api_end_builder( state: &mut WrState, dl_descriptor: &mut BuiltDisplayListDescriptor, dl_items_data: &mut WrVecU8, dl_cache_data: &mut WrVecU8, dl_spatial_tree: &mut WrVecU8, ) { let (_, dl) = state.frame_builder.dl_builder.end(); let (payload, descriptor) = dl.into_data(); *dl_items_data = WrVecU8::from_vec(payload.items_data); *dl_cache_data = WrVecU8::from_vec(payload.cache_data); *dl_spatial_tree = WrVecU8::from_vec(payload.spatial_tree); *dl_descriptor = descriptor; } #[repr(C)] pub struct HitResult { pipeline_id: WrPipelineId, scroll_id: u64, animation_id: u64, hit_info: u16, } #[no_mangle] pub extern "C" fn wr_api_hit_test(dh: &mut DocumentHandle, point: WorldPoint, out_results: &mut ThinVec) { let result = dh.ensure_hit_tester().hit_test(point); for item in &result.items { out_results.push(HitResult { pipeline_id: item.pipeline, scroll_id: item.tag.0, animation_id: item.animation_id, hit_info: item.tag.1, }); } } pub type VecU8 = Vec; pub type ArcVecU8 = Arc; #[no_mangle] pub extern "C" fn wr_add_ref_arc(arc: &ArcVecU8) -> *const VecU8 { Arc::into_raw(arc.clone()) } #[no_mangle] pub unsafe extern "C" fn wr_dec_ref_arc(arc: *const VecU8) { Arc::from_raw(arc); } // TODO: nical // Update for the new blob image interface changes. // extern "C" { // TODO: figure out the API for tiled blob images. pub fn wr_moz2d_render_cb( blob: ByteSlice, format: ImageFormat, render_rect: &LayoutIntRect, visible_rect: &DeviceIntRect, tile_size: u16, tile_offset: &TileOffset, dirty_rect: Option<&LayoutIntRect>, output: MutByteSlice, ) -> bool; } #[no_mangle] pub extern "C" fn wr_root_scroll_node_id() -> WrSpatialId { // The PipelineId doesn't matter here, since we just want the numeric part of the id // produced for any given root reference frame. WrSpatialId { id: SpatialId::root_scroll_node(PipelineId(0, 0)).0, } } #[no_mangle] pub extern "C" fn wr_root_clip_id() -> WrClipId { // The PipelineId doesn't matter here, since we just want the numeric part of the id // produced for any given root reference frame. WrClipId::from_webrender(ClipId::root(PipelineId(0, 0))) } #[repr(C)] #[derive(Clone, Copy)] pub struct WrClipId { id: usize, } impl WrClipId { fn to_webrender(&self, pipeline_id: WrPipelineId) -> ClipId { ClipId(self.id, pipeline_id) } fn from_webrender(clip_id: ClipId) -> Self { WrClipId { id: clip_id.0 } } } #[repr(C)] #[derive(Clone, Copy)] pub struct WrSpatialId { id: usize, } impl WrSpatialId { fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpatialId { SpatialId::new(self.id, pipeline_id) } fn from_webrender(id: SpatialId) -> Self { WrSpatialId { id: id.0 } } } #[no_mangle] pub unsafe extern "C" fn wr_device_delete(device: *mut Device) { mem::drop(Box::from_raw(device)); } // Call MakeCurrent before this. #[no_mangle] pub extern "C" fn wr_shaders_new( gl_context: *mut c_void, program_cache: Option<&mut WrProgramCache>, precache_shaders: bool, ) -> *mut WrShaders { let mut device = wr_device_new(gl_context, program_cache); let precache_flags = if precache_shaders || env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") { ShaderPrecacheFlags::FULL_COMPILE } else { ShaderPrecacheFlags::ASYNC_COMPILE }; let opts = WebRenderOptions { precache_flags, ..Default::default() }; let gl_type = device.gl().get_type(); device.begin_frame(); let shaders = Rc::new(RefCell::new(match Shaders::new(&mut device, gl_type, &opts) { Ok(shaders) => shaders, Err(e) => { warn!(" Failed to create a Shaders: {:?}", e); let msg = CString::new(format!("wr_shaders_new: {:?}", e)).unwrap(); unsafe { gfx_critical_note(msg.as_ptr()); } return ptr::null_mut(); }, })); let shaders = WrShaders(shaders); device.end_frame(); Box::into_raw(Box::new(shaders)) } #[no_mangle] pub unsafe extern "C" fn wr_shaders_delete(shaders: *mut WrShaders, gl_context: *mut c_void) { let mut device = wr_device_new(gl_context, None); let shaders = Box::from_raw(shaders); if let Ok(shaders) = Rc::try_unwrap(shaders.0) { shaders.into_inner().deinit(&mut device); } // let shaders go out of scope and get dropped } #[no_mangle] pub unsafe extern "C" fn wr_program_cache_report_memory( cache: *const WrProgramCache, size_of_op: VoidPtrToSizeFn, ) -> usize { (*cache).program_cache.report_memory(size_of_op) }