Source code

Revision control

Other Tools

/* 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, FastHashMap,
NativeSurfaceId, NativeSurfaceInfo, NativeTileId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer,
RendererOptions, RendererStats, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod,
WrShaders, 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<ClipId> {
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<HitTesterRequest>,
hit_tester: Option<Arc<dyn ApiHitTester>>,
}
impl DocumentHandle {
pub fn new(
api: RenderApi,
hit_tester: Option<Arc<dyn ApiHitTester>>,
size: DeviceIntSize,
layer: i8,
id: u32,
) -> DocumentHandle {
let doc = api.add_document_with_id(size, layer, 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: 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<u8> {
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<u8> {
self.convert_into_vec::<u8>()
}
// Like flush_into_vec, but also does an unsafe conversion to the desired type.
fn convert_into_vec<T>(&mut self) -> Vec<T> {
let vec = unsafe {
Vec::from_raw_parts(
self.data as *mut T,
self.length / mem::size_of::<T>(),
self.capacity / mem::size_of::<T>(),
)
};
self.data = ptr::null_mut();
self.length = 0;
self.capacity = 0;
vec
}
fn from_vec(mut v: Vec<u8>) -> 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: 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<ImageDescriptor> 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<T> {
pub id: u64,
pub value: T,
}
pub type WrTransformProperty = WrAnimationPropertyValue<LayoutTransform>;
pub type WrOpacityProperty = WrAnimationPropertyValue<f32>;
pub type WrColorProperty = WrAnimationPropertyValue<ColorF>;
/// 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);
}
struct CppNotifier {
window_id: WrWindowId,
}
unsafe impl Send for CppNotifier {}
extern "C" {
fn wr_notifier_wake_up(window_id: WrWindowId);
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<dyn RenderNotifier> {
Box::new(CppNotifier {
window_id: self.window_id,
})
}
fn wake_up(&self) {
unsafe {
wr_notifier_wake_up(self.window_id);
}
}
fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool, render_time_ns: Option<u64>) {
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());
}
}
}
#[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.clone()));
}
#[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,
out_stats: &mut RendererStats,
out_dirty_rects: &mut ThinVec<DeviceIntRect>,
) -> bool {
match renderer.render(DeviceIntSize::new(width, height)) {
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_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,
}
}
}
type WrPipelineIdEpochs = ThinVec<WrPipelineIdAndEpoch>;
#[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<WrPipelineEpoch>,
/// 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<WrRemovedPipeline>,
}
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,
transaction: &mut Transaction,
epochs_being_rendered: &WrPipelineIdEpochs,
);
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<DocumentId>, 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<DocumentId>) {
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,
epochs_being_rendered: &FastHashMap<PipelineId, Epoch>,
) -> Vec<FrameMsg> {
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,
&mut transaction,
&epochs_being_rendered.iter().map(WrPipelineIdAndEpoch::from).collect(),
)
};
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<rayon::ThreadPool>);
#[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());
gecko_profiler_register_thread(CString::new(name).unwrap().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,
resource_override_path,
use_optimized_shaders,
upload_method,
cached_programs,
false,
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_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);
}
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 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) }
}
}
/// 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<MappedTileInfo> {
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);
}
}
}
// Call MakeCurrent before this.
#[no_mangle]
pub extern "C" fn wr_window_new(
window_id: WrWindowId,
window_width: i32,
window_height: i32,
support_low_priority_transactions: bool,
support_low_priority_threadpool: bool,
allow_texture_swizzling: bool,
enable_picture_caching: 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,
max_update_rects: usize,
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,
) -> 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.clone()) as Rc<dyn gl::Gl>, 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 {
let wr_compositor = if compositor != ptr::null_mut() {
Some(WrCompositor(compositor))
} else {
None
};
CompositorConfig::Native {
max_update_rects: 1,
compositor: Box::new(SwCompositor::new(sw_gl.unwrap(), native_gl, wr_compositor)),
}
} else if compositor != ptr::null_mut() {
CompositorConfig::Native {
max_update_rects,
compositor: Box::new(WrCompositor(compositor)),
}
} else {
CompositorConfig::Draw {
max_partial_present_rects,
draw_previous_partial_present_regions,
}
};
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.clone(),
))),
workers: Some(workers.clone()),
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,
enable_picture_caching,
allow_pixel_local_storage_support: false,
// 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: !software && surface_origin_is_top_left,
compositor_config,
enable_gpu_markers,
panic_on_gl_error,
..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: window_id });
let (renderer, sender) = match Renderer::new(gl, notifier, opts, shaders, window_size) {
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();
}
let layer = 0;
*out_handle = Box::into_raw(Box::new(DocumentHandle::new(
sender.create_api_by_client(next_namespace_id()),
None,
window_size,
layer,
document_id,
)));
*out_renderer = Box::into_raw(Box::new(renderer));
return 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<fn()>
size_of_op: unsafe extern "C" fn(ptr: *const c_void) -> usize,
enclosing_size_of_op: Option<unsafe extern "C" fn(ptr: *const c_void) -> 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) {
txn.generate_frame();
}
#[no_mangle]
pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) {
txn.invalidate_rendered_frame();
}
fn wr_animation_properties_into_vec<T>(
animation_array: *const WrAnimationPropertyValue<T>,
array_count: usize,
vec: &mut Vec<PropertyValue<T>>,
) 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(