diff --git a/gfx/webrender/Cargo.toml b/gfx/webrender/Cargo.toml index c3fa393dddf6..8cddd5bd5131 100644 --- a/gfx/webrender/Cargo.toml +++ b/gfx/webrender/Cargo.toml @@ -35,8 +35,8 @@ smallvec = "0.6" ws = { optional = true, version = "0.7.3" } serde_json = { optional = true, version = "1.0" } serde = { optional = true, version = "1.0", features = ["serde_derive"] } -image = { optional = true, version = "0.17" } -base64 = { optional = true, version = "0.3.0" } +image = { optional = true, version = "0.18" } +base64 = { optional = true, version = "0.6" } ron = { optional = true, version = "0.1.7" } [dev-dependencies] diff --git a/gfx/webrender/res/brush_radial_gradient.glsl b/gfx/webrender/res/brush_radial_gradient.glsl index 2cc8f3a27680..96fcb6375636 100644 --- a/gfx/webrender/res/brush_radial_gradient.glsl +++ b/gfx/webrender/res/brush_radial_gradient.glsl @@ -9,8 +9,7 @@ flat varying int vGradientAddress; flat varying float vGradientRepeat; -flat varying vec2 vStartCenter; -flat varying vec2 vEndCenter; +flat varying vec2 vCenter; flat varying float vStartRadius; flat varying float vEndRadius; @@ -23,8 +22,8 @@ varying vec2 vLocalPos; #ifdef WR_VERTEX_SHADER struct RadialGradient { - vec4 start_end_center; - vec4 start_end_radius_ratio_xy_extend_mode; + vec4 center_start_end_radius; + vec4 ratio_xy_extend_mode; }; RadialGradient fetch_radial_gradient(int address) { @@ -43,23 +42,20 @@ void brush_vs( vPos = vi.local_pos - local_rect.p0; - vStartCenter = gradient.start_end_center.xy; - vEndCenter = gradient.start_end_center.zw; - - vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x; - vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y; + vCenter = gradient.center_start_end_radius.xy; + vStartRadius = gradient.center_start_end_radius.z; + vEndRadius = gradient.center_start_end_radius.w; // Transform all coordinates by the y scale so the // fragment shader can work with circles - float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z; + float ratio_xy = gradient.ratio_xy_extend_mode.x; vPos.y *= ratio_xy; - vStartCenter.y *= ratio_xy; - vEndCenter.y *= ratio_xy; + vCenter.y *= ratio_xy; vGradientAddress = user_data.x; // Whether to repeat the gradient instead of clamping. - vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) != EXTEND_MODE_CLAMP); + vGradientRepeat = float(int(gradient.ratio_xy_extend_mode.y) != EXTEND_MODE_CLAMP); #ifdef WR_FEATURE_ALPHA_PASS vLocalPos = vi.local_pos; @@ -69,14 +65,13 @@ void brush_vs( #ifdef WR_FRAGMENT_SHADER vec4 brush_fs() { - vec2 cd = vEndCenter - vStartCenter; - vec2 pd = vPos - vStartCenter; + vec2 pd = vPos - vCenter; float rd = vEndRadius - vStartRadius; - // Solve for t in length(t * cd - pd) = vStartRadius + t * rd + // Solve for t in length(t - pd) = vStartRadius + t * rd // using a quadratic equation in form of At^2 - 2Bt + C = 0 - float A = dot(cd, cd) - rd * rd; - float B = dot(pd, cd) + vStartRadius * rd; + float A = -(rd * rd); + float B = vStartRadius * rd; float C = dot(pd, pd) - vStartRadius * vStartRadius; float offset; diff --git a/gfx/webrender/src/device.rs b/gfx/webrender/src/device.rs index d3cede338add..af2d0cae294b 100644 --- a/gfx/webrender/src/device.rs +++ b/gfx/webrender/src/device.rs @@ -931,22 +931,6 @@ impl Device { } } - //TODO: remove once the Angle workaround is no longer needed - pub fn reset_angle_sampler_metadata(&mut self, texture: &Texture) { - self.bind_texture(DEFAULT_TEXTURE, texture); - self.gl.tex_parameter_f( - texture.target, - gl::TEXTURE_BASE_LEVEL, - 1.0 as _, - ); - self.gl.draw_arrays(gl::TRIANGLES, 0, 1); // dummy draw - self.gl.tex_parameter_f( - texture.target, - gl::TEXTURE_BASE_LEVEL, - 0.0 as _, // assumes 0.0 is the normal value for this texture - ); - } - pub fn create_texture( &mut self, target: TextureTarget, diff --git a/gfx/webrender/src/display_list_flattener.rs b/gfx/webrender/src/display_list_flattener.rs index f6a93bfe4cbd..1c9b9ee0209b 100644 --- a/gfx/webrender/src/display_list_flattener.rs +++ b/gfx/webrender/src/display_list_flattener.rs @@ -738,11 +738,10 @@ impl<'a> DisplayListFlattener<'a> { self.add_radial_gradient( clip_and_scroll, &prim_info, - info.gradient.start_center, + info.gradient.center, info.gradient.start_radius, - info.gradient.end_center, info.gradient.end_radius, - info.gradient.ratio_xy, + info.gradient.radius.width / info.gradient.radius.height, item.gradient_stops(), info.gradient.extend_mode, info.tile_size, @@ -1824,11 +1823,10 @@ impl<'a> DisplayListFlattener<'a> { self.add_radial_gradient( clip_and_scroll, &info, - border.gradient.start_center - segment_rel, + border.gradient.center - segment_rel, border.gradient.start_radius, - border.gradient.end_center - segment_rel, border.gradient.end_radius, - border.gradient.ratio_xy, + border.gradient.radius.width / border.gradient.radius.height, gradient_stops, border.gradient.extend_mode, segment.size, @@ -1938,9 +1936,8 @@ impl<'a> DisplayListFlattener<'a> { &mut self, clip_and_scroll: ScrollNodeAndClipChain, info: &LayerPrimitiveInfo, - start_center: LayerPoint, + center: LayerPoint, start_radius: f32, - end_center: LayerPoint, end_radius: f32, ratio_xy: f32, stops: ItemRange, @@ -1951,8 +1948,7 @@ impl<'a> DisplayListFlattener<'a> { BrushKind::RadialGradient { stops_range: stops, extend_mode, - start_center, - end_center, + center, start_radius, end_radius, ratio_xy, @@ -1973,9 +1969,8 @@ impl<'a> DisplayListFlattener<'a> { &mut self, clip_and_scroll: ScrollNodeAndClipChain, info: &LayerPrimitiveInfo, - start_center: LayerPoint, + center: LayerPoint, start_radius: f32, - end_center: LayerPoint, end_radius: f32, ratio_xy: f32, stops: ItemRange, @@ -1996,9 +1991,8 @@ impl<'a> DisplayListFlattener<'a> { self.add_radial_gradient_impl( clip_and_scroll, info, - start_center, + center, start_radius, - end_center, end_radius, ratio_xy, stops, @@ -2010,9 +2004,8 @@ impl<'a> DisplayListFlattener<'a> { self.add_radial_gradient_impl( clip_and_scroll, &prim_info, - start_center, + center, start_radius, - end_center, end_radius, ratio_xy, stops, diff --git a/gfx/webrender/src/glyph_cache.rs b/gfx/webrender/src/glyph_cache.rs index 5b544f582674..3a9d4a6ba2b2 100644 --- a/gfx/webrender/src/glyph_cache.rs +++ b/gfx/webrender/src/glyph_cache.rs @@ -2,38 +2,43 @@ * 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::{DevicePoint, DeviceUintSize, GlyphKey}; +use api::GlyphKey; use glyph_rasterizer::{FontInstance, GlyphFormat}; use internal_types::FastHashMap; use resource_cache::ResourceClassCache; -use std::sync::Arc; -use texture_cache::TextureCacheHandle; +use texture_cache::{TextureCache, TextureCacheHandle, EvictionNotice}; #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct GenericCachedGlyphInfo { +pub struct CachedGlyphInfo { pub texture_cache_handle: TextureCacheHandle, - pub glyph_bytes: D, - pub size: DeviceUintSize, - pub offset: DevicePoint, - pub scale: f32, pub format: GlyphFormat, } -pub type CachedGlyphInfo = GenericCachedGlyphInfo>>; -pub type GlyphKeyCache = ResourceClassCache>; +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum GlyphCacheEntry { + // A glyph that has been successfully rasterized. + Cached(CachedGlyphInfo), + // A glyph that should not be rasterized (i.e. a space). + Blank, + // A glyph that has been submitted to the font backend for rasterization, + // but is still pending a result. + Pending, +} -#[cfg(any(feature = "capture", feature = "replay"))] -pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo; -#[cfg(any(feature = "capture", feature = "replay"))] -pub type PlainGlyphKeyCache = ResourceClassCache>; -#[cfg(feature = "capture")] -pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>; -#[cfg(feature = "replay")] -pub type PlainGlyphCacheOwn = FastHashMap; +pub type GlyphKeyCache = ResourceClassCache; +impl GlyphKeyCache { + pub fn eviction_notice(&self) -> &EvictionNotice { + &self.user_data + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct GlyphCache { - pub glyph_key_caches: FastHashMap, + glyph_key_caches: FastHashMap, } impl GlyphCache { @@ -46,7 +51,7 @@ impl GlyphCache { pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache { self.glyph_key_caches .entry(font) - .or_insert(ResourceClassCache::new()) + .or_insert_with(|| GlyphKeyCache::new()) } pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache { @@ -78,4 +83,36 @@ impl GlyphCache { cache.clear(); } } + + // Clear out evicted entries from glyph key caches and, if possible, + // also remove entirely any subsequently empty glyph key caches. + fn clear_evicted(&mut self, texture_cache: &TextureCache) { + self.glyph_key_caches.retain(|_, cache| { + // Scan for any glyph key caches that have evictions. + if cache.eviction_notice().check() { + // If there are evictions, filter out any glyphs evicted from the + // texture cache from the glyph key cache. + let mut keep_cache = false; + cache.retain(|_, entry| { + let keep_glyph = match *entry { + GlyphCacheEntry::Cached(ref glyph) => + texture_cache.is_allocated(&glyph.texture_cache_handle), + GlyphCacheEntry::Pending => true, + // If the cache only has blank glyphs left, just get rid of it. + GlyphCacheEntry::Blank => false, + }; + keep_cache |= keep_glyph; + keep_glyph + }); + // Only keep the glyph key cache if it still has valid glyphs. + keep_cache + } else { + true + } + }); + } + + pub fn begin_frame(&mut self, texture_cache: &TextureCache) { + self.clear_evicted(texture_cache); + } } diff --git a/gfx/webrender/src/glyph_rasterizer.rs b/gfx/webrender/src/glyph_rasterizer.rs index 7bae9fca8434..292928946abe 100644 --- a/gfx/webrender/src/glyph_rasterizer.rs +++ b/gfx/webrender/src/glyph_rasterizer.rs @@ -4,16 +4,16 @@ #[cfg(test)] use api::{IdNamespace, LayoutPoint}; -use api::{ColorF, ColorU, DevicePoint, DeviceUintSize}; +use api::{ColorF, ColorU}; use api::{FontInstanceFlags, FontInstancePlatformOptions}; use api::{FontKey, FontRenderMode, FontTemplate, FontVariation}; use api::{GlyphDimensions, GlyphKey, SubpixelDirection}; use api::{ImageData, ImageDescriptor, ImageFormat, LayerToWorldTransform}; use app_units::Au; use device::TextureFilter; -use glyph_cache::{CachedGlyphInfo, GlyphCache}; +use glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo}; use gpu_cache::GpuCache; -use internal_types::{FastHashSet, ResourceCacheError}; +use internal_types::ResourceCacheError; use platform::font::FontContext; use profiler::TextureCacheProfileCounters; use rayon::ThreadPool; @@ -309,11 +309,11 @@ pub struct GlyphRasterizer { // because the glyph cache hash table is not updated // until the end of the frame when we wait for glyph requests // to be resolved. - pending_glyphs: FastHashSet, + pending_glyphs: usize, // Receives the rendered glyphs. - glyph_rx: Receiver>, - glyph_tx: Sender>, + glyph_rx: Receiver, + glyph_tx: Sender, // We defer removing fonts to the end of the frame so that: // - this work is done outside of the critical path, @@ -341,7 +341,7 @@ impl GlyphRasterizer { shared_context: Mutex::new(shared_context), workers: Arc::clone(&workers), }), - pending_glyphs: FastHashSet::default(), + pending_glyphs: 0, glyph_rx, glyph_tx, workers, @@ -391,7 +391,7 @@ impl GlyphRasterizer { .lock_shared_context() .has_font(&font.font_key) ); - let mut glyphs = Vec::new(); + let mut new_glyphs = Vec::new(); let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone()); @@ -399,58 +399,51 @@ impl GlyphRasterizer { for key in glyph_keys { match glyph_key_cache.entry(key.clone()) { Entry::Occupied(mut entry) => { - if let Ok(Some(ref mut glyph_info)) = *entry.get_mut() { - if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) { - // This case gets hit when we have already rasterized - // the glyph and stored it in CPU memory, but the glyph - // has been evicted from the texture cache. In which case - // we need to re-upload it to the GPU. - texture_cache.update( - &mut glyph_info.texture_cache_handle, - ImageDescriptor { - width: glyph_info.size.width, - height: glyph_info.size.height, - stride: None, - format: ImageFormat::BGRA8, - is_opaque: false, - offset: 0, - }, - TextureFilter::Linear, - Some(ImageData::Raw(glyph_info.glyph_bytes.clone())), - [glyph_info.offset.x, glyph_info.offset.y, glyph_info.scale], - None, - gpu_cache, - ); + let value = entry.into_mut(); + match *value { + GlyphCacheEntry::Cached(ref glyph) => { + // Skip the glyph if it is already has a valid texture cache handle. + if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) { + continue; + } } + // Otherwise, skip the entry if it is blank or pending. + GlyphCacheEntry::Blank | + GlyphCacheEntry::Pending => continue, } + // This case gets hit when we already rasterized the glyph, but the + // glyph has been evicted from the texture cache. Just force it to + // pending so it gets rematerialized. + *value = GlyphCacheEntry::Pending; } - Entry::Vacant(..) => { - let request = GlyphRequest::new(&font, key); - if self.pending_glyphs.insert(request.clone()) { - glyphs.push(request); - } + Entry::Vacant(entry) => { + // This is the first time we've seen the glyph, so mark it as pending. + entry.insert(GlyphCacheEntry::Pending); } } + + new_glyphs.push(key.clone()); } - if glyphs.is_empty() { + if new_glyphs.is_empty() { return; } + self.pending_glyphs += 1; let font_contexts = Arc::clone(&self.font_contexts); let glyph_tx = self.glyph_tx.clone(); // spawn an async task to get off of the render backend thread as early as // possible and in that task use rayon's fork join dispatch to rasterize the // glyphs in the thread pool. self.workers.spawn(move || { - let jobs = glyphs + let jobs = new_glyphs .par_iter() - .map(|request: &GlyphRequest| { + .map(|key: &GlyphKey| { profile_scope!("glyph-raster"); let mut context = font_contexts.lock_current_context(); let job = GlyphRasterJob { - request: request.clone(), - result: context.rasterize_glyph(&request.font, &request.key), + key: key.clone(), + result: context.rasterize_glyph(&font, key), }; // Sanity check. @@ -466,7 +459,7 @@ impl GlyphRasterizer { }) .collect(); - glyph_tx.send(jobs).unwrap(); + glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap(); }); } @@ -493,72 +486,60 @@ impl GlyphRasterizer { gpu_cache: &mut GpuCache, _texture_cache_profile: &mut TextureCacheProfileCounters, ) { - let mut rasterized_glyphs = Vec::with_capacity(self.pending_glyphs.len()); + // Pull rasterized glyphs from the queue and Update the caches. + while self.pending_glyphs > 0 { + self.pending_glyphs -= 1; - // Pull rasterized glyphs from the queue. - - while !self.pending_glyphs.is_empty() { // TODO: rather than blocking until all pending glyphs are available // we could try_recv and steal work from the thread pool to take advantage // of the fact that this thread is alive and we avoid the added latency // of blocking it. - let raster_jobs = self.glyph_rx + let GlyphRasterJobs { font, mut jobs } = self.glyph_rx .recv() .expect("BUG: Should be glyphs pending!"); - for job in raster_jobs { - debug_assert!(self.pending_glyphs.contains(&job.request)); - self.pending_glyphs.remove(&job.request); - rasterized_glyphs.push(job); - } - } + // Ensure that the glyphs are always processed in the same + // order for a given text run (since iterating a hash set doesn't + // guarantee order). This can show up as very small float inaccuacry + // differences in rasterizers due to the different coordinates + // that text runs get associated with by the texture cache allocator. + jobs.sort_by(|a, b| a.key.cmp(&b.key)); - // Ensure that the glyphs are always processed in the same - // order for a given text run (since iterating a hash set doesn't - // guarantee order). This can show up as very small float inaccuacry - // differences in rasterizers due to the different coordinates - // that text runs get associated with by the texture cache allocator. - rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request)); + let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font); - // Update the caches. - for job in rasterized_glyphs { - let glyph_info = job.result - .and_then(|glyph| if glyph.width > 0 && glyph.height > 0 { - assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0)); - let glyph_bytes = Arc::new(glyph.bytes); - let mut texture_cache_handle = TextureCacheHandle::new(); - texture_cache.request(&mut texture_cache_handle, gpu_cache); - texture_cache.update( - &mut texture_cache_handle, - ImageDescriptor { - width: glyph.width, - height: glyph.height, - stride: None, - format: ImageFormat::BGRA8, - is_opaque: false, - offset: 0, - }, - TextureFilter::Linear, - Some(ImageData::Raw(glyph_bytes.clone())), - [glyph.left, -glyph.top, glyph.scale], - None, - gpu_cache, - ); - Some(CachedGlyphInfo { - texture_cache_handle, - glyph_bytes, - size: DeviceUintSize::new(glyph.width, glyph.height), - offset: DevicePoint::new(glyph.left, -glyph.top), - scale: glyph.scale, - format: glyph.format, - }) - } else { - None + for GlyphRasterJob { key, result } in jobs { + let glyph_info = result.map_or(GlyphCacheEntry::Blank, |glyph| { + if glyph.width > 0 && glyph.height > 0 { + assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0)); + let mut texture_cache_handle = TextureCacheHandle::new(); + texture_cache.request(&mut texture_cache_handle, gpu_cache); + texture_cache.update( + &mut texture_cache_handle, + ImageDescriptor { + width: glyph.width, + height: glyph.height, + stride: None, + format: ImageFormat::BGRA8, + is_opaque: false, + offset: 0, + }, + TextureFilter::Linear, + Some(ImageData::Raw(Arc::new(glyph.bytes))), + [glyph.left, -glyph.top, glyph.scale], + None, + gpu_cache, + Some(glyph_key_cache.eviction_notice()), + ); + GlyphCacheEntry::Cached(CachedGlyphInfo { + texture_cache_handle, + format: glyph.format, + }) + } else { + GlyphCacheEntry::Blank + } }); - - let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(job.request.font); - - glyph_key_cache.insert(job.request.key, Ok(glyph_info)); + glyph_key_cache.insert(key, glyph_info); + } } // Now that we are done with the critical path (rendering the glyphs), @@ -583,7 +564,7 @@ impl GlyphRasterizer { #[cfg(feature = "replay")] pub fn reset(&mut self) { //TODO: any signals need to be sent to the workers? - self.pending_glyphs.clear(); + self.pending_glyphs = 0; self.fonts_to_remove.clear(); } } @@ -619,10 +600,15 @@ impl GlyphRequest { } struct GlyphRasterJob { - request: GlyphRequest, + key: GlyphKey, result: Option, } +struct GlyphRasterJobs { + font: FontInstance, + jobs: Vec, +} + #[test] fn rasterize_200_glyphs() { // This test loads a font from disc, the renders 4 requests containing diff --git a/gfx/webrender/src/prim_store.rs b/gfx/webrender/src/prim_store.rs index 68c3051aed18..7db90d47ddb3 100644 --- a/gfx/webrender/src/prim_store.rs +++ b/gfx/webrender/src/prim_store.rs @@ -222,8 +222,7 @@ pub enum BrushKind { gradient_index: CachedGradientIndex, stops_range: ItemRange, extend_mode: ExtendMode, - start_center: LayerPoint, - end_center: LayerPoint, + center: LayerPoint, start_radius: f32, end_radius: f32, ratio_xy: f32, @@ -379,18 +378,18 @@ impl BrushPrimitive { 0.0, ]); } - BrushKind::RadialGradient { start_center, end_center, start_radius, end_radius, ratio_xy, extend_mode, .. } => { - request.push([ - start_center.x, - start_center.y, - end_center.x, - end_center.y, - ]); + BrushKind::RadialGradient { center, start_radius, end_radius, ratio_xy, extend_mode, .. } => { request.push([ + center.x, + center.y, start_radius, end_radius, + ]); + request.push([ ratio_xy, pack_as_float(extend_mode as u32), + 0., + 0., ]); } } diff --git a/gfx/webrender/src/record.rs b/gfx/webrender/src/record.rs index 14c9c03f59ce..6b9a19d97b5d 100644 --- a/gfx/webrender/src/record.rs +++ b/gfx/webrender/src/record.rs @@ -2,7 +2,7 @@ * 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::{ApiMsg, FrameMsg}; +use api::{ApiMsg, FrameMsg, SceneMsg}; use bincode::{serialize, Infinite}; use byteorder::{LittleEndian, WriteBytesExt}; use std::any::TypeId; @@ -67,11 +67,23 @@ pub fn should_record_msg(msg: &ApiMsg) -> bool { ApiMsg::AddDocument { .. } | ApiMsg::DeleteDocument(..) => true, ApiMsg::UpdateDocument(_, ref msgs) => { + if msgs.generate_frame { + return true; + } + + for msg in &msgs.scene_ops { + match *msg { + SceneMsg::SetDisplayList { .. } | + SceneMsg::SetRootPipeline { .. } => return true, + _ => {} + } + } + for msg in &msgs.frame_ops { match *msg { FrameMsg::GetScrollNodeState(..) | FrameMsg::HitTest(..) => {} - _ => { return true; } + _ => return true, } } diff --git a/gfx/webrender/src/render_backend.rs b/gfx/webrender/src/render_backend.rs index 372b49f29784..e61c600f91c2 100644 --- a/gfx/webrender/src/render_backend.rs +++ b/gfx/webrender/src/render_backend.rs @@ -695,8 +695,9 @@ impl RenderBackend { pub fn run(&mut self, mut profile_counters: BackendProfileCounters) { let mut frame_counter: u32 = 0; + let mut keep_going = true; - loop { + while keep_going { profile_scope!("handle_msg"); while let Ok(msg) = self.scene_rx.try_recv() { @@ -740,7 +741,7 @@ impl RenderBackend { } } - let keep_going = match self.api_rx.recv() { + keep_going = match self.api_rx.recv() { Ok(msg) => { if let Some(ref mut r) = self.recorder { r.write_msg(frame_counter, &msg); @@ -749,13 +750,10 @@ impl RenderBackend { } Err(..) => { false } }; - - if !keep_going { - let _ = self.scene_tx.send(SceneBuilderRequest::Stop); - self.notifier.shut_down(); - break; - } } + + let _ = self.scene_tx.send(SceneBuilderRequest::Stop); + self.notifier.shut_down(); } fn process_api_msg( @@ -965,14 +963,6 @@ impl RenderBackend { let doc = self.documents.get_mut(&document_id).unwrap(); - if !doc.can_render() { - // TODO: this happens if we are building the first scene asynchronously and - // scroll at the same time. we should keep track of the fact that we skipped - // composition here and do it as soon as we receive the scene. - op.render = false; - op.composite = false; - } - if transaction_msg.generate_frame { if let Some(ref mut ros) = doc.render_on_scroll { *ros = true; @@ -984,6 +974,14 @@ impl RenderBackend { } } + if !doc.can_render() { + // TODO: this happens if we are building the first scene asynchronously and + // scroll at the same time. we should keep track of the fact that we skipped + // composition here and do it as soon as we receive the scene. + op.render = false; + op.composite = false; + } + debug_assert!(op.render || !op.composite); if op.render { diff --git a/gfx/webrender/src/render_task.rs b/gfx/webrender/src/render_task.rs index ea1fe8bfae20..fe94211f9ed9 100644 --- a/gfx/webrender/src/render_task.rs +++ b/gfx/webrender/src/render_task.rs @@ -933,6 +933,7 @@ impl RenderTaskCache { [0.0; 3], None, gpu_cache, + None, ); // Get the allocation details in the texture cache, and store diff --git a/gfx/webrender/src/renderer.rs b/gfx/webrender/src/renderer.rs index 9228f28b88d1..9312bc031c35 100644 --- a/gfx/webrender/src/renderer.rs +++ b/gfx/webrender/src/renderer.rs @@ -35,7 +35,7 @@ use device::{ProgramCache, ReadPixelsFormat}; use euclid::{rect, Transform3D}; use frame_builder::FrameBuilderConfig; use gleam::gl; -use glyph_rasterizer::GlyphFormat; +use glyph_rasterizer::{GlyphFormat, GlyphRasterizer}; use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList}; use gpu_types::PrimitiveInstance; use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError}; @@ -1309,9 +1309,6 @@ impl Renderer { let shaders = Shaders::new(&mut device, gl_type, &options)?; - let texture_cache = TextureCache::new(max_device_size); - let max_texture_size = texture_cache.max_texture_size(); - let backend_profile_counters = BackendProfileCounters::new(); let dither_matrix_texture = if options.enable_dithering { @@ -1489,11 +1486,7 @@ impl Renderer { let thread_listener_for_scene_builder = thread_listener.clone(); 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 resource_cache = ResourceCache::new( - texture_cache, - workers, - blob_image_renderer, - )?; + let glyph_rasterizer = GlyphRasterizer::new(workers)?; let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(config, api_tx.clone()); thread::Builder::new().name(scene_thread_name.clone()).spawn(move || { @@ -1515,6 +1508,14 @@ impl Renderer { if let Some(ref thread_listener) = *thread_listener_for_render_backend { thread_listener.thread_started(&rb_thread_name); } + + let texture_cache = TextureCache::new(max_device_size); + let resource_cache = ResourceCache::new( + texture_cache, + glyph_rasterizer, + blob_image_renderer, + ); + let mut backend = RenderBackend::new( api_rx, payload_rx_for_backend, @@ -1552,7 +1553,7 @@ impl Renderer { backend_profile_counters: BackendProfileCounters::new(), profile_counters: RendererProfileCounters::new(), profiler: Profiler::new(), - max_texture_size: max_texture_size, + max_texture_size: max_device_size, max_recorded_profiles: options.max_recorded_profiles, clear_color: options.clear_color, enable_clear_scissor: options.enable_clear_scissor, @@ -2358,14 +2359,6 @@ impl Renderer { textures: &BatchTextures, stats: &mut RendererStats, ) { - // Work around Angle bug that forgets to update sampler metadata, - // by making the use of those samplers uniform across programs. - // https://github.com/servo/webrender/wiki/Driver-issues#texturesize-in-vertex-shaders - let work_around_angle_bug = cfg!(windows); - if work_around_angle_bug { - self.device.reset_angle_sampler_metadata(&self.texture_resolver.dummy_cache_texture); - } - for i in 0 .. textures.colors.len() { self.texture_resolver.bind( &textures.colors[i], diff --git a/gfx/webrender/src/resource_cache.rs b/gfx/webrender/src/resource_cache.rs index ae80bd932523..3da3be0cff21 100644 --- a/gfx/webrender/src/resource_cache.rs +++ b/gfx/webrender/src/resource_cache.rs @@ -19,16 +19,11 @@ use capture::PlainExternalImage; #[cfg(any(feature = "replay", feature = "png"))] use capture::CaptureConfig; use device::TextureFilter; -use glyph_cache::GlyphCache; -#[cfg(feature = "capture")] -use glyph_cache::{PlainGlyphCacheRef, PlainCachedGlyphInfo}; -#[cfg(feature = "replay")] -use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn}; +use glyph_cache::{GlyphCache, GlyphCacheEntry}; use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest}; use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; -use internal_types::{FastHashMap, FastHashSet, ResourceCacheError, SourceTexture, TextureUpdateList}; +use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList}; use profiler::{ResourceProfileCounters, TextureCacheProfileCounters}; -use rayon::ThreadPool; use render_backend::FrameId; use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree}; use std::collections::hash_map::Entry::{self, Occupied, Vacant}; @@ -143,46 +138,40 @@ struct CachedImageInfo { epoch: Epoch, } -#[derive(Debug)] -#[cfg_attr(feature = "capture", derive(Clone, Serialize))] -#[cfg_attr(feature = "replay", derive(Deserialize))] -pub enum ResourceClassCacheError { - OverLimitSize, -} - -pub type ResourceCacheResult = Result; - #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct ResourceClassCache { - resources: FastHashMap>, +pub struct ResourceClassCache { + resources: FastHashMap, + pub user_data: U, } -impl ResourceClassCache +impl ResourceClassCache where K: Clone + Hash + Eq + Debug, + U: Default, { - pub fn new() -> ResourceClassCache { + pub fn new() -> ResourceClassCache { ResourceClassCache { resources: FastHashMap::default(), + user_data: Default::default(), } } - fn get(&self, key: &K) -> &ResourceCacheResult { + pub fn get(&self, key: &K) -> &V { self.resources.get(key) .expect("Didn't find a cached resource with that ID!") } - pub fn insert(&mut self, key: K, value: ResourceCacheResult) { + pub fn insert(&mut self, key: K, value: V) { self.resources.insert(key, value); } - pub fn get_mut(&mut self, key: &K) -> &mut ResourceCacheResult { + pub fn get_mut(&mut self, key: &K) -> &mut V { self.resources.get_mut(key) .expect("Didn't find a cached resource with that ID!") } - pub fn entry(&mut self, key: K) -> Entry> { + pub fn entry(&mut self, key: K) -> Entry { self.resources.entry(key) } @@ -203,6 +192,13 @@ where let _ = self.resources.remove(&key).unwrap(); } } + + pub fn retain(&mut self, f: F) + where + F: FnMut(&K, &mut V) -> bool, + { + self.resources.retain(f); + } } @@ -224,7 +220,14 @@ impl Into for ImageRequest { } } -type ImageCache = ResourceClassCache; +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Clone, Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ImageCacheError { + OverLimitSize, +} + +type ImageCache = ResourceClassCache, ()>; pub type FontInstanceMap = Arc>>; #[derive(Default)] @@ -273,12 +276,10 @@ pub struct ResourceCache { impl ResourceCache { pub fn new( texture_cache: TextureCache, - workers: Arc, + glyph_rasterizer: GlyphRasterizer, blob_image_renderer: Option>, - ) -> Result { - let glyph_rasterizer = GlyphRasterizer::new(workers)?; - - Ok(ResourceCache { + ) -> Self { + ResourceCache { cached_glyphs: GlyphCache::new(), cached_images: ResourceClassCache::new(), cached_render_tasks: RenderTaskCache::new(), @@ -290,7 +291,7 @@ impl ResourceCache { pending_image_requests: FastHashSet::default(), glyph_rasterizer, blob_image_renderer, - }) + } } pub fn max_texture_size(&self) -> u32 { @@ -566,7 +567,7 @@ impl ResourceCache { // The image or tiling size is too big for hardware texture size. warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!", template.descriptor.width, template.descriptor.height, template.tiling.unwrap_or(0)); - self.cached_images.insert(request, Err(ResourceClassCacheError::OverLimitSize)); + self.cached_images.insert(request, Err(ImageCacheError::OverLimitSize)); return; } @@ -682,7 +683,7 @@ impl ResourceCache { debug_assert!(fetch_buffer.is_empty()); for (loop_index, key) in glyph_keys.iter().enumerate() { - if let Ok(Some(ref glyph)) = *glyph_key_cache.get(key) { + if let GlyphCacheEntry::Cached(ref glyph) = *glyph_key_cache.get(key) { let cache_item = self.texture_cache.get(&glyph.texture_cache_handle); if current_texture_id != cache_item.texture_id || current_glyph_format != glyph.format { @@ -794,6 +795,7 @@ impl ResourceCache { debug_assert_eq!(self.state, State::Idle); self.state = State::AddResources; self.texture_cache.begin_frame(frame_id); + self.cached_glyphs.begin_frame(&mut self.texture_cache); self.cached_render_tasks.begin_frame(&mut self.texture_cache); self.current_frame_id = frame_id; } @@ -928,6 +930,7 @@ impl ResourceCache { [0.0; 3], image_template.dirty_rect, gpu_cache, + None, ); image_template.dirty_rect = None; } @@ -1035,7 +1038,7 @@ pub struct PlainResources { #[derive(Serialize)] pub struct PlainCacheRef<'a> { current_frame_id: FrameId, - glyphs: PlainGlyphCacheRef<'a>, + glyphs: &'a GlyphCache, glyph_dimensions: &'a GlyphDimensionsCache, images: &'a ImageCache, render_tasks: &'a RenderTaskCache, @@ -1046,7 +1049,7 @@ pub struct PlainCacheRef<'a> { #[derive(Deserialize)] pub struct PlainCacheOwn { current_frame_id: FrameId, - glyphs: PlainGlyphCacheOwn, + glyphs: GlyphCache, glyph_dimensions: GlyphDimensionsCache, images: ImageCache, render_tasks: RenderTaskCache, @@ -1228,64 +1231,10 @@ impl ResourceCache { } #[cfg(feature = "capture")] - pub fn save_caches(&self, root: &PathBuf) -> PlainCacheRef { - use std::io::Write; - use std::fs; - - let path_glyphs = root.join("glyphs"); - if !path_glyphs.is_dir() { - fs::create_dir(&path_glyphs).unwrap(); - } - - info!("\tcached glyphs"); - let mut glyph_paths = FastHashMap::default(); - for cache in self.cached_glyphs.glyph_key_caches.values() { - for result in cache.resources.values() { - let arc = match *result { - Ok(Some(ref info)) => &info.glyph_bytes, - Ok(None) | Err(_) => continue, - }; - let glyph_id = glyph_paths.len() + 1; - let entry = match glyph_paths.entry(arc.as_ptr()) { - Entry::Occupied(_) => continue, - Entry::Vacant(e) => e, - }; - - let file_name = format!("{}.raw", glyph_id); - let short_path = format!("glyphs/{}", file_name); - fs::File::create(path_glyphs.join(&file_name)) - .expect(&format!("Unable to create {}", short_path)) - .write_all(&*arc) - .unwrap(); - entry.insert(short_path); - } - } - + pub fn save_caches(&self, _root: &PathBuf) -> PlainCacheRef { PlainCacheRef { current_frame_id: self.current_frame_id, - glyphs: self.cached_glyphs.glyph_key_caches - .iter() - .map(|(font_instance, cache)| { - let resources = cache.resources - .iter() - .map(|(key, result)| { - (key.clone(), match *result { - Ok(Some(ref info)) => Ok(Some(PlainCachedGlyphInfo { - texture_cache_handle: info.texture_cache_handle.clone(), - glyph_bytes: glyph_paths[&info.glyph_bytes.as_ptr()].clone(), - size: info.size, - offset: info.offset, - scale: info.scale, - format: info.format, - })), - Ok(None) => Ok(None), - Err(ref e) => Err(e.clone()), - }) - }) - .collect(); - (font_instance, ResourceClassCache { resources }) - }) - .collect(), + glyphs: &self.cached_glyphs, glyph_dimensions: &self.cached_glyph_dimensions, images: &self.cached_images, render_tasks: &self.cached_render_tasks, @@ -1311,47 +1260,8 @@ impl ResourceCache { match caches { Some(cached) => { - let glyph_key_caches = cached.glyphs - .into_iter() - .map(|(font_instance, rcc)| { - let resources = rcc.resources - .into_iter() - .map(|(key, result)| { - (key, match result { - Ok(Some(info)) => { - let glyph_bytes = match raw_map.entry(info.glyph_bytes) { - Entry::Occupied(e) => { - e.get().clone() - } - Entry::Vacant(e) => { - let mut buffer = Vec::new(); - File::open(root.join(e.key())) - .expect(&format!("Unable to open {}", e.key())) - .read_to_end(&mut buffer) - .unwrap(); - e.insert(Arc::new(buffer)) - .clone() - } - }; - Ok(Some(CachedGlyphInfo { - texture_cache_handle: info.texture_cache_handle, - glyph_bytes, - size: info.size, - offset: info.offset, - scale: info.scale, - format: info.format, - })) - }, - Ok(None) => Ok(None), - Err(e) => Err(e), - }) - }) - .collect(); - (font_instance, ResourceClassCache { resources }) - }) - .collect(); self.current_frame_id = cached.current_frame_id; - self.cached_glyphs = GlyphCache { glyph_key_caches }; + self.cached_glyphs = cached.glyphs; self.cached_glyph_dimensions = cached.glyph_dimensions; self.cached_images = cached.images; self.cached_render_tasks = cached.render_tasks; diff --git a/gfx/webrender/src/texture_cache.rs b/gfx/webrender/src/texture_cache.rs index 45c69777784d..c92cde7825db 100644 --- a/gfx/webrender/src/texture_cache.rs +++ b/gfx/webrender/src/texture_cache.rs @@ -14,8 +14,10 @@ use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpda use profiler::{ResourceProfileCounter, TextureCacheProfileCounters}; use render_backend::FrameId; use resource_cache::CacheItem; +use std::cell::Cell; use std::cmp; use std::mem; +use std::rc::Rc; // The fixed number of layers for the shared texture cache. // There is one array texture per image format, allocated lazily. @@ -103,6 +105,8 @@ struct CacheEntry { filter: TextureFilter, // The actual device texture ID this is part of. texture_id: CacheTextureId, + // Optional notice when the entry is evicted from the cache. + eviction_notice: Option, } impl CacheEntry { @@ -124,6 +128,7 @@ impl CacheEntry { format, filter, uv_rect_handle: GpuCacheHandle::new(), + eviction_notice: None, } } @@ -150,6 +155,12 @@ impl CacheEntry { image_source.write_gpu_blocks(&mut request); } } + + fn evict(&self) { + if let Some(eviction_notice) = self.eviction_notice.as_ref() { + eviction_notice.notify(); + } + } } type WeakCacheEntryHandle = WeakFreeListHandle; @@ -173,6 +184,34 @@ impl TextureCacheHandle { } } +// An eviction notice is a shared condition useful for detecting +// when a TextureCacheHandle gets evicted from the TextureCache. +// It is optionally installed to the TextureCache when an update() +// is scheduled. A single notice may be shared among any number of +// TextureCacheHandle updates. The notice may then be subsequently +// checked to see if any of the updates using it have been evicted. +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct EvictionNotice { + evicted: Rc>, +} + +impl EvictionNotice { + fn notify(&self) { + self.evicted.set(true); + } + + pub fn check(&self) -> bool { + if self.evicted.get() { + self.evicted.set(false); + true + } else { + false + } + } +} + #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct TextureCache { @@ -301,6 +340,7 @@ impl TextureCache { user_data: [f32; 3], mut dirty_rect: Option, gpu_cache: &mut GpuCache, + eviction_notice: Option<&EvictionNotice>, ) { // Determine if we need to allocate texture cache memory // for this item. We need to reallocate if any of the following @@ -339,6 +379,9 @@ impl TextureCache { .get_opt_mut(handle.entry.as_ref().unwrap()) .expect("BUG: handle must be valid now"); + // Install the new eviction notice for this update, if applicable. + entry.eviction_notice = eviction_notice.cloned(); + // Invalidate the contents of the resource rect in the GPU cache. // This ensures that the update_gpu_cache below will add // the new information to the GPU cache. @@ -498,6 +541,7 @@ impl TextureCache { // Free the selected items for handle in eviction_candidates { let entry = self.entries.free(handle); + entry.evict(); self.free(entry); } @@ -552,6 +596,7 @@ impl TextureCache { retained_entries.push(handle); } else { let entry = self.entries.free(handle); + entry.evict(); if let Some(region) = self.free(entry) { found_matching_slab |= region.slab_size == needed_slab_size; freed_complete_page |= region.is_empty(); @@ -1069,6 +1114,7 @@ impl TextureArray { format: self.format, filter: self.filter, texture_id: self.texture_id.unwrap(), + eviction_notice: None, } }) } diff --git a/gfx/webrender_api/src/display_item.rs b/gfx/webrender_api/src/display_item.rs index 920a774526c9..b9ba903aae2c 100644 --- a/gfx/webrender_api/src/display_item.rs +++ b/gfx/webrender_api/src/display_item.rs @@ -418,11 +418,10 @@ pub struct GradientStop { #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct RadialGradient { - pub start_center: LayoutPoint, + pub center: LayoutPoint, + pub radius: LayoutSize, pub start_radius: f32, - pub end_center: LayoutPoint, pub end_radius: f32, - pub ratio_xy: f32, pub extend_mode: ExtendMode, } // IMPLICIT stops: Vec diff --git a/gfx/webrender_api/src/display_list.rs b/gfx/webrender_api/src/display_list.rs index e645899e21f6..722bbe7dde08 100644 --- a/gfx/webrender_api/src/display_list.rs +++ b/gfx/webrender_api/src/display_list.rs @@ -1188,11 +1188,10 @@ impl DisplayListBuilder { self.push_stops(&stops); return RadialGradient { - start_center: center, + center, + radius: LayoutSize::new(1.0, 1.0), start_radius: 0.0, - end_center: center, end_radius: 1.0, - ratio_xy: 1.0, extend_mode, }; } @@ -1203,11 +1202,10 @@ impl DisplayListBuilder { self.push_stops(&stops); RadialGradient { - start_center: center, + center, + radius, start_radius: radius.width * start_offset, - end_center: center, end_radius: radius.width * end_offset, - ratio_xy: radius.width / radius.height, extend_mode, } } @@ -1216,22 +1214,20 @@ impl DisplayListBuilder { // because create_gradient stores the stops in anticipation pub fn create_complex_radial_gradient( &mut self, - start_center: LayoutPoint, + center: LayoutPoint, + radius: LayoutSize, start_radius: f32, - end_center: LayoutPoint, end_radius: f32, - ratio_xy: f32, stops: Vec, extend_mode: ExtendMode, ) -> RadialGradient { self.push_stops(&stops); RadialGradient { - start_center, + center, + radius, start_radius, - end_center, end_radius, - ratio_xy, extend_mode, } } diff --git a/gfx/webrender_bindings/revision.txt b/gfx/webrender_bindings/revision.txt index 072af53cc16b..4959cb7fdaac 100644 --- a/gfx/webrender_bindings/revision.txt +++ b/gfx/webrender_bindings/revision.txt @@ -1 +1 @@ -486ee5f3aefb0172c2c5703e19f833e63eb295b9 +2083e83d958dd4a230ccae5c518e4bc8fbf88009 diff --git a/gfx/wrench/Cargo.toml b/gfx/wrench/Cargo.toml index dc71625e961a..071894ab8c19 100644 --- a/gfx/wrench/Cargo.toml +++ b/gfx/wrench/Cargo.toml @@ -6,7 +6,7 @@ build = "build.rs" license = "MPL-2.0" [dependencies] -base64 = "0.3" +base64 = "0.6" bincode = "0.9" byteorder = "1.0" env_logger = { version = "0.5", optional = true } @@ -14,7 +14,7 @@ euclid = "0.17" gleam = "0.4" glutin = "0.12" app_units = "0.6" -image = "0.17" +image = "0.18" clap = { version = "2", features = ["yaml"] } lazy_static = "1" log = "0.4" @@ -38,6 +38,7 @@ headless = [ "osmesa-sys", "osmesa-src" ] [target.'cfg(target_os = "windows")'.dependencies] dwrote = "0.4.1" +mozangle = {version = "0.1.5", features = ["egl"]} [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] font-loader = "0.6" diff --git a/gfx/wrench/src/angle.rs b/gfx/wrench/src/angle.rs new file mode 100644 index 000000000000..2cf06dcef80e --- /dev/null +++ b/gfx/wrench/src/angle.rs @@ -0,0 +1,72 @@ +/* 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 glutin; +use glutin::{WindowBuilder, ContextBuilder, EventsLoop, Window, CreationError}; + +#[cfg(not(windows))] +pub enum Context {} + +#[cfg(windows)] +pub use ::egl::Context; + +impl Context { + #[cfg(not(windows))] + pub fn with_window( + _: WindowBuilder, + _: ContextBuilder, + _: &EventsLoop, + ) -> Result<(Window, Self), CreationError> { + Err(CreationError::PlatformSpecific("ANGLE rendering is only supported on Windows".into())) + } + + #[cfg(windows)] + pub fn with_window( + window_builder: WindowBuilder, + context_builder: ContextBuilder, + events_loop: &EventsLoop, + ) -> Result<(Window, Self), CreationError> { + use glutin::os::windows::WindowExt; + + // FIXME: &context_builder.pf_reqs https://github.com/tomaka/glutin/pull/1002 + let pf_reqs = &glutin::PixelFormatRequirements::default(); + let gl_attr = &context_builder.gl_attr.map_sharing(|_| unimplemented!()); + let window = window_builder.build(events_loop)?; + Self::new(pf_reqs, gl_attr) + .and_then(|p| p.finish(window.get_hwnd() as _)) + .map(|context| (window, context)) + } +} + +#[cfg(not(windows))] +impl glutin::GlContext for Context { + unsafe fn make_current(&self) -> Result<(), glutin::ContextError> { + match *self {} + } + + fn is_current(&self) -> bool { + match *self {} + } + + fn get_proc_address(&self, _: &str) -> *const () { + match *self {} + } + + fn swap_buffers(&self) -> Result<(), glutin::ContextError> { + match *self {} + } + + fn get_api(&self) -> glutin::Api { + match *self {} + } + + fn get_pixel_format(&self) -> glutin::PixelFormat { + match *self {} + } + + fn resize(&self, _: u32, _: u32) { + match *self {} + } +} + diff --git a/gfx/wrench/src/args.yaml b/gfx/wrench/src/args.yaml index 2ca3b150cb05..c94ca7a326ed 100644 --- a/gfx/wrench/src/args.yaml +++ b/gfx/wrench/src/args.yaml @@ -44,6 +44,9 @@ args: short: h long: headless help: Enable headless rendering + - angle: + long: angle + help: Enable ANGLE rendering (on Windows only) - dp_ratio: short: p long: device-pixel-ratio diff --git a/gfx/wrench/src/egl.rs b/gfx/wrench/src/egl.rs new file mode 100644 index 000000000000..4c0ad6080ffc --- /dev/null +++ b/gfx/wrench/src/egl.rs @@ -0,0 +1,617 @@ +// Licensed under the Apache License, Version 2.0. +// This file may not be copied, modified, or distributed except according to those terms. + +//! Based on https://github.com/tomaka/glutin/blob/1b2d62c0e9/src/api/egl/mod.rs +#![cfg(windows)] +#![allow(unused_variables)] + +use glutin::ContextError; +use glutin::CreationError; +use glutin::GlAttributes; +use glutin::GlContext; +use glutin::GlRequest; +use glutin::PixelFormat; +use glutin::PixelFormatRequirements; +use glutin::ReleaseBehavior; +use glutin::Robustness; +use glutin::Api; + +use std::ffi::{CStr, CString}; +use std::os::raw::c_int; +use std::{mem, ptr}; +use std::cell::Cell; + +use mozangle::egl::ffi as egl; +mod ffi { + pub use mozangle::egl::ffi as egl; + pub use mozangle::egl::ffi::*; +} + +pub struct Context { + display: ffi::egl::types::EGLDisplay, + context: ffi::egl::types::EGLContext, + surface: Cell, + api: Api, + pixel_format: PixelFormat, +} + +impl Context { + /// Start building an EGL context. + /// + /// This function initializes some things and chooses the pixel format. + /// + /// To finish the process, you must call `.finish(window)` on the `ContextPrototype`. + pub fn new<'a>( + pf_reqs: &PixelFormatRequirements, + opengl: &'a GlAttributes<&'a Context>, + ) -> Result, CreationError> + { + if opengl.sharing.is_some() { + unimplemented!() + } + + // calling `eglGetDisplay` or equivalent + let display = unsafe { egl::GetDisplay(ptr::null_mut()) }; + + if display.is_null() { + return Err(CreationError::OsError("Could not create EGL display object".to_string())); + } + + let egl_version = unsafe { + let mut major: ffi::egl::types::EGLint = mem::uninitialized(); + let mut minor: ffi::egl::types::EGLint = mem::uninitialized(); + + if egl::Initialize(display, &mut major, &mut minor) == 0 { + return Err(CreationError::OsError(format!("eglInitialize failed"))) + } + + (major, minor) + }; + + // the list of extensions supported by the client once initialized is different from the + // list of extensions obtained earlier + let extensions = if egl_version >= (1, 2) { + let p = unsafe { CStr::from_ptr(egl::QueryString(display, ffi::egl::EXTENSIONS as i32)) }; + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| format!("")); + list.split(' ').map(|e| e.to_string()).collect::>() + + } else { + vec![] + }; + + // binding the right API and choosing the version + let (version, api) = unsafe { + match opengl.version { + GlRequest::Latest => { + if egl_version >= (1, 4) { + if egl::BindAPI(ffi::egl::OPENGL_API) != 0 { + (None, Api::OpenGl) + } else if egl::BindAPI(ffi::egl::OPENGL_ES_API) != 0 { + (None, Api::OpenGlEs) + } else { + return Err(CreationError::OpenGlVersionNotSupported); + } + } else { + (None, Api::OpenGlEs) + } + }, + GlRequest::Specific(Api::OpenGlEs, version) => { + if egl_version >= (1, 2) { + if egl::BindAPI(ffi::egl::OPENGL_ES_API) == 0 { + return Err(CreationError::OpenGlVersionNotSupported); + } + } + (Some(version), Api::OpenGlEs) + }, + GlRequest::Specific(Api::OpenGl, version) => { + if egl_version < (1, 4) { + return Err(CreationError::OpenGlVersionNotSupported); + } + if egl::BindAPI(ffi::egl::OPENGL_API) == 0 { + return Err(CreationError::OpenGlVersionNotSupported); + } + (Some(version), Api::OpenGl) + }, + GlRequest::Specific(_, _) => return Err(CreationError::OpenGlVersionNotSupported), + GlRequest::GlThenGles { opengles_version, opengl_version } => { + if egl_version >= (1, 4) { + if egl::BindAPI(ffi::egl::OPENGL_API) != 0 { + (Some(opengl_version), Api::OpenGl) + } else if egl::BindAPI(ffi::egl::OPENGL_ES_API) != 0 { + (Some(opengles_version), Api::OpenGlEs) + } else { + return Err(CreationError::OpenGlVersionNotSupported); + } + } else { + (Some(opengles_version), Api::OpenGlEs) + } + }, + } + }; + + let (config_id, pixel_format) = unsafe { + try!(choose_fbconfig(display, &egl_version, api, version, pf_reqs)) + }; + + Ok(ContextPrototype { + opengl: opengl, + display: display, + egl_version: egl_version, + extensions: extensions, + api: api, + version: version, + config_id: config_id, + pixel_format: pixel_format, + }) + } +} + +impl GlContext for Context { + unsafe fn make_current(&self) -> Result<(), ContextError> { + let ret = egl::MakeCurrent(self.display, self.surface.get(), self.surface.get(), self.context); + + if ret == 0 { + match egl::GetError() as u32 { + ffi::egl::CONTEXT_LOST => return Err(ContextError::ContextLost), + err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err) + } + + } else { + Ok(()) + } + } + + #[inline] + fn is_current(&self) -> bool { + unsafe { egl::GetCurrentContext() == self.context } + } + + fn get_proc_address(&self, addr: &str) -> *const () { + let addr = CString::new(addr.as_bytes()).unwrap(); + let addr = addr.as_ptr(); + unsafe { + egl::GetProcAddress(addr) as *const _ + } + } + + #[inline] + fn swap_buffers(&self) -> Result<(), ContextError> { + if self.surface.get() == ffi::egl::NO_SURFACE { + return Err(ContextError::ContextLost); + } + + let ret = unsafe { + egl::SwapBuffers(self.display, self.surface.get()) + }; + + if ret == 0 { + match unsafe { egl::GetError() } as u32 { + ffi::egl::CONTEXT_LOST => return Err(ContextError::ContextLost), + err => panic!("eglSwapBuffers failed (eglGetError returned 0x{:x})", err) + } + + } else { + Ok(()) + } + } + + #[inline] + fn get_api(&self) -> Api { + self.api + } + + #[inline] + fn get_pixel_format(&self) -> PixelFormat { + self.pixel_format.clone() + } + + #[inline] + fn resize(&self, _: u32, _: u32) {} +} + +unsafe impl Send for Context {} +unsafe impl Sync for Context {} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { + // we don't call MakeCurrent(0, 0) because we are not sure that the context + // is still the current one + egl::DestroyContext(self.display, self.context); + egl::DestroySurface(self.display, self.surface.get()); + egl::Terminate(self.display); + } + } +} + +pub struct ContextPrototype<'a> { + opengl: &'a GlAttributes<&'a Context>, + display: ffi::egl::types::EGLDisplay, + egl_version: (ffi::egl::types::EGLint, ffi::egl::types::EGLint), + extensions: Vec, + api: Api, + version: Option<(u8, u8)>, + config_id: ffi::egl::types::EGLConfig, + pixel_format: PixelFormat, +} + +impl<'a> ContextPrototype<'a> { + pub fn get_native_visual_id(&self) -> ffi::egl::types::EGLint { + let mut value = unsafe { mem::uninitialized() }; + let ret = unsafe { egl::GetConfigAttrib(self.display, self.config_id, + ffi::egl::NATIVE_VISUAL_ID + as ffi::egl::types::EGLint, &mut value) }; + if ret == 0 { panic!("eglGetConfigAttrib failed") }; + value + } + + pub fn finish(self, native_window: ffi::EGLNativeWindowType) + -> Result + { + let surface = unsafe { + let surface = egl::CreateWindowSurface(self.display, self.config_id, native_window, + ptr::null()); + if surface.is_null() { + return Err(CreationError::OsError(format!("eglCreateWindowSurface failed"))) + } + surface + }; + + self.finish_impl(surface) + } + + pub fn finish_pbuffer(self, dimensions: (u32, u32)) -> Result { + let attrs = &[ + ffi::egl::WIDTH as c_int, dimensions.0 as c_int, + ffi::egl::HEIGHT as c_int, dimensions.1 as c_int, + ffi::egl::NONE as c_int, + ]; + + let surface = unsafe { + let surface = egl::CreatePbufferSurface(self.display, self.config_id, + attrs.as_ptr()); + if surface.is_null() { + return Err(CreationError::OsError(format!("eglCreatePbufferSurface failed"))) + } + surface + }; + + self.finish_impl(surface) + } + + fn finish_impl(self, surface: ffi::egl::types::EGLSurface) + -> Result + { + let context = unsafe { + if let Some(version) = self.version { + try!(create_context(self.display, &self.egl_version, + &self.extensions, self.api, version, self.config_id, + self.opengl.debug, self.opengl.robustness)) + + } else if self.api == Api::OpenGlEs { + if let Ok(ctxt) = create_context(self.display, &self.egl_version, + &self.extensions, self.api, (2, 0), self.config_id, + self.opengl.debug, self.opengl.robustness) + { + ctxt + } else if let Ok(ctxt) = create_context(self.display, &self.egl_version, + &self.extensions, self.api, (1, 0), + self.config_id, self.opengl.debug, + self.opengl.robustness) + { + ctxt + } else { + return Err(CreationError::OpenGlVersionNotSupported); + } + + } else { + if let Ok(ctxt) = create_context(self.display, &self.egl_version, + &self.extensions, self.api, (3, 2), self.config_id, + self.opengl.debug, self.opengl.robustness) + { + ctxt + } else if let Ok(ctxt) = create_context(self.display, &self.egl_version, + &self.extensions, self.api, (3, 1), + self.config_id, self.opengl.debug, + self.opengl.robustness) + { + ctxt + } else if let Ok(ctxt) = create_context(self.display, &self.egl_version, + &self.extensions, self.api, (1, 0), + self.config_id, self.opengl.debug, + self.opengl.robustness) + { + ctxt + } else { + return Err(CreationError::OpenGlVersionNotSupported); + } + } + }; + + Ok(Context { + display: self.display, + context: context, + surface: Cell::new(surface), + api: self.api, + pixel_format: self.pixel_format, + }) + } +} + +unsafe fn choose_fbconfig(display: ffi::egl::types::EGLDisplay, + egl_version: &(ffi::egl::types::EGLint, ffi::egl::types::EGLint), + api: Api, version: Option<(u8, u8)>, reqs: &PixelFormatRequirements) + -> Result<(ffi::egl::types::EGLConfig, PixelFormat), CreationError> +{ + let descriptor = { + let mut out: Vec = Vec::with_capacity(37); + + if egl_version >= &(1, 2) { + out.push(ffi::egl::COLOR_BUFFER_TYPE as c_int); + out.push(ffi::egl::RGB_BUFFER as c_int); + } + + out.push(ffi::egl::SURFACE_TYPE as c_int); + // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error + // if we ask for PBUFFER_BIT as well as WINDOW_BIT + out.push((ffi::egl::WINDOW_BIT) as c_int); + + match (api, version) { + (Api::OpenGlEs, Some((3, _))) => { + if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); } + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + }, + (Api::OpenGlEs, Some((2, _))) => { + if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); } + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + }, + (Api::OpenGlEs, Some((1, _))) => { + if egl_version >= &(1, 3) { + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES_BIT as c_int); + } + }, + (Api::OpenGlEs, _) => unimplemented!(), + (Api::OpenGl, _) => { + if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); } + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_BIT as c_int); + }, + (_, _) => unimplemented!(), + }; + + if let Some(hardware_accelerated) = reqs.hardware_accelerated { + out.push(ffi::egl::CONFIG_CAVEAT as c_int); + out.push(if hardware_accelerated { + ffi::egl::NONE as c_int + } else { + ffi::egl::SLOW_CONFIG as c_int + }); + } + + if let Some(color) = reqs.color_bits { + out.push(ffi::egl::RED_SIZE as c_int); + out.push((color / 3) as c_int); + out.push(ffi::egl::GREEN_SIZE as c_int); + out.push((color / 3 + if color % 3 != 0 { 1 } else { 0 }) as c_int); + out.push(ffi::egl::BLUE_SIZE as c_int); + out.push((color / 3 + if color % 3 == 2 { 1 } else { 0 }) as c_int); + } + + if let Some(alpha) = reqs.alpha_bits { + out.push(ffi::egl::ALPHA_SIZE as c_int); + out.push(alpha as c_int); + } + + if let Some(depth) = reqs.depth_bits { + out.push(ffi::egl::DEPTH_SIZE as c_int); + out.push(depth as c_int); + } + + if let Some(stencil) = reqs.stencil_bits { + out.push(ffi::egl::STENCIL_SIZE as c_int); + out.push(stencil as c_int); + } + + if let Some(true) = reqs.double_buffer { + return Err(CreationError::NoAvailablePixelFormat); + } + + if let Some(multisampling) = reqs.multisampling { + out.push(ffi::egl::SAMPLES as c_int); + out.push(multisampling as c_int); + } + + if reqs.stereoscopy { + return Err(CreationError::NoAvailablePixelFormat); + } + + // FIXME: srgb is not taken into account + + match reqs.release_behavior { + ReleaseBehavior::Flush => (), + ReleaseBehavior::None => { + // TODO: with EGL you need to manually set the behavior + unimplemented!() + }, + } + + out.push(ffi::egl::NONE as c_int); + out + }; + + // calling `eglChooseConfig` + let mut config_id = mem::uninitialized(); + let mut num_configs = mem::uninitialized(); + if egl::ChooseConfig(display, descriptor.as_ptr(), &mut config_id, 1, &mut num_configs) == 0 { + return Err(CreationError::OsError(format!("eglChooseConfig failed"))); + } + if num_configs == 0 { + return Err(CreationError::NoAvailablePixelFormat); + } + + // analyzing each config + macro_rules! attrib { + ($display:expr, $config:expr, $attr:expr) => ( + { + let mut value = mem::uninitialized(); + let res = egl::GetConfigAttrib($display, $config, + $attr as ffi::egl::types::EGLint, &mut value); + if res == 0 { + return Err(CreationError::OsError(format!("eglGetConfigAttrib failed"))); + } + value + } + ) + }; + + let desc = PixelFormat { + hardware_accelerated: attrib!(display, config_id, ffi::egl::CONFIG_CAVEAT) + != ffi::egl::SLOW_CONFIG as i32, + color_bits: attrib!(display, config_id, ffi::egl::RED_SIZE) as u8 + + attrib!(display, config_id, ffi::egl::BLUE_SIZE) as u8 + + attrib!(display, config_id, ffi::egl::GREEN_SIZE) as u8, + alpha_bits: attrib!(display, config_id, ffi::egl::ALPHA_SIZE) as u8, + depth_bits: attrib!(display, config_id, ffi::egl::DEPTH_SIZE) as u8, + stencil_bits: attrib!(display, config_id, ffi::egl::STENCIL_SIZE) as u8, + stereoscopy: false, + double_buffer: true, + multisampling: match attrib!(display, config_id, ffi::egl::SAMPLES) { + 0 | 1 => None, + a => Some(a as u16), + }, + srgb: false, // TODO: use EGL_KHR_gl_colorspace to know that + }; + + Ok((config_id, desc)) +} + +unsafe fn create_context(display: ffi::egl::types::EGLDisplay, + egl_version: &(ffi::egl::types::EGLint, ffi::egl::types::EGLint), + extensions: &[String], api: Api, version: (u8, u8), + config_id: ffi::egl::types::EGLConfig, gl_debug: bool, + gl_robustness: Robustness) + -> Result +{ + let mut context_attributes = Vec::with_capacity(10); + let mut flags = 0; + + if egl_version >= &(1, 5) || extensions.iter().find(|s| s == &"EGL_KHR_create_context") + .is_some() + { + context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32); + context_attributes.push(version.0 as i32); + context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32); + context_attributes.push(version.1 as i32); + + // handling robustness + let supports_robustness = egl_version >= &(1, 5) || + extensions.iter() + .find(|s| s == &"EGL_EXT_create_context_robustness") + .is_some(); + + match gl_robustness { + Robustness::NotRobust => (), + + Robustness::NoError => { + if extensions.iter().find(|s| s == &"EGL_KHR_create_context_no_error").is_some() { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_NO_ERROR_KHR as c_int); + context_attributes.push(1); + } + }, + + Robustness::RobustNoResetNotification => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } else { + return Err(CreationError::RobustnessNotSupported); + } + }, + + Robustness::TryRobustNoResetNotification => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } + }, + + Robustness::RobustLoseContextOnReset => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } else { + return Err(CreationError::RobustnessNotSupported); + } + }, + + Robustness::TryRobustLoseContextOnReset => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } + }, + } + + if gl_debug { + if egl_version >= &(1, 5) { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32); + context_attributes.push(ffi::egl::TRUE as i32); + } + + // TODO: using this flag sometimes generates an error + // there was a change in the specs that added this flag, so it may not be + // supported everywhere ; however it is not possible to know whether it is + // supported or not + //flags = flags | ffi::egl::CONTEXT_OPENGL_DEBUG_BIT_KHR as i32; + } + + context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); + context_attributes.push(flags); + + } else if egl_version >= &(1, 3) && api == Api::OpenGlEs { + // robustness is not supported + match gl_robustness { + Robustness::RobustNoResetNotification | Robustness::RobustLoseContextOnReset => { + return Err(CreationError::RobustnessNotSupported); + }, + _ => () + } + + context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32); + context_attributes.push(version.0 as i32); + } + + context_attributes.push(ffi::egl::NONE as i32); + + let context = egl::CreateContext(display, config_id, ptr::null(), + context_attributes.as_ptr()); + + if context.is_null() { + match egl::GetError() as u32 { + ffi::egl::BAD_ATTRIBUTE => return Err(CreationError::OpenGlVersionNotSupported), + e => panic!("eglCreateContext failed: 0x{:x}", e), + } + } + + Ok(context) +} + diff --git a/gfx/wrench/src/main.rs b/gfx/wrench/src/main.rs index 081b4b872cbd..ce2ec85760c5 100644 --- a/gfx/wrench/src/main.rs +++ b/gfx/wrench/src/main.rs @@ -27,6 +27,8 @@ extern crate image; extern crate lazy_static; #[macro_use] extern crate log; +#[cfg(target_os = "windows")] +extern crate mozangle; #[cfg(feature = "headless")] extern crate osmesa_sys; extern crate ron; @@ -37,8 +39,10 @@ extern crate time; extern crate webrender; extern crate yaml_rust; +mod angle; mod binary_frame_reader; mod blob; +mod egl; mod json_frame_writer; mod parse_function; mod perf; @@ -159,6 +163,7 @@ impl HeadlessContext { pub enum WindowWrapper { Window(glutin::GlWindow, Rc), + Angle(glutin::Window, angle::Context, Rc), Headless(HeadlessContext, Rc), } @@ -168,21 +173,26 @@ impl WindowWrapper { fn swap_buffers(&self) { match *self { WindowWrapper::Window(ref window, _) => window.swap_buffers().unwrap(), + WindowWrapper::Angle(_, ref context, _) => context.swap_buffers().unwrap(), WindowWrapper::Headless(_, _) => {} } } fn get_inner_size(&self) -> DeviceUintSize { + //HACK: `winit` needs to figure out its hidpi story... + #[cfg(target_os = "macos")] + fn inner_size(window: &glutin::Window) -> (u32, u32) { + let (w, h) = window.get_inner_size().unwrap(); + let factor = window.hidpi_factor(); + ((w as f32 * factor) as _, (h as f32 * factor) as _) + } + #[cfg(not(target_os = "macos"))] + fn inner_size(window: &glutin::Window) -> (u32, u32) { + window.get_inner_size().unwrap() + } let (w, h) = match *self { - //HACK: `winit` needs to figure out its hidpi story... - #[cfg(target_os = "macos")] - WindowWrapper::Window(ref window, _) => { - let (w, h) = window.get_inner_size().unwrap(); - let factor = window.hidpi_factor(); - ((w as f32 * factor) as _, (h as f32 * factor) as _) - }, - #[cfg(not(target_os = "macos"))] - WindowWrapper::Window(ref window, _) => window.get_inner_size().unwrap(), + WindowWrapper::Window(ref window, _) => inner_size(window.window()), + WindowWrapper::Angle(ref window, ..) => inner_size(window), WindowWrapper::Headless(ref context, _) => (context.width, context.height), }; DeviceUintSize::new(w, h) @@ -191,6 +201,7 @@ impl WindowWrapper { fn hidpi_factor(&self) -> f32 { match *self { WindowWrapper::Window(ref window, _) => window.hidpi_factor(), + WindowWrapper::Angle(ref window, ..) => window.hidpi_factor(), WindowWrapper::Headless(_, _) => 1.0, } } @@ -198,6 +209,7 @@ impl WindowWrapper { fn resize(&mut self, size: DeviceUintSize) { match *self { WindowWrapper::Window(ref mut window, _) => window.set_inner_size(size.width, size.height), + WindowWrapper::Angle(ref mut window, ..) => window.set_inner_size(size.width, size.height), WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update } } @@ -205,19 +217,24 @@ impl WindowWrapper { fn set_title(&mut self, title: &str) { match *self { WindowWrapper::Window(ref window, _) => window.set_title(title), + WindowWrapper::Angle(ref window, ..) => window.set_title(title), WindowWrapper::Headless(_, _) => (), } } pub fn gl(&self) -> &gl::Gl { match *self { - WindowWrapper::Window(_, ref gl) | WindowWrapper::Headless(_, ref gl) => &**gl, + WindowWrapper::Window(_, ref gl) | + WindowWrapper::Angle(_, _, ref gl) | + WindowWrapper::Headless(_, ref gl) => &**gl, } } pub fn clone_gl(&self) -> Rc { match *self { - WindowWrapper::Window(_, ref gl) | WindowWrapper::Headless(_, ref gl) => gl.clone(), + WindowWrapper::Window(_, ref gl) | + WindowWrapper::Angle(_, _, ref gl) | + WindowWrapper::Headless(_, ref gl) => gl.clone(), } } } @@ -227,6 +244,7 @@ fn make_window( dp_ratio: Option, vsync: bool, events_loop: &Option, + angle: bool, ) -> WindowWrapper { let wrapper = match *events_loop { Some(ref events_loop) => { @@ -240,25 +258,37 @@ fn make_window( .with_title("WRech") .with_multitouch() .with_dimensions(size.width, size.height); - let window = glutin::GlWindow::new(window_builder, context_builder, events_loop) - .unwrap(); - unsafe { - window - .make_current() - .expect("unable to make context current!"); - } + let init = |context: &glutin::GlContext| { + unsafe { + context + .make_current() + .expect("unable to make context current!"); + } - let gl = match window.get_api() { - glutin::Api::OpenGl => unsafe { - gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) - }, - glutin::Api::OpenGlEs => unsafe { - gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) - }, - glutin::Api::WebGl => unimplemented!(), + match context.get_api() { + glutin::Api::OpenGl => unsafe { + gl::GlFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::OpenGlEs => unsafe { + gl::GlesFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::WebGl => unimplemented!(), + } }; - WindowWrapper::Window(window, gl) + + if angle { + let (window, context) = angle::Context::with_window( + window_builder, context_builder, events_loop + ).unwrap(); + let gl = init(&context); + WindowWrapper::Angle(window, context, gl) + } else { + let window = glutin::GlWindow::new(window_builder, context_builder, events_loop) + .unwrap(); + let gl = init(&window); + WindowWrapper::Window(window, gl) + } } None => { let gl = match gl::GlType::default() { @@ -293,8 +323,14 @@ fn make_window( wrapper } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum NotifierEvent { + WakeUp, + ShutDown, +} + struct Notifier { - tx: Sender<()>, + tx: Sender, } // setup a notifier so we can wait for frames to be finished @@ -306,7 +342,11 @@ impl RenderNotifier for Notifier { } fn wake_up(&self) { - self.tx.send(()).unwrap(); + self.tx.send(NotifierEvent::WakeUp).unwrap(); + } + + fn shut_down(&self) { + self.tx.send(NotifierEvent::ShutDown).unwrap(); } fn new_document_ready(&self, _: DocumentId, scrolled: bool, _composite_needed: bool) { @@ -316,7 +356,7 @@ impl RenderNotifier for Notifier { } } -fn create_notifier() -> (Box, Receiver<()>) { +fn create_notifier() -> (Box, Receiver) { let (tx, rx) = channel(); (Box::new(Notifier { tx: tx }), rx) } @@ -364,7 +404,9 @@ fn main() { Some(glutin::EventsLoop::new()) }; - let mut window = make_window(size, dp_ratio, args.is_present("vsync"), &events_loop); + let mut window = make_window( + size, dp_ratio, args.is_present("vsync"), &events_loop, args.is_present("angle"), + ); let dp_ratio = dp_ratio.unwrap_or(window.hidpi_factor()); let dim = window.get_inner_size(); @@ -420,17 +462,19 @@ fn main() { reftest_options.allow_max_difference = allow_max_diff.parse().unwrap_or(1); reftest_options.allow_num_differences = dim.width as usize * dim.height as usize; } - let num_failures = ReftestHarness::new(&mut wrench, &mut window, rx.unwrap()) + let rx = rx.unwrap(); + let num_failures = ReftestHarness::new(&mut wrench, &mut window, &rx) .run(base_manifest, specific_reftest, &reftest_options); - wrench.renderer.deinit(); + wrench.shut_down(rx); // exit with an error code to fail on CI process::exit(num_failures as _); } else if let Some(_) = args.subcommand_matches("rawtest") { + let rx = rx.unwrap(); { - let harness = RawtestHarness::new(&mut wrench, &mut window, rx.unwrap()); + let harness = RawtestHarness::new(&mut wrench, &mut window, &rx); harness.run(); } - wrench.renderer.deinit(); + wrench.shut_down(rx); return; } else if let Some(subargs) = args.subcommand_matches("perf") { // Perf mode wants to benchmark the total cost of drawing diff --git a/gfx/wrench/src/perf.rs b/gfx/wrench/src/perf.rs index 3d5621b515c4..b5100e414e66 100644 --- a/gfx/wrench/src/perf.rs +++ b/gfx/wrench/src/perf.rs @@ -2,6 +2,7 @@ * 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 NotifierEvent; use WindowWrapper; use serde_json; use std::collections::{HashMap, HashSet}; @@ -123,11 +124,11 @@ impl Profile { pub struct PerfHarness<'a> { wrench: &'a mut Wrench, window: &'a mut WindowWrapper, - rx: Receiver<()>, + rx: Receiver, } impl<'a> PerfHarness<'a> { - pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self { + pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver) -> Self { PerfHarness { wrench, window, rx } } diff --git a/gfx/wrench/src/png.rs b/gfx/wrench/src/png.rs index 725678fe98a9..7acd3ce7da87 100644 --- a/gfx/wrench/src/png.rs +++ b/gfx/wrench/src/png.rs @@ -2,7 +2,7 @@ * 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 WindowWrapper; +use {WindowWrapper, NotifierEvent}; use image::png::PNGEncoder; use image::{self, ColorType, GenericImage}; use std::fs::File; @@ -77,7 +77,7 @@ pub fn png( surface: ReadSurface, window: &mut WindowWrapper, mut reader: YamlFrameReader, - rx: Receiver<()>, + rx: Receiver, ) { reader.do_frame(wrench); diff --git a/gfx/wrench/src/rawtest.rs b/gfx/wrench/src/rawtest.rs index 1a03b351fcc2..49ba048b991d 100644 --- a/gfx/wrench/src/rawtest.rs +++ b/gfx/wrench/src/rawtest.rs @@ -2,7 +2,7 @@ * 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 WindowWrapper; +use {WindowWrapper, NotifierEvent}; use blob; use euclid::{TypedRect, TypedSize2D, TypedPoint2D}; use std::sync::Arc; @@ -13,7 +13,7 @@ use wrench::Wrench; pub struct RawtestHarness<'a> { wrench: &'a mut Wrench, - rx: Receiver<()>, + rx: &'a Receiver, window: &'a mut WindowWrapper, } @@ -30,7 +30,7 @@ fn rect(x: T, y: T, width: T, height: T) -> TypedRect { } impl<'a> RawtestHarness<'a> { - pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self { + pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver) -> Self { RawtestHarness { wrench, rx, diff --git a/gfx/wrench/src/reftest.rs b/gfx/wrench/src/reftest.rs index 7dfd1f541088..7f1e5df0d389 100644 --- a/gfx/wrench/src/reftest.rs +++ b/gfx/wrench/src/reftest.rs @@ -2,7 +2,7 @@ * 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 WindowWrapper; +use {WindowWrapper, NotifierEvent}; use base64; use image::load as load_piston_image; use image::png::PNGEncoder; @@ -292,10 +292,10 @@ impl ReftestManifest { pub struct ReftestHarness<'a> { wrench: &'a mut Wrench, window: &'a mut WindowWrapper, - rx: Receiver<()>, + rx: &'a Receiver, } impl<'a> ReftestHarness<'a> { - pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self { + pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver) -> Self { ReftestHarness { wrench, window, rx } } diff --git a/gfx/wrench/src/wrench.rs b/gfx/wrench/src/wrench.rs index d629ada01a2e..6189259531fb 100644 --- a/gfx/wrench/src/wrench.rs +++ b/gfx/wrench/src/wrench.rs @@ -16,12 +16,13 @@ use ron_frame_writer::RonFrameWriter; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, Mutex}; +use std::sync::mpsc::Receiver; use time; use webrender; use webrender::api::*; use webrender::{DebugFlags, RendererStats}; use yaml_frame_writer::YamlFrameWriterReceiver; -use {WindowWrapper, BLACK_COLOR, WHITE_COLOR}; +use {WindowWrapper, NotifierEvent, BLACK_COLOR, WHITE_COLOR}; // TODO(gw): This descriptor matches what we currently support for fonts // but is quite a mess. We should at least document and @@ -590,4 +591,18 @@ impl Wrench { } } } + + pub fn shut_down(self, rx: Receiver) { + self.api.shut_down(); + + loop { + match rx.recv() { + Ok(NotifierEvent::ShutDown) => { break; } + Ok(_) => {} + Err(e) => { panic!("Did not shut down properly: {:?}.", e); } + } + } + + self.renderer.deinit(); + } } diff --git a/gfx/wrench/src/yaml_frame_reader.rs b/gfx/wrench/src/yaml_frame_reader.rs index 0296854e2943..1aef56886c82 100644 --- a/gfx/wrench/src/yaml_frame_reader.rs +++ b/gfx/wrench/src/yaml_frame_reader.rs @@ -599,13 +599,10 @@ impl YamlFrameReader { } fn to_radial_gradient(&mut self, dl: &mut DisplayListBuilder, item: &Yaml) -> RadialGradient { - if item["start-center"].is_badvalue() { - let center = item["center"] - .as_point() - .expect("radial gradient must have start center"); - let radius = item["radius"] - .as_size() - .expect("radial gradient must have start radius"); + let center = item["center"].as_point().expect("radial gradient must have center"); + let radius = item["radius"].as_size().expect("radial gradient must have a radius"); + + if item["start-radius"].is_badvalue() { let stops = item["stops"] .as_vec() .expect("radial gradient must have stops") @@ -629,19 +626,12 @@ impl YamlFrameReader { dl.create_radial_gradient(center, radius, stops, extend_mode) } else { - let start_center = item["start-center"] - .as_point() - .expect("radial gradient must have start center"); let start_radius = item["start-radius"] .as_force_f32() .expect("radial gradient must have start radius"); - let end_center = item["end-center"] - .as_point() - .expect("radial gradient must have end center"); let end_radius = item["end-radius"] .as_force_f32() .expect("radial gradient must have end radius"); - let ratio_xy = item["ratio-xy"].as_force_f32().unwrap_or(1.0); let stops = item["stops"] .as_vec() .expect("radial gradient must have stops") @@ -664,11 +654,10 @@ impl YamlFrameReader { }; dl.create_complex_radial_gradient( - start_center, + center, + radius, start_radius, - end_center, end_radius, - ratio_xy, stops, extend_mode, ) diff --git a/gfx/wrench/src/yaml_frame_writer.rs b/gfx/wrench/src/yaml_frame_writer.rs index f24c8da810cf..75fd21425b41 100644 --- a/gfx/wrench/src/yaml_frame_writer.rs +++ b/gfx/wrench/src/yaml_frame_writer.rs @@ -905,11 +905,10 @@ impl YamlFrameWriter { ]; yaml_node(&mut v, "width", f32_vec_yaml(&widths, true)); str_node(&mut v, "border-type", "radial-gradient"); - point_node(&mut v, "start-center", &details.gradient.start_center); + point_node(&mut v, "center", &details.gradient.center); + size_node(&mut v, "radius", &details.gradient.radius); f32_node(&mut v, "start-radius", details.gradient.start_radius); - point_node(&mut v, "end-center", &details.gradient.end_center); f32_node(&mut v, "end-radius", details.gradient.end_radius); - f32_node(&mut v, "ratio-xy", details.gradient.ratio_xy); let mut stops = vec![]; for stop in display_list.get(base.gradient_stops()) { stops.push(Yaml::Real(stop.offset.to_string())); @@ -961,11 +960,10 @@ impl YamlFrameWriter { } RadialGradient(item) => { str_node(&mut v, "type", "radial-gradient"); - point_node(&mut v, "start-center", &item.gradient.start_center); + point_node(&mut v, "center", &item.gradient.center); + size_node(&mut v, "center", &item.gradient.radius); f32_node(&mut v, "start-radius", item.gradient.start_radius); - point_node(&mut v, "end-center", &item.gradient.end_center); f32_node(&mut v, "end-radius", item.gradient.end_radius); - f32_node(&mut v, "ratio-xy", item.gradient.ratio_xy); size_node(&mut v, "tile-size", &item.tile_size); size_node(&mut v, "tile-spacing", &item.tile_spacing); let mut stops = vec![];