/* 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/. */ 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 swgl_bindings::SwCompositor; use tracy_rs::register_thread_with_profiler; use webrender::{ api::units::*, api::*, render_api::*, set_profiler_hooks, AsyncPropertySampler, AsyncScreenshotHandle, Compositor, CompositorCapabilities, CompositorConfig, CompositorSurfaceTransform, DebugFlags, Device, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, PartialPresentCompositor, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, RendererStats, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, SharedShaders, TextureCacheConfig, ThreadListener, UploadMethod, ONE_TIME_USAGE_HINT, }; use wr_malloc_size_of::MallocSizeOfOps; #[cfg(target_os = "macos")] use core_foundation::string::CFString; #[cfg(target_os = "macos")] use core_graphics::font::CGFont; 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; #[repr(C)] pub struct WrSpaceAndClip { space: WrSpatialId, clip: WrClipId, } impl WrSpaceAndClip { fn from_webrender(sac: SpaceAndClipInfo) -> Self { WrSpaceAndClip { space: WrSpatialId { id: sac.spatial_id.0 }, clip: WrClipId::from_webrender(sac.clip_id), } } fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo { SpaceAndClipInfo { spatial_id: self.space.to_webrender(pipeline_id), clip_id: self.clip.to_webrender(pipeline_id), } } } #[inline] fn clip_chain_id_to_webrender(id: u64, pipeline_id: WrPipelineId) -> ClipId { if id == ROOT_CLIP_CHAIN { ClipId::root(pipeline_id) } else { ClipId::ClipChain(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_id: clip_chain_id_to_webrender(self.clip_chain, pipeline_id), } } } #[repr(C)] pub enum WrStackingContextClip { None, ClipId(WrClipId), ClipChain(u64), } impl WrStackingContextClip { fn to_webrender(&self, pipeline_id: WrPipelineId) -> Option { match *self { WrStackingContextClip::None => None, WrStackingContextClip::ClipChain(id) => Some(clip_chain_id_to_webrender(id, pipeline_id)), WrStackingContextClip::ClipId(id) => Some(id.to_webrender(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 movign 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) { if self.hit_tester.is_none() { self.hit_tester = Some(self.hit_tester_request.take().unwrap().resolve()); } } } #[repr(C)] pub struct WrVecU8 { data: *mut u8, length: usize, capacity: usize, } impl WrVecU8 { fn to_vec(self) -> Vec { unsafe { Vec::from_raw_parts(self.data, self.length, self.capacity) } } // Equivalent to `to_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.to_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> Into for &'a WrImageDescriptor { fn into(self) -> ImageDescriptor { let mut flags = ImageDescriptorFlags::empty(); if self.opacity == OpacityType::Opaque { flags |= ImageDescriptorFlags::IS_OPAQUE; } ImageDescriptor { size: DeviceIntSize::new(self.width, self.height), stride: if self.stride != 0 { Some(self.stride) } else { None }, format: self.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, rendering: ImageRendering, ) -> 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, rendering: ImageRendering) -> ExternalImage { let image = unsafe { wr_renderer_lock_external_image(self.external_image_obj, id, channel_index, rendering) }; 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, } /// 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, } 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()) }; if symbol.is_null() { // XXX Bug 1322949 Make whitelist for extensions warn!("Could not find symbol {:?} by glcontext", symbol_name); } 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 record_telemetry_time(probe: TelemetryProbe, time_ns: u64); 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); fn wr_notifier_nop_frame_done(window_id: WrWindowId); fn wr_notifier_external_event(window_id: WrWindowId, raw_event: usize); fn wr_schedule_render(window_id: WrWindowId); // 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, render_time_ns: Option) { unsafe { if let Some(time) = render_time_ns { record_telemetry_time(TelemetryProbe::FrameBuildTime, time); } if composite_needed { wr_notifier_new_frame_ready(self.window_id); } else { wr_notifier_nop_frame_done(self.window_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: &str) { let value = CString::new(value).unwrap(); 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(Some(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_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::new( 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) { *report += renderer.report_memory(); } // 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_start_marker(name: *const c_char); pub fn gecko_profiler_end_marker(name: *const c_char); pub fn gecko_profiler_event_marker(name: *const c_char); pub fn gecko_profiler_add_text_marker( name: *const c_char, text_bytes: *const c_char, text_len: usize, microseconds: u64, ); pub fn gecko_profiler_thread_is_being_profiled() -> bool; } /// 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 begin_marker(&self, label: &CStr) { unsafe { gecko_profiler_start_marker(label.as_ptr()); } } fn end_marker(&self, label: &CStr) { unsafe { gecko_profiler_end_marker(label.as_ptr()); } } fn event_marker(&self, label: &CStr) { unsafe { gecko_profiler_event_marker(label.as_ptr()); } } fn add_text_marker(&self, label: &CStr, text: &str, duration: Duration) { unsafe { // NB: This can be as_micros() once we require Rust 1.33. let micros = duration.subsec_micros() as u64 + duration.as_secs() * 1000 * 1000; let text_bytes = text.as_bytes(); gecko_profiler_add_text_marker( label.as_ptr(), text_bytes.as_ptr() as *const c_char, text_bytes.len(), 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) { unsafe { gecko_profiler_start_marker(b"SceneBuilding\0".as_ptr() as *const c_char); } } fn pre_scene_swap(&self, scenebuild_time: u64) { unsafe { record_telemetry_time(TelemetryProbe::SceneBuildTime, scenebuild_time); apz_pre_scene_swap(self.window_id); } } fn post_scene_swap(&self, _document_ids: &Vec, info: PipelineInfo, sceneswap_time: u64) { let mut info = WrPipelineInfo::new(&info); unsafe { record_telemetry_time(TelemetryProbe::SceneSwapTime, sceneswap_time); 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) } unsafe { gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char); } } fn post_resource_update(&self, _document_ids: &Vec) { unsafe { wr_schedule_render(self.window_id) } unsafe { gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char); } } fn post_empty_scene_build(&self) { unsafe { gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char); } } 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(); unsafe { // XXX: When we implement scroll-linked animations, we will probably // need to call apz_sample_transforms prior to omta_sample. omta_sample(self.window_id, &mut transaction); apz_sample_transforms(self.window_id, generated_frame_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 gecko_profiler_register_thread(name: *const ::std::os::raw::c_char); fn gecko_profiler_unregister_thread(); fn wr_register_thread_local_arena(); } pub struct GeckoProfilerThreadListener {} impl GeckoProfilerThreadListener { pub fn new() -> GeckoProfilerThreadListener { GeckoProfilerThreadListener {} } } impl ThreadListener for GeckoProfilerThreadListener { fn thread_started(&self, thread_name: &str) { let name = CString::new(thread_name).unwrap(); unsafe { // gecko_profiler_register_thread copies the passed name here. gecko_profiler_register_thread(name.as_ptr()); } } fn thread_stopped(&self, _: &str) { unsafe { gecko_profiler_unregister_thread(); } } } 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 8. We get diminishing returns // with high worker counts and extra overhead because of rayon and font // management. let num_threads = num_cpus::get().max(2).min(8); 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()); let name = CString::new(name).unwrap(); gecko_profiler_register_thread(name.as_ptr()); }) .exit_handler(|_idx| unsafe { gecko_profiler_unregister_thread(); }) .build(); let workers = Arc::new(worker.unwrap()); // This effectively leaks the thread pool. Not great but we only create one and it lives // for as long as the browser. // Do this to avoid intermittent race conditions with nsThreadManager shutdown. // A better fix would involve removing the dependency between implicit nsThreadManager // and webrender's threads, or be able to synchronously terminate rayon's thread pool. mem::forget(Arc::clone(&workers)); Box::into_raw(Box::new(WrThreadPool(workers))) } #[no_mangle] pub unsafe extern "C" fn wr_thread_pool_delete(thread_pool: *mut WrThreadPool) { 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) { 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 = match pc { Some(cached_programs) => Some(Rc::clone(cached_programs.rc_get())), None => None, }; Device::new( gl, Some(Box::new(MozCrashAnnotator)), resource_override_path, use_optimized_shaders, upload_method, 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_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, 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) -> CompositorCapabilities; 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, 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, id: NativeSurfaceId, is_opaque: bool) { unsafe { wr_compositor_create_external_surface(self.0, id, is_opaque); } } fn destroy_surface(&mut self, id: NativeSurfaceId) { unsafe { wr_compositor_destroy_surface(self.0, id); } } fn create_tile(&mut self, id: NativeTileId) { unsafe { wr_compositor_create_tile(self.0, id.surface_id, id.x, id.y); } } fn destroy_tile(&mut self, id: NativeTileId) { unsafe { wr_compositor_destroy_tile(self.0, id.surface_id, id.x, id.y); } } fn attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId) { unsafe { wr_compositor_attach_external_image(self.0, id, external_image); } } fn bind(&mut self, 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) { unsafe { wr_compositor_unbind(self.0); } } fn begin_frame(&mut self) { unsafe { wr_compositor_begin_frame(self.0); } } fn add_surface( &mut self, 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, dirty_rects: &[DeviceIntRect], opaque_rects: &[DeviceIntRect], ) { unsafe { wr_compositor_start_compositing( self.0, dirty_rects.as_ptr(), dirty_rects.len(), opaque_rects.as_ptr(), opaque_rects.len(), ); } } fn end_frame(&mut self) { unsafe { wr_compositor_end_frame(self.0); } } fn enable_native_compositor(&mut self, enable: bool) { unsafe { wr_compositor_enable_native_compositor(self.0, enable); } } fn deinit(&mut self) { unsafe { wr_compositor_deinit(self.0); } } fn get_capabilities(&self) -> CompositorCapabilities { unsafe { wr_compositor_get_capabilities(self.0) } } } 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()); } } } /// Information about the underlying data buffer of a mapped tile. #[repr(C)] #[derive(Copy, Clone)] pub struct MappedTileInfo { pub data: *mut c_void, pub stride: i32, } /// WrCompositor-specific extensions to the basic Compositor interface. impl 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. pub fn map_tile( &mut self, 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 != ptr::null_mut() && 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. pub fn unmap_tile(&mut self) { unsafe { wr_compositor_unmap_tile(self.0); } } } /// 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, start_debug_server: 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, max_update_rects: usize, 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, ) -> bool { assert!(unsafe { is_in_render_thread() }); let native_gl = if gl_context == ptr::null_mut() { None } else if unsafe { is_glcontext_gles(gl_context) } { unsafe { Some(gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol))) } } else { unsafe { Some(gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol))) } }; let software = swgl_context != ptr::null_mut(); let (gl, sw_gl) = if software { let ctx = swgl::Context::from(swgl_context); ctx.make_current(); (Rc::new(ctx) as Rc, Some(ctx)) } else { ( native_gl .as_ref() .expect("Native GL context required when not using SWGL!") .clone(), 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 != ptr::null_mut() && 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 = match program_cache { Some(program_cache) => Some(Rc::clone(&program_cache.rc_get())), None => None, }; 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 { max_update_rects: 1, compositor: Box::new(SwCompositor::new( sw_gl.unwrap(), native_gl, WrCompositor(compositor), use_native_compositor, )), } } else if use_native_compositor { CompositorConfig::Native { max_update_rects, 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 = RendererOptions { enable_aa: true, force_subpixel_aa: false, enable_subpixel_aa: cfg!(not(target_os = "android")), 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), thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())), 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_texture_size: Some(8192), // Moz2D doesn't like textures bigger than this clear_color: Some(color), precache_flags, namespace_alloc_by_client: true, // 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, start_debug_server, surface_origin_is_top_left, compositor_config, enable_gpu_markers, panic_on_gl_error, picture_tile_size, texture_cache_config, ..Default::default() }; // Ensure the WR profiler callbacks are hooked up to the Gecko profiler. set_profiler_hooks(Some(&PROFILER_HOOKS)); let window_size = DeviceIntSize::new(window_width, window_height); let notifier = Box::new(CppNotifier { window_id }); let (renderer, sender) = match Renderer::new(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 != ptr::null_mut() { 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() }); dh.ensure_hit_tester(); let handle = DocumentHandle { api: dh.api.create_sender().create_api_by_client(next_namespace_id()), document_id: dh.document_id, hit_tester: dh.hit_tester.clone(), 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_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 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_enable_multithreading(dh: &mut DocumentHandle, enable: bool) { dh.api.send_debug_cmd(DebugCommand::EnableMultithreading(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, background: ColorF, viewport_size: LayoutSize, pipeline_id: WrPipelineId, dl_descriptor: BuiltDisplayListDescriptor, dl_data: &mut WrVecU8, ) { let color = if background.a == 0.0 { None } else { Some(background) }; // See the documentation of set_display_list in api.rs. I don't think // it makes a difference in gecko at the moment(until APZ is figured out) // but I suppose it is a good default. let preserve_frame_state = true; let dl_vec = dl_data.flush_into_vec(); let dl = BuiltDisplayList::from_data(dl_vec, dl_descriptor); txn.set_display_list(epoch, color, viewport_size, (pipeline_id, dl), preserve_frame_state); } #[no_mangle] pub extern "C" fn wr_transaction_set_document_view(txn: &mut Transaction, doc_rect: &DeviceIntRect) { txn.set_document_view(*doc_rect, 1.0); } #[no_mangle] pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction, id: u64) { txn.generate_frame(id); } #[no_mangle] pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) { txn.invalidate_rendered_frame(); } 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_update_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, ) { 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.update_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, pipeline_id: WrPipelineId, scroll_id: u64, new_scroll_origin: LayoutPoint, ) { let scroll_id = ExternalScrollId(scroll_id, pipeline_id); txn.scroll_node_with_id(new_scroll_origin, scroll_id, ScrollClamping::NoClamping); } #[no_mangle] pub extern "C" fn wr_transaction_pinch_zoom(txn: &mut Transaction, pinch_zoom: f32) { txn.set_pinch_zoom(ZoomFactor::new(pinch_zoom)); } #[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_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, bytes: &mut WrVecU8, visible_rect: DeviceIntRect, ) { txn.add_blob_image( image_key, descriptor.into(), Arc::new(bytes.flush_into_vec()), visible_rect, if descriptor.format == ImageFormat::BGRA8 { Some(256) } 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 preserve_frame_state = true; let frame_builder = WebRenderFrameBuilder::new(pipeline_id); txn.set_display_list( epoch, None, LayoutSize::new(0.0, 0.0), frame_builder.dl_builder.finalize(), preserve_frame_state, ); } #[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 { let chars = bytes.flush_into_vec(); let name = String::from_utf8(chars).unwrap(); let font = match CGFont::from_name(&CFString::new(&*name)) { Ok(font) => font, Err(_) => { // If for some reason we failed to load a font descriptor, then our // only options are to either abort or substitute a fallback font. // It is preferable to use a fallback font instead so that rendering // can at least still proceed in some fashion without erroring. // Lucida Grande is the fallback font in Gecko, so use that here. CGFont::from_name(&CFString::from_static_string("Lucida Grande")) .expect("Failed reading font descriptor and could not load fallback font") } }; NativeFontHandle(font) } #[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 fn with_capacity(root_pipeline_id: WrPipelineId, capacity: usize) -> WebRenderFrameBuilder { WebRenderFrameBuilder { root_pipeline_id, dl_builder: DisplayListBuilder::with_capacity(root_pipeline_id, capacity), } } } pub struct WrState { pipeline_id: WrPipelineId, frame_builder: WebRenderFrameBuilder, } #[no_mangle] pub extern "C" fn wr_state_new(pipeline_id: WrPipelineId, capacity: usize) -> *mut WrState { assert!(unsafe { !is_in_render_thread() }); let state = Box::new(WrState { pipeline_id, frame_builder: WebRenderFrameBuilder::with_capacity(pipeline_id, capacity), }); Box::into_raw(state) } #[no_mangle] pub extern "C" fn wr_state_delete(state: *mut WrState) { assert!(unsafe { !is_in_render_thread() }); unsafe { 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, Zoom, } #[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 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, mut bounds: LayoutRect, spatial_id: WrSpatialId, params: &WrStackingContextParams, transform: *const LayoutTransform, 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 = match transform_ref { Some(t) => Some(PropertyBinding::Value(*t)), None => None, }; 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.cloned().unwrap_or(LayoutTransform::identity()), )); } _ => 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); // 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, WrReferenceFrameKind::Perspective => ReferenceFrameKind::Perspective { scrolling_relative_to }, WrReferenceFrameKind::Zoom => ReferenceFrameKind::Zoom, }; wr_spatial_id = state.frame_builder.dl_builder.push_reference_frame( bounds.origin, wr_spatial_id, params.transform_style, transform_binding, reference_frame_kind, ); bounds.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( bounds.origin, wr_spatial_id, Some(data.scale_from), data.vertical_flip, rotation, ); bounds.origin = LayoutPoint::zero(); result.id = wr_spatial_id.0; assert_ne!(wr_spatial_id.0, 0); } state.frame_builder.dl_builder.push_stacking_context( bounds.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_clip_with_parent_clip( state: &mut WrState, parent: &WrSpaceAndClip, clip_rect: LayoutRect, complex: *const ComplexClipRegion, complex_count: usize, ) -> WrClipId { wr_dp_define_clip_impl( &mut state.frame_builder, parent.to_webrender(state.pipeline_id), clip_rect, unsafe { make_slice(complex, complex_count) }, ) } #[no_mangle] pub extern "C" fn wr_dp_define_clip_with_parent_clip_chain( state: &mut WrState, parent: &WrSpaceAndClipChain, clip_rect: LayoutRect, complex: *const ComplexClipRegion, complex_count: usize, ) -> WrClipId { wr_dp_define_clip_impl( &mut state.frame_builder, parent.to_webrender(state.pipeline_id), clip_rect, unsafe { make_slice(complex, complex_count) }, ) } fn wr_dp_define_clip_impl( frame_builder: &mut WebRenderFrameBuilder, parent: SpaceAndClipInfo, clip_rect: LayoutRect, complex_regions: &[ComplexClipRegion], ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = frame_builder .dl_builder .define_clip(&parent, clip_rect, complex_regions.iter().cloned()); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_image_mask_clip_with_parent_clip_chain( state: &mut WrState, parent: &WrSpaceAndClipChain, mask: ImageMask, ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = state .frame_builder .dl_builder .define_clip_image_mask(&parent.to_webrender(state.pipeline_id), mask); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_rounded_rect_clip_with_parent_clip_chain( state: &mut WrState, parent: &WrSpaceAndClipChain, complex: ComplexClipRegion, ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = state .frame_builder .dl_builder .define_clip_rounded_rect(&parent.to_webrender(state.pipeline_id), complex); WrClipId::from_webrender(clip_id) } #[no_mangle] pub extern "C" fn wr_dp_define_rect_clip_with_parent_clip_chain( state: &mut WrState, parent: &WrSpaceAndClipChain, clip_rect: LayoutRect, ) -> WrClipId { debug_assert!(unsafe { is_in_main_thread() }); let clip_id = state .frame_builder .dl_builder .define_clip_rect(&parent.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, ) -> 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, ); WrSpatialId { id: spatial_id.0 } } #[no_mangle] pub extern "C" fn wr_dp_define_scroll_layer( state: &mut WrState, external_scroll_id: u64, parent: &WrSpaceAndClip, content_rect: LayoutRect, clip_rect: LayoutRect, scroll_offset: LayoutPoint, ) -> WrSpaceAndClip { 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, ScrollSensitivity::Script, // TODO(gw): We should also update the Gecko-side APIs to provide // this as a vector rather than a point. scroll_offset.to_vector(), ); WrSpaceAndClip::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_id: space_and_clip.clip_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, parent: &WrSpaceAndClipChain, color: ColorF, ) { debug_assert!(unsafe { !is_in_render_thread() }); let prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent); 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_rect_with_parent_clip( state: &mut WrState, rect: LayoutRect, clip_rect: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClip, color: ColorF, ) { debug_assert!(unsafe { !is_in_render_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect, clip_id: space_and_clip.clip_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state.frame_builder.dl_builder.push_rect(&prim_info, rect, color); } #[no_mangle] pub extern "C" fn wr_dp_push_backdrop_filter_with_parent_clip( state: &mut WrState, rect: LayoutRect, clip: LayoutRect, is_backface_visible: bool, parent: &WrSpaceAndClip, 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_id: space_and_clip.clip_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_id: space_and_clip.clip_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 space_and_clip = parent.to_webrender(state.pipeline_id); let clip_rect = clip.intersection(&rect); if clip_rect.is_none() { return; } let tag = (scroll_id, hit_info); let prim_info = CommonItemProperties { clip_rect: clip_rect.unwrap(), clip_id: space_and_clip.clip_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false), }; state.frame_builder.dl_builder.push_hit_test(&prim_info, tag); } #[no_mangle] pub extern "C" fn wr_dp_push_clear_rect_with_parent_clip( state: &mut WrState, rect: LayoutRect, clip_rect: LayoutRect, parent: &WrSpaceAndClip, ) { debug_assert!(unsafe { !is_in_render_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); let prim_info = CommonItemProperties { clip_rect, clip_id: space_and_clip.clip_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_image( state: &mut WrState, bounds: LayoutRect, clip: LayoutRect, is_backface_visible: 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 prim_info = CommonItemProperties { clip_rect: clip, clip_id: space_and_clip.clip_id, spatial_id: space_and_clip.spatial_id, flags: prim_flags2( is_backface_visible, prefer_compositor_surface, supports_external_compositing, ), }; 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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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 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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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, width: i32, height: i32, fill: bool, slice: DeviceIntSideOffsets, outset: LayoutSideOffsets, 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), width: params.width, height: params.height, slice: params.slice, fill: params.fill, outset: params.outset, 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_id: space_and_clip.clip_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, outset: LayoutSideOffsets, ) { 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, outset, 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_id: space_and_clip.clip_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, outset: LayoutSideOffsets, ) { 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.size.width as i32, height: rect.size.height as i32, slice, fill, outset, 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_id: space_and_clip.clip_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, outset: LayoutSideOffsets, ) { 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.size.width as i32, height: rect.size.height as i32, slice, fill, outset, 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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_id: space_and_clip.clip_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_finalize_builder( state: &mut WrState, dl_descriptor: &mut BuiltDisplayListDescriptor, dl_data: &mut WrVecU8, ) { let frame_builder = mem::replace(&mut state.frame_builder, WebRenderFrameBuilder::new(state.pipeline_id)); let (_, dl) = frame_builder.dl_builder.finalize(); let (data, descriptor) = dl.into_data(); *dl_data = WrVecU8::from_vec(data); *dl_descriptor = descriptor; } #[repr(C)] pub struct HitResult { pipeline_id: WrPipelineId, scroll_id: u64, hit_info: u16, } #[no_mangle] pub extern "C" fn wr_api_hit_test(dh: &mut DocumentHandle, point: WorldPoint, out_results: &mut ThinVec) { dh.ensure_hit_tester(); let result = dh.hit_tester.as_ref().unwrap().hit_test(None, point); for item in &result.items { out_results.push(HitResult { pipeline_id: item.pipeline, scroll_id: item.tag.0, 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::Clip(self.id, pipeline_id) } fn from_webrender(clip_id: ClipId) -> Self { match clip_id { ClipId::Clip(id, _) => WrClipId { id }, ClipId::ClipChain(_) => panic!("Unexpected clip chain"), } } } #[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) } } #[no_mangle] pub unsafe extern "C" fn wr_device_delete(device: *mut Device) { 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 = RendererOptions { 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) }