/* 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 api::{BlobImageHandler, ColorF, IdNamespace, DocumentId, CrashAnnotator}; use api::{VoidPtrToSizeFn, FontRenderMode, ImageFormat}; use api::{RenderNotifier, ImageBufferKind}; use api::units::*; use api::channel::unbounded_channel; pub use api::DebugFlags; use crate::render_api::{RenderApiSender, FrameMsg}; use crate::composite::{CompositorKind, CompositorConfig}; use crate::device::{ UploadMethod, UploadPBOPool, VertexUsageHint, Device, ProgramCache, TextureFilter }; use crate::frame_builder::FrameBuilderConfig; use crate::glyph_cache::GlyphCache; use glyph_rasterizer::{GlyphRasterizer, SharedFontResources}; use crate::gpu_types::PrimitiveInstanceData; use crate::internal_types::{FastHashMap, FastHashSet, FrameId}; use crate::picture; use crate::profiler::{self, Profiler, TransactionProfile}; use crate::device::query::{GpuProfiler, GpuDebugMethod}; use crate::render_backend::RenderBackend; use crate::resource_cache::ResourceCache; use crate::scene_builder_thread::{SceneBuilderThread, SceneBuilderThreadChannels, LowPrioritySceneBuilderThread}; use crate::texture_cache::{TextureCache, TextureCacheConfig}; use crate::picture_textures::PictureTextures; use crate::renderer::{ debug, gpu_cache, vertex, gl, Renderer, DebugOverlayState, BufferDamageTracker, PipelineInfo, TextureResolver, RendererError, ShaderPrecacheFlags, VERTEX_DATA_TEXTURE_COUNT, upload::UploadTexturePool, shade::{Shaders, SharedShaders}, }; use std::{ mem, thread, cell::RefCell, collections::VecDeque, rc::Rc, sync::{Arc, atomic::{AtomicBool, Ordering}}, num::NonZeroUsize, path::PathBuf, }; use tracy_rs::register_thread_with_profiler; use rayon::{ThreadPool, ThreadPoolBuilder}; use malloc_size_of::MallocSizeOfOps; /// Use this hint for all vertex data re-initialization. This allows /// the driver to better re-use RBOs internally. pub const ONE_TIME_USAGE_HINT: VertexUsageHint = VertexUsageHint::Stream; /// Is only false if no WR instances have ever been created. static HAS_BEEN_INITIALIZED: AtomicBool = AtomicBool::new(false); /// Returns true if a WR instance has ever been initialized in this process. pub fn wr_has_been_initialized() -> bool { HAS_BEEN_INITIALIZED.load(Ordering::SeqCst) } /// Allows callers to hook in at certain points of the async scene build. These /// functions are all called from the scene builder thread. pub trait SceneBuilderHooks { /// This is called exactly once, when the scene builder thread is started /// and before it processes anything. fn register(&self); /// This is called before each scene build starts. fn pre_scene_build(&self); /// This is called before each scene swap occurs. fn pre_scene_swap(&self); /// This is called after each scene swap occurs. The PipelineInfo contains /// the updated epochs and pipelines removed in the new scene compared to /// the old scene. fn post_scene_swap(&self, document_id: &Vec, info: PipelineInfo); /// This is called after a resource update operation on the scene builder /// thread, in the case where resource updates were applied without a scene /// build. fn post_resource_update(&self, document_ids: &Vec); /// This is called after a scene build completes without any changes being /// made. We guarantee that each pre_scene_build call will be matched with /// exactly one of post_scene_swap, post_resource_update or /// post_empty_scene_build. fn post_empty_scene_build(&self); /// This is a generic callback which provides an opportunity to run code /// on the scene builder thread. This is called as part of the main message /// loop of the scene builder thread, but outside of any specific message /// handler. fn poke(&self); /// This is called exactly once, when the scene builder thread is about to /// terminate. fn deregister(&self); } /// Allows callers to hook into the main render_backend loop and provide /// additional frame ops for generate_frame transactions. These functions /// are all called from the render backend thread. pub trait AsyncPropertySampler { /// This is called exactly once, when the render backend thread is started /// and before it processes anything. fn register(&self); /// This is called for each transaction with the generate_frame flag set /// (i.e. that will trigger a render). The list of frame messages returned /// are processed as though they were part of the original transaction. fn sample(&self, document_id: DocumentId, generated_frame_id: Option) -> Vec; /// This is called exactly once, when the render backend thread is about to /// terminate. fn deregister(&self); } pub struct WebRenderOptions { pub resource_override_path: Option, /// Whether to use shaders that have been optimized at build time. pub use_optimized_shaders: bool, pub enable_aa: bool, pub enable_dithering: bool, pub max_recorded_profiles: usize, pub precache_flags: ShaderPrecacheFlags, /// Enable sub-pixel anti-aliasing if a fast implementation is available. pub enable_subpixel_aa: bool, pub clear_color: ColorF, pub enable_clear_scissor: Option, pub max_internal_texture_size: Option, pub image_tiling_threshold: i32, pub upload_method: UploadMethod, /// The default size in bytes for PBOs used to upload texture data. pub upload_pbo_default_size: usize, pub batched_upload_threshold: i32, pub workers: Option>, pub enable_multithreading: bool, pub blob_image_handler: Option>, pub crash_annotator: Option>, pub size_of_op: Option, pub enclosing_size_of_op: Option, pub cached_programs: Option>, pub debug_flags: DebugFlags, pub renderer_id: Option, pub scene_builder_hooks: Option>, pub sampler: Option>, pub support_low_priority_transactions: bool, pub namespace_alloc_by_client: bool, /// If namespaces are allocated by the client, then the namespace for fonts /// must also be allocated by the client to avoid namespace collisions with /// the backend. pub shared_font_namespace: Option, pub testing: bool, /// Set to true if this GPU supports hardware fast clears as a performance /// optimization. Likely requires benchmarking on various GPUs to see if /// it is a performance win. The default is false, which tends to be best /// performance on lower end / integrated GPUs. pub gpu_supports_fast_clears: bool, pub allow_dual_source_blending: bool, pub allow_advanced_blend_equation: bool, /// If true, allow textures to be initialized with glTexStorage. /// This affects VRAM consumption and data upload paths. pub allow_texture_storage_support: bool, /// If true, we allow the data uploaded in a different format from the /// one expected by the driver, pretending the format is matching, and /// swizzling the components on all the shader sampling. pub allow_texture_swizzling: bool, /// Use `ps_clear` shader with batched quad rendering to clear the rects /// in texture cache and picture cache tasks. /// This helps to work around some Intel drivers /// that incorrectly synchronize clears to following draws. pub clear_caches_with_quads: bool, /// Output the source of the shader with the given name. pub dump_shader_source: Option, pub surface_origin_is_top_left: bool, /// The configuration options defining how WR composites the final scene. pub compositor_config: CompositorConfig, pub enable_gpu_markers: bool, /// If true, panic whenever a GL error occurs. This has a significant /// performance impact, so only use when debugging specific problems! pub panic_on_gl_error: bool, pub picture_tile_size: Option, pub texture_cache_config: TextureCacheConfig, /// If true, we'll use instanced vertex attributes. Each instace is a quad. /// If false, we'll duplicate the instance attributes per vertex and issue /// regular indexed draws instead. pub enable_instancing: bool, /// If true, we'll reject contexts backed by a software rasterizer, except /// Software WebRender. pub reject_software_rasterizer: bool, /// If enabled, pinch-zoom will apply the zoom factor during compositing /// of picture cache tiles. This is higher performance (tiles are not /// re-rasterized during zoom) but lower quality result. For most display /// items, if the zoom factor is relatively small, bilinear filtering should /// make the result look quite close to the high-quality zoom, except for glyphs. pub low_quality_pinch_zoom: bool, pub max_shared_surface_size: i32, } impl WebRenderOptions { /// Number of batches to look back in history for adding the current /// transparent instance into. const BATCH_LOOKBACK_COUNT: usize = 10; /// Since we are re-initializing the instance buffers on every draw call, /// the driver has to internally manage PBOs in flight. /// It's typically done by bucketing up to a specific limit, and then /// just individually managing the largest buffers. /// Having a limit here allows the drivers to more easily manage /// the PBOs for us. const MAX_INSTANCE_BUFFER_SIZE: usize = 0x20000; // actual threshold in macOS GL drivers } impl Default for WebRenderOptions { fn default() -> Self { WebRenderOptions { resource_override_path: None, use_optimized_shaders: false, enable_aa: true, enable_dithering: false, debug_flags: DebugFlags::empty(), max_recorded_profiles: 0, precache_flags: ShaderPrecacheFlags::empty(), enable_subpixel_aa: false, clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0), enable_clear_scissor: None, max_internal_texture_size: None, image_tiling_threshold: 4096, // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL, // but we are unable to make this decision here, so picking the reasonable medium. upload_method: UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT), upload_pbo_default_size: 512 * 512 * 4, batched_upload_threshold: 512 * 512, workers: None, enable_multithreading: true, blob_image_handler: None, crash_annotator: None, size_of_op: None, enclosing_size_of_op: None, renderer_id: None, cached_programs: None, scene_builder_hooks: None, sampler: None, support_low_priority_transactions: false, namespace_alloc_by_client: false, shared_font_namespace: None, testing: false, gpu_supports_fast_clears: false, allow_dual_source_blending: true, allow_advanced_blend_equation: false, allow_texture_storage_support: true, allow_texture_swizzling: true, clear_caches_with_quads: true, dump_shader_source: None, surface_origin_is_top_left: false, compositor_config: CompositorConfig::default(), enable_gpu_markers: true, panic_on_gl_error: false, picture_tile_size: None, texture_cache_config: TextureCacheConfig::DEFAULT, // Disabling instancing means more vertex data to upload and potentially // process by the vertex shaders. enable_instancing: true, reject_software_rasterizer: false, low_quality_pinch_zoom: false, max_shared_surface_size: 2048, } } } /// Initializes WebRender and creates a `Renderer` and `RenderApiSender`. /// /// # Examples /// Initializes a `Renderer` with some reasonable values. For more information see /// [`WebRenderOptions`][WebRenderOptions]. /// /// ```rust,ignore /// # use webrender::renderer::Renderer; /// # use std::path::PathBuf; /// let opts = webrender::WebRenderOptions { /// device_pixel_ratio: 1.0, /// resource_override_path: None, /// enable_aa: false, /// }; /// let (renderer, sender) = Renderer::new(opts); /// ``` /// [WebRenderOptions]: struct.WebRenderOptions.html pub fn create_webrender_instance( gl: Rc, notifier: Box, mut options: WebRenderOptions, shaders: Option<&SharedShaders>, ) -> Result<(Renderer, RenderApiSender), RendererError> { if !wr_has_been_initialized() { // If the profiler feature is enabled, try to load the profiler shared library // if the path was provided. #[cfg(feature = "profiler")] unsafe { if let Ok(ref tracy_path) = std::env::var("WR_TRACY_PATH") { let ok = tracy_rs::load(tracy_path); info!("Load tracy from {} -> {}", tracy_path, ok); } } register_thread_with_profiler("Compositor".to_owned()); } HAS_BEEN_INITIALIZED.store(true, Ordering::SeqCst); let (api_tx, api_rx) = unbounded_channel(); let (result_tx, result_rx) = unbounded_channel(); let gl_type = gl.get_type(); let mut device = Device::new( gl, options.crash_annotator.clone(), options.resource_override_path.clone(), options.use_optimized_shaders, options.upload_method.clone(), options.batched_upload_threshold, options.cached_programs.take(), options.allow_texture_storage_support, options.allow_texture_swizzling, options.dump_shader_source.take(), options.surface_origin_is_top_left, options.panic_on_gl_error, ); let color_cache_formats = device.preferred_color_formats(); let swizzle_settings = device.swizzle_settings(); let use_dual_source_blending = device.get_capabilities().supports_dual_source_blending && options.allow_dual_source_blending; let ext_blend_equation_advanced = options.allow_advanced_blend_equation && device.get_capabilities().supports_advanced_blend_equation; let ext_blend_equation_advanced_coherent = device.supports_extension("GL_KHR_blend_equation_advanced_coherent"); let enable_clear_scissor = options .enable_clear_scissor .unwrap_or(device.get_capabilities().prefers_clear_scissor); // 2048 is the minimum that the texture cache can work with. const MIN_TEXTURE_SIZE: i32 = 2048; let mut max_internal_texture_size = device.max_texture_size(); if max_internal_texture_size < MIN_TEXTURE_SIZE { // Broken GL contexts can return a max texture size of zero (See #1260). // Better to gracefully fail now than panic as soon as a texture is allocated. error!( "Device reporting insufficient max texture size ({})", max_internal_texture_size ); return Err(RendererError::MaxTextureSize); } if let Some(internal_limit) = options.max_internal_texture_size { assert!(internal_limit >= MIN_TEXTURE_SIZE); max_internal_texture_size = max_internal_texture_size.min(internal_limit); } if options.reject_software_rasterizer { let renderer_name_lc = device.get_capabilities().renderer_name.to_lowercase(); if renderer_name_lc.contains("llvmpipe") || renderer_name_lc.contains("softpipe") || renderer_name_lc.contains("software rasterizer") { return Err(RendererError::SoftwareRasterizer); } } let image_tiling_threshold = options.image_tiling_threshold .min(max_internal_texture_size); device.begin_frame(); let shaders = match shaders { Some(shaders) => Rc::clone(shaders), None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)), }; let dither_matrix_texture = if options.enable_dithering { let dither_matrix: [u8; 64] = [ 0, 48, 12, 60, 3, 51, 15, 63, 32, 16, 44, 28, 35, 19, 47, 31, 8, 56, 4, 52, 11, 59, 7, 55, 40, 24, 36, 20, 43, 27, 39, 23, 2, 50, 14, 62, 1, 49, 13, 61, 34, 18, 46, 30, 33, 17, 45, 29, 10, 58, 6, 54, 9, 57, 5, 53, 42, 26, 38, 22, 41, 25, 37, 21, ]; let texture = device.create_texture( ImageBufferKind::Texture2D, ImageFormat::R8, 8, 8, TextureFilter::Nearest, None, ); device.upload_texture_immediate(&texture, &dither_matrix); Some(texture) } else { None }; let max_primitive_instance_count = WebRenderOptions::MAX_INSTANCE_BUFFER_SIZE / mem::size_of::(); let vaos = vertex::RendererVAOs::new( &mut device, if options.enable_instancing { None } else { NonZeroUsize::new(max_primitive_instance_count) }, ); let texture_upload_pbo_pool = UploadPBOPool::new(&mut device, options.upload_pbo_default_size); let staging_texture_pool = UploadTexturePool::new(); let texture_resolver = TextureResolver::new(&mut device); let mut vertex_data_textures = Vec::new(); for _ in 0 .. VERTEX_DATA_TEXTURE_COUNT { vertex_data_textures.push(vertex::VertexDataTextures::new()); } // On some (mostly older, integrated) GPUs, the normal GPU texture cache update path // doesn't work well when running on ANGLE, causing CPU stalls inside D3D and/or the // GPU driver. See https://bugzilla.mozilla.org/show_bug.cgi?id=1576637 for much // more detail. To reduce the number of code paths we have active that require testing, // we will enable the GPU cache scatter update path on all devices running with ANGLE. // We want a better solution long-term, but for now this is a significant performance // improvement on HD4600 era GPUs, and shouldn't hurt performance in a noticeable // way on other systems running under ANGLE. let is_software = device.get_capabilities().renderer_name.starts_with("Software"); // On other GL platforms, like macOS or Android, creating many PBOs is very inefficient. // This is what happens in GPU cache updates in PBO path. Instead, we switch everything // except software GL to use the GPU scattered updates. let supports_scatter = device.get_capabilities().supports_color_buffer_float; let gpu_cache_texture = gpu_cache::GpuCacheTexture::new( &mut device, supports_scatter && !is_software, )?; device.end_frame(); let backend_notifier = notifier.clone(); let clear_alpha_targets_with_quads = !device.get_capabilities().supports_alpha_target_clears; let prefer_subpixel_aa = options.enable_subpixel_aa && use_dual_source_blending; let default_font_render_mode = match (options.enable_aa, prefer_subpixel_aa) { (true, true) => FontRenderMode::Subpixel, (true, false) => FontRenderMode::Alpha, (false, _) => FontRenderMode::Mono, }; let compositor_kind = match options.compositor_config { CompositorConfig::Draw { max_partial_present_rects, draw_previous_partial_present_regions, .. } => { CompositorKind::Draw { max_partial_present_rects, draw_previous_partial_present_regions } } CompositorConfig::Native { ref compositor } => { let capabilities = compositor.get_capabilities(&mut device); CompositorKind::Native { capabilities, } } }; let config = FrameBuilderConfig { default_font_render_mode, dual_source_blending_is_supported: use_dual_source_blending, testing: options.testing, gpu_supports_fast_clears: options.gpu_supports_fast_clears, gpu_supports_advanced_blend: ext_blend_equation_advanced, advanced_blend_is_coherent: ext_blend_equation_advanced_coherent, gpu_supports_render_target_partial_update: device.get_capabilities().supports_render_target_partial_update, external_images_require_copy: !device.get_capabilities().supports_image_external_essl3, batch_lookback_count: WebRenderOptions::BATCH_LOOKBACK_COUNT, background_color: Some(options.clear_color), compositor_kind, tile_size_override: None, max_surface_override: None, max_depth_ids: device.max_depth_ids(), max_target_size: max_internal_texture_size, force_invalidation: false, is_software, low_quality_pinch_zoom: options.low_quality_pinch_zoom, max_shared_surface_size: options.max_shared_surface_size, }; info!("WR {:?}", config); let debug_flags = options.debug_flags; let size_of_op = options.size_of_op; let enclosing_size_of_op = options.enclosing_size_of_op; let make_size_of_ops = move || size_of_op.map(|o| MallocSizeOfOps::new(o, enclosing_size_of_op)); let workers = options .workers .take() .unwrap_or_else(|| { let worker = ThreadPoolBuilder::new() .thread_name(|idx|{ format!("WRWorker#{}", idx) }) .start_handler(move |idx| { register_thread_with_profiler(format!("WRWorker#{}", idx)); profiler::register_thread(&format!("WRWorker#{}", idx)); }) .exit_handler(move |_idx| { profiler::unregister_thread(); }) .build(); Arc::new(worker.unwrap()) }); let sampler = options.sampler; let namespace_alloc_by_client = options.namespace_alloc_by_client; // Ensure shared font keys exist within their own unique namespace so // that they don't accidentally collide across Renderer instances. let font_namespace = if namespace_alloc_by_client { options.shared_font_namespace.expect("Shared font namespace must be allocated by client") } else { RenderBackend::next_namespace_id() }; let fonts = SharedFontResources::new(font_namespace); let blob_image_handler = options.blob_image_handler.take(); let scene_builder_hooks = options.scene_builder_hooks; let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0)); let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0)); let lp_scene_thread_name = format!("WRSceneBuilderLP#{}", options.renderer_id.unwrap_or(0)); let glyph_rasterizer = GlyphRasterizer::new(workers, device.get_capabilities().supports_r8_texture_upload); let (scene_builder_channels, scene_tx) = SceneBuilderThreadChannels::new(api_tx.clone()); let sb_fonts = fonts.clone(); thread::Builder::new().name(scene_thread_name.clone()).spawn(move || { register_thread_with_profiler(scene_thread_name.clone()); profiler::register_thread(&scene_thread_name); let mut scene_builder = SceneBuilderThread::new( config, sb_fonts, make_size_of_ops(), scene_builder_hooks, scene_builder_channels, ); scene_builder.run(); profiler::unregister_thread(); })?; let low_priority_scene_tx = if options.support_low_priority_transactions { let (low_priority_scene_tx, low_priority_scene_rx) = unbounded_channel(); let lp_builder = LowPrioritySceneBuilderThread { rx: low_priority_scene_rx, tx: scene_tx.clone(), }; thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || { register_thread_with_profiler(lp_scene_thread_name.clone()); profiler::register_thread(&lp_scene_thread_name); let mut scene_builder = lp_builder; scene_builder.run(); profiler::unregister_thread(); })?; low_priority_scene_tx } else { scene_tx.clone() }; let rb_blob_handler = blob_image_handler .as_ref() .map(|handler| handler.create_similar()); let texture_cache_config = options.texture_cache_config.clone(); let mut picture_tile_size = options.picture_tile_size.unwrap_or(picture::TILE_SIZE_DEFAULT); // Clamp the picture tile size to reasonable values. picture_tile_size.width = picture_tile_size.width.max(128).min(4096); picture_tile_size.height = picture_tile_size.height.max(128).min(4096); let picture_texture_filter = if options.low_quality_pinch_zoom { TextureFilter::Linear } else { TextureFilter::Nearest }; let rb_scene_tx = scene_tx.clone(); let rb_fonts = fonts.clone(); let enable_multithreading = options.enable_multithreading; thread::Builder::new().name(rb_thread_name.clone()).spawn(move || { register_thread_with_profiler(rb_thread_name.clone()); profiler::register_thread(&rb_thread_name); let texture_cache = TextureCache::new( max_internal_texture_size, image_tiling_threshold, color_cache_formats, swizzle_settings, &texture_cache_config, ); let picture_textures = PictureTextures::new( picture_tile_size, picture_texture_filter, ); let glyph_cache = GlyphCache::new(); let mut resource_cache = ResourceCache::new( texture_cache, picture_textures, glyph_rasterizer, glyph_cache, rb_fonts, rb_blob_handler, ); resource_cache.enable_multithreading(enable_multithreading); let mut backend = RenderBackend::new( api_rx, result_tx, rb_scene_tx, resource_cache, backend_notifier, config, sampler, make_size_of_ops(), debug_flags, namespace_alloc_by_client, ); backend.run(); profiler::unregister_thread(); })?; let debug_method = if !options.enable_gpu_markers { // The GPU markers are disabled. GpuDebugMethod::None } else if device.supports_extension("GL_KHR_debug") { GpuDebugMethod::KHR } else if device.supports_extension("GL_EXT_debug_marker") { GpuDebugMethod::MarkerEXT } else { warn!("asking to enable_gpu_markers but no supporting extension was found"); GpuDebugMethod::None }; info!("using {:?}", debug_method); let gpu_profiler = GpuProfiler::new(Rc::clone(device.rc_gl()), debug_method); #[cfg(feature = "capture")] let read_fbo = device.create_fbo(); let mut renderer = Renderer { result_rx, api_tx: api_tx.clone(), device, active_documents: FastHashMap::default(), pending_texture_updates: Vec::new(), pending_texture_cache_updates: false, pending_native_surface_updates: Vec::new(), pending_gpu_cache_updates: Vec::new(), pending_gpu_cache_clear: false, pending_shader_updates: Vec::new(), shaders, debug: debug::LazyInitializedDebugRenderer::new(), debug_flags: DebugFlags::empty(), profile: TransactionProfile::new(), frame_counter: 0, resource_upload_time: 0.0, gpu_cache_upload_time: 0.0, profiler: Profiler::new(), max_recorded_profiles: options.max_recorded_profiles, clear_color: options.clear_color, enable_clear_scissor, enable_advanced_blend_barriers: !ext_blend_equation_advanced_coherent, clear_caches_with_quads: options.clear_caches_with_quads, clear_alpha_targets_with_quads, last_time: 0, gpu_profiler, vaos, vertex_data_textures, current_vertex_data_textures: 0, pipeline_info: PipelineInfo::default(), dither_matrix_texture, external_image_handler: None, size_of_ops: make_size_of_ops(), cpu_profiles: VecDeque::new(), gpu_profiles: VecDeque::new(), gpu_cache_texture, gpu_cache_debug_chunks: Vec::new(), gpu_cache_frame_id: FrameId::INVALID, gpu_cache_overflow: false, texture_upload_pbo_pool, staging_texture_pool, texture_resolver, renderer_errors: Vec::new(), async_frame_recorder: None, async_screenshots: None, #[cfg(feature = "capture")] read_fbo, #[cfg(feature = "replay")] owned_external_images: FastHashMap::default(), notifications: Vec::new(), device_size: None, zoom_debug_texture: None, cursor_position: DeviceIntPoint::zero(), shared_texture_cache_cleared: false, documents_seen: FastHashSet::default(), force_redraw: true, compositor_config: options.compositor_config, current_compositor_kind: compositor_kind, allocated_native_surfaces: FastHashSet::default(), debug_overlay_state: DebugOverlayState::new(), buffer_damage_tracker: BufferDamageTracker::default(), max_primitive_instance_count, enable_instancing: options.enable_instancing, consecutive_oom_frames: 0, target_frame_publish_id: None, pending_result_msg: None, }; // We initially set the flags to default and then now call set_debug_flags // to ensure any potential transition when enabling a flag is run. renderer.set_debug_flags(debug_flags); let sender = RenderApiSender::new( api_tx, scene_tx, low_priority_scene_tx, blob_image_handler, fonts, ); Ok((renderer, sender)) }