--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -30,18 +30,18 @@ webrender_api = {path = "../webrender_ap
bitflags = "1.0"
thread_profiler = "0.1.1"
plane-split = "0.8"
png = { optional = true, version = "0.11" }
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]
mozangle = "0.1"
env_logger = "0.5"
rand = "0.3" # for the benchmarks
glutin = "0.12" # for the example apps
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -4,32 +4,31 @@
#define VECS_PER_SPECIFIC_BRUSH 2
#include shared,prim_shared,brush
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;
varying vec2 vPos;
#ifdef WR_FEATURE_ALPHA_PASS
varying vec2 vLocalPos;
#endif
#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) {
vec4 data[2] = fetch_from_resource_cache_2(address);
return RadialGradient(data[0], data[1]);
}
void brush_vs(
@@ -38,50 +37,46 @@ void brush_vs(
RectWithSize local_rect,
ivec3 user_data,
PictureTask pic_task
) {
RadialGradient gradient = fetch_radial_gradient(prim_address);
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;
#endif
}
#endif
#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;
if (A == 0.0) {
// Since A is 0, just solve for -2Bt + C = 0
if (B == 0.0) {
discard;
}
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -926,32 +926,16 @@ impl Device {
if self.bound_program != program.id {
self.gl.use_program(program.id);
self.bound_program = program.id;
self.program_mode_id = UniformLocation(program.u_mode);
}
}
- //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,
format: ImageFormat,
) -> Texture {
Texture {
id: self.gl.gen_textures(1)[0],
target: get_gl_target(target),
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -733,21 +733,20 @@ impl<'a> DisplayListFlattener<'a> {
info.tile_size,
info.tile_spacing,
);
}
SpecificDisplayItem::RadialGradient(ref info) => {
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,
info.tile_spacing,
);
}
SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
let bounds = box_shadow_info
@@ -1819,21 +1818,20 @@ impl<'a> DisplayListFlattener<'a> {
for segment in create_segments(border.outset) {
let segment_rel = segment.origin - rect.origin;
let mut info = info.clone();
info.rect = segment;
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,
LayerSize::zero(),
);
}
}
}
@@ -1933,31 +1931,29 @@ impl<'a> DisplayListFlattener<'a> {
}
}
}
fn add_radial_gradient_impl(
&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<GradientStop>,
extend_mode: ExtendMode,
gradient_index: CachedGradientIndex,
) {
let prim = BrushPrimitive::new(
BrushKind::RadialGradient {
stops_range: stops,
extend_mode,
- start_center,
- end_center,
+ center,
start_radius,
end_radius,
ratio_xy,
gradient_index,
},
None,
);
@@ -1968,19 +1964,18 @@ impl<'a> DisplayListFlattener<'a> {
PrimitiveContainer::Brush(prim),
);
}
pub fn add_radial_gradient(
&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<GradientStop>,
extend_mode: ExtendMode,
tile_size: LayerSize,
tile_spacing: LayerSize,
) {
let gradient_index = CachedGradientIndex(self.cached_gradients.len());
@@ -1991,33 +1986,31 @@ impl<'a> DisplayListFlattener<'a> {
tile_spacing,
64 * 64,
);
if prim_infos.is_empty() {
self.add_radial_gradient_impl(
clip_and_scroll,
info,
- start_center,
+ center,
start_radius,
- end_center,
end_radius,
ratio_xy,
stops,
extend_mode,
gradient_index,
);
} else {
for prim_info in prim_infos {
self.add_radial_gradient_impl(
clip_and_scroll,
&prim_info,
- start_center,
+ center,
start_radius,
- end_center,
end_radius,
ratio_xy,
stops,
extend_mode,
gradient_index,
);
}
}
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,57 +1,62 @@
/* 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::{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 CachedGlyphInfo {
+ pub texture_cache_handle: TextureCacheHandle,
+ pub format: GlyphFormat,
+}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct GenericCachedGlyphInfo<D> {
- pub texture_cache_handle: TextureCacheHandle,
- pub glyph_bytes: D,
- pub size: DeviceUintSize,
- pub offset: DevicePoint,
- pub scale: f32,
- pub format: GlyphFormat,
+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,
}
-pub type CachedGlyphInfo = GenericCachedGlyphInfo<Arc<Vec<u8>>>;
-pub type GlyphKeyCache = ResourceClassCache<GlyphKey, Option<CachedGlyphInfo>>;
+pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, EvictionNotice>;
-#[cfg(any(feature = "capture", feature = "replay"))]
-pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo<String>;
-#[cfg(any(feature = "capture", feature = "replay"))]
-pub type PlainGlyphKeyCache = ResourceClassCache<GlyphKey, Option<PlainCachedGlyphInfo>>;
-#[cfg(feature = "capture")]
-pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>;
-#[cfg(feature = "replay")]
-pub type PlainGlyphCacheOwn = FastHashMap<FontInstance, PlainGlyphKeyCache>;
+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<FontInstance, GlyphKeyCache>,
+ glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
}
impl GlyphCache {
pub fn new() -> Self {
GlyphCache {
glyph_key_caches: FastHashMap::default(),
}
}
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 {
self.glyph_key_caches
.get(font)
.expect("BUG: Unable to find glyph key cache!")
}
@@ -73,9 +78,41 @@ impl GlyphCache {
.filter(&key_fun)
.cloned()
.collect::<Vec<_>>();
for key in caches_to_destroy {
let mut cache = self.glyph_key_caches.remove(&key).unwrap();
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);
+ }
}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,24 +1,24 @@
/* 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/. */
#[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;
use rayon::prelude::*;
use std::cmp;
use std::collections::hash_map::Entry;
use std::hash::{Hash, Hasher};
use std::mem;
@@ -304,21 +304,21 @@ pub struct GlyphRasterizer {
font_contexts: Arc<FontContexts>,
// Maintain a set of glyphs that have been requested this
// frame. This ensures the glyph thread won't rasterize
// the same glyph more than once in a frame. This is required
// 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<GlyphRequest>,
+ pending_glyphs: usize,
// Receives the rendered glyphs.
- glyph_rx: Receiver<Vec<GlyphRasterJob>>,
- glyph_tx: Sender<Vec<GlyphRasterJob>>,
+ glyph_rx: Receiver<GlyphRasterJobs>,
+ glyph_tx: Sender<GlyphRasterJobs>,
// We defer removing fonts to the end of the frame so that:
// - this work is done outside of the critical path,
// - we don't have to worry about the ordering of events if a font is used on
// a frame where it is used (although it seems unlikely).
fonts_to_remove: Vec<FontKey>,
}
@@ -336,17 +336,17 @@ impl GlyphRasterizer {
}
Ok(GlyphRasterizer {
font_contexts: Arc::new(FontContexts {
worker_contexts: contexts,
shared_context: Mutex::new(shared_context),
workers: Arc::clone(&workers),
}),
- pending_glyphs: FastHashSet::default(),
+ pending_glyphs: 0,
glyph_rx,
glyph_tx,
workers,
fonts_to_remove: Vec::new(),
})
}
pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
@@ -386,92 +386,85 @@ impl GlyphRasterizer {
texture_cache: &mut TextureCache,
gpu_cache: &mut GpuCache,
) {
assert!(
self.font_contexts
.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());
// select glyphs that have not been requested yet.
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.
if let Some(ref glyph) = job.result {
let bpp = 4; // We always render glyphs in 32 bits RGBA format.
assert_eq!(
glyph.bytes.len(),
bpp * (glyph.width * glyph.height) as usize
);
}
job
})
.collect();
- glyph_tx.send(jobs).unwrap();
+ glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
});
}
pub fn get_glyph_dimensions(
&mut self,
font: &FontInstance,
glyph_key: &GlyphKey,
) -> Option<GlyphDimensions> {
@@ -488,82 +481,70 @@ impl GlyphRasterizer {
pub fn resolve_glyphs(
&mut self,
glyph_cache: &mut GlyphCache,
texture_cache: &mut TextureCache,
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),
// we can schedule removing the fonts if needed.
if !self.fonts_to_remove.is_empty() {
let font_contexts = Arc::clone(&self.font_contexts);
let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
self.workers.spawn(move || {
@@ -578,17 +559,17 @@ 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();
}
}
impl FontContext {
fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
match template {
&FontTemplate::Raw(ref bytes, index) => {
@@ -614,20 +595,25 @@ impl GlyphRequest {
GlyphRequest {
key: key.clone(),
font: font.clone(),
}
}
}
struct GlyphRasterJob {
- request: GlyphRequest,
+ key: GlyphKey,
result: Option<RasterizedGlyph>,
}
+struct GlyphRasterJobs {
+ font: FontInstance,
+ jobs: Vec<GlyphRasterJob>,
+}
+
#[test]
fn rasterize_200_glyphs() {
// This test loads a font from disc, the renders 4 requests containing
// 50 glyphs each, deletes the font and waits for the result.
use rayon::ThreadPoolBuilder;
use std::fs::File;
use std::io::Read;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -217,18 +217,17 @@ pub enum BrushKind {
format: YuvFormat,
color_space: YuvColorSpace,
image_rendering: ImageRendering,
},
RadialGradient {
gradient_index: CachedGradientIndex,
stops_range: ItemRange<GradientStop>,
extend_mode: ExtendMode,
- start_center: LayerPoint,
- end_center: LayerPoint,
+ center: LayerPoint,
start_radius: f32,
end_radius: f32,
ratio_xy: f32,
},
LinearGradient {
gradient_index: CachedGradientIndex,
stops_range: ItemRange<GradientStop>,
stops_count: usize,
@@ -374,28 +373,28 @@ impl BrushPrimitive {
]);
request.push([
pack_as_float(extend_mode as u32),
0.0,
0.0,
0.0,
]);
}
- BrushKind::RadialGradient { start_center, end_center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
+ BrushKind::RadialGradient { center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
request.push([
- start_center.x,
- start_center.y,
- end_center.x,
- end_center.y,
+ center.x,
+ center.y,
+ start_radius,
+ end_radius,
]);
request.push([
- start_radius,
- end_radius,
ratio_xy,
pack_as_float(extend_mode as u32),
+ 0.,
+ 0.,
]);
}
}
}
}
// Key that identifies a unique (partial) image that is being
// stored in the render task cache.
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
/* 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::{ApiMsg, FrameMsg};
+use api::{ApiMsg, FrameMsg, SceneMsg};
use bincode::{serialize, Infinite};
use byteorder::{LittleEndian, WriteBytesExt};
use std::any::TypeId;
use std::fmt::Debug;
use std::fs::File;
use std::io::Write;
use std::mem;
use std::path::PathBuf;
@@ -62,21 +62,33 @@ impl ApiRecordingReceiver for BinaryReco
}
pub fn should_record_msg(msg: &ApiMsg) -> bool {
match *msg {
ApiMsg::UpdateResources(..) |
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,
}
}
false
}
_ => false,
}
}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -690,18 +690,19 @@ impl RenderBackend {
}
fn next_namespace_id(&self) -> IdNamespace {
IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
}
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() {
match msg {
SceneBuilderResult::Transaction {
document_id,
mut built_scene,
resource_updates,
@@ -735,32 +736,29 @@ impl RenderBackend {
&mut frame_counter,
&mut profile_counters
);
}
}
}
}
- 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);
}
self.process_api_msg(msg, &mut profile_counters, &mut frame_counter)
}
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(
&mut self,
msg: ApiMsg,
profile_counters: &mut BackendProfileCounters,
frame_counter: &mut u32,
) -> bool {
@@ -960,35 +958,35 @@ impl RenderBackend {
for frame_msg in transaction_msg.frame_ops {
let _timer = profile_counters.total_time.timer();
op.combine(self.process_frame_msg(document_id, frame_msg));
}
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;
}
if doc.current.scene.root_pipeline_id.is_some() {
op.render = true;
op.composite = true;
}
}
+ 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 {
profile_scope!("generate frame");
*frame_counter += 1;
// borrow ck hack for profile_counters
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -928,16 +928,17 @@ impl RenderTaskCache {
texture_cache.update(
&mut cache_entry.handle,
descriptor,
TextureFilter::Linear,
None,
[0.0; 3],
None,
gpu_cache,
+ None,
);
// Get the allocation details in the texture cache, and store
// this in the render task. The renderer will draw this
// task into the appropriate layer and rect of the texture
// cache on this frame.
let (texture_id, texture_layer, uv_rect) =
texture_cache.get_cache_location(&cache_entry.handle);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -30,17 +30,17 @@ use debug_server::{self, DebugServer};
use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
use device::{ExternalTexture, FBOId, TextureSlot};
use device::{FileWatcherHandler, ShaderError, TextureFilter,
VertexUsageHint, VAO, VBO, CustomVAO};
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};
use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
use internal_types::{RenderTargetInfo, SavedTargetIndex};
use picture::ContentOrigin;
use prim_store::DeferredResolve;
@@ -1304,19 +1304,16 @@ impl Renderer {
);
register_thread_with_profiler("Compositor".to_owned());
device.begin_frame();
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 {
let dither_matrix: [u8; 64] = [
00,
48,
12,
60,
@@ -1484,21 +1481,17 @@ impl Renderer {
});
let enable_render_on_scroll = options.enable_render_on_scroll;
let blob_image_renderer = options.blob_image_renderer.take();
let thread_listener_for_render_backend = thread_listener.clone();
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 || {
register_thread_with_profiler(scene_thread_name.clone());
if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
thread_listener.thread_started(&scene_thread_name);
}
@@ -1510,16 +1503,24 @@ impl Renderer {
}
})?;
thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
register_thread_with_profiler(rb_thread_name.clone());
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,
result_tx,
scene_tx,
scene_rx,
device_pixel_ratio,
resource_cache,
@@ -1547,17 +1548,17 @@ impl Renderer {
pending_gpu_cache_updates: Vec::new(),
pending_shader_updates: Vec::new(),
shaders,
debug: debug_renderer,
debug_flags,
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,
last_time: 0,
gpu_profile,
prim_vao,
blur_vao,
clip_vao,
@@ -2353,24 +2354,16 @@ impl Renderer {
fn draw_instanced_batch<T>(
&mut self,
data: &[T],
vertex_array_kind: VertexArrayKind,
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],
TextureSampler::color(i),
&mut self.device,
);
}
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -14,26 +14,21 @@ use api::{TileOffset, TileSize};
use app_units::Au;
#[cfg(feature = "capture")]
use capture::ExternalCaptureImage;
#[cfg(feature = "replay")]
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};
use std::cmp;
use std::fmt::Debug;
use std::hash::Hash;
use std::mem;
#[cfg(any(feature = "capture", feature = "replay"))]
@@ -138,56 +133,50 @@ impl ImageTemplates {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct CachedImageInfo {
texture_cache_handle: TextureCacheHandle,
epoch: Epoch,
}
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum ResourceClassCacheError {
- OverLimitSize,
-}
-
-pub type ResourceCacheResult<V> = Result<V, ResourceClassCacheError>;
-
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ResourceClassCache<K: Hash + Eq, V> {
- resources: FastHashMap<K, ResourceCacheResult<V>>,
+pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
+ resources: FastHashMap<K, V>,
+ pub user_data: U,
}
-impl<K, V> ResourceClassCache<K, V>
+impl<K, V, U> ResourceClassCache<K, V, U>
where
K: Clone + Hash + Eq + Debug,
+ U: Default,
{
- pub fn new() -> ResourceClassCache<K, V> {
+ pub fn new() -> ResourceClassCache<K, V, U> {
ResourceClassCache {
resources: FastHashMap::default(),
+ user_data: Default::default(),
}
}
- fn get(&self, key: &K) -> &ResourceCacheResult<V> {
+ 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<V>) {
+ pub fn insert(&mut self, key: K, value: V) {
self.resources.insert(key, value);
}
- pub fn get_mut(&mut self, key: &K) -> &mut ResourceCacheResult<V> {
+ 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<K, ResourceCacheResult<V>> {
+ pub fn entry(&mut self, key: K) -> Entry<K, V> {
self.resources.entry(key)
}
pub fn clear(&mut self) {
self.resources.clear();
}
fn clear_keys<F>(&mut self, key_fun: F)
@@ -198,16 +187,23 @@ where
.keys()
.filter(&key_fun)
.cloned()
.collect::<Vec<_>>();
for key in resources_to_destroy {
let _ = self.resources.remove(&key).unwrap();
}
}
+
+ pub fn retain<F>(&mut self, f: F)
+ where
+ F: FnMut(&K, &mut V) -> bool,
+ {
+ self.resources.retain(f);
+ }
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ImageRequest {
pub key: ImageKey,
@@ -219,17 +215,24 @@ impl Into<BlobImageRequest> for ImageReq
fn into(self) -> BlobImageRequest {
BlobImageRequest {
key: self.key,
tile: self.tile,
}
}
}
-type ImageCache = ResourceClassCache<ImageRequest, CachedImageInfo>;
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ImageCacheError {
+ OverLimitSize,
+}
+
+type ImageCache = ResourceClassCache<ImageRequest, Result<CachedImageInfo, ImageCacheError>, ()>;
pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, FontInstance>>>;
#[derive(Default)]
struct Resources {
font_templates: FastHashMap<FontKey, FontTemplate>,
font_instances: FontInstanceMap,
image_templates: ImageTemplates,
}
@@ -268,34 +271,32 @@ pub struct ResourceCache {
pending_image_requests: FastHashSet<ImageRequest>,
blob_image_renderer: Option<Box<BlobImageRenderer>>,
}
impl ResourceCache {
pub fn new(
texture_cache: TextureCache,
- workers: Arc<ThreadPool>,
+ glyph_rasterizer: GlyphRasterizer,
blob_image_renderer: Option<Box<BlobImageRenderer>>,
- ) -> Result<Self, ResourceCacheError> {
- let glyph_rasterizer = GlyphRasterizer::new(workers)?;
-
- Ok(ResourceCache {
+ ) -> Self {
+ ResourceCache {
cached_glyphs: GlyphCache::new(),
cached_images: ResourceClassCache::new(),
cached_render_tasks: RenderTaskCache::new(),
resources: Resources::default(),
cached_glyph_dimensions: FastHashMap::default(),
texture_cache,
state: State::Idle,
current_frame_id: FrameId(0),
pending_image_requests: FastHashSet::default(),
glyph_rasterizer,
blob_image_renderer,
- })
+ }
}
pub fn max_texture_size(&self) -> u32 {
self.texture_cache.max_texture_size()
}
fn should_tile(limit: u32, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
let size_check = descriptor.width > limit || descriptor.height > limit;
@@ -561,17 +562,17 @@ impl ResourceCache {
let side_size =
template.tiling.map_or(cmp::max(template.descriptor.width, template.descriptor.height),
|tile_size| tile_size as u32);
if side_size > self.texture_cache.max_texture_size() {
// 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;
}
// If this image exists in the texture cache, *and* the epoch
// in the cache matches that of the template, then it is
// valid to use as-is.
let (entry, needs_update) = match self.cached_images.entry(request) {
Occupied(entry) => {
@@ -677,17 +678,17 @@ impl ResourceCache {
self.glyph_rasterizer.prepare_font(&mut font);
let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
let mut current_texture_id = SourceTexture::Invalid;
let mut current_glyph_format = GlyphFormat::Subpixel;
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 {
if !fetch_buffer.is_empty() {
f(current_texture_id, current_glyph_format, fetch_buffer);
fetch_buffer.clear();
}
current_texture_id = cache_item.texture_id;
@@ -789,16 +790,17 @@ impl ResourceCache {
})
.collect()
}
pub fn begin_frame(&mut self, frame_id: FrameId) {
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;
}
pub fn block_until_all_resources_added(
&mut self,
gpu_cache: &mut GpuCache,
texture_cache_profile: &mut TextureCacheProfileCounters,
@@ -923,16 +925,17 @@ impl ResourceCache {
self.texture_cache.update(
&mut entry.texture_cache_handle,
descriptor,
filter,
Some(image_data),
[0.0; 3],
image_template.dirty_rect,
gpu_cache,
+ None,
);
image_template.dirty_rect = None;
}
}
pub fn end_frame(&mut self) {
debug_assert_eq!(self.state, State::QueryResources);
self.state = State::Idle;
@@ -1030,28 +1033,28 @@ pub struct PlainResources {
font_instances: FastHashMap<FontInstanceKey, FontInstance>,
image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
}
#[cfg(feature = "capture")]
#[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,
textures: &'a TextureCache,
}
#[cfg(feature = "replay")]
#[derive(Deserialize)]
pub struct PlainCacheOwn {
current_frame_id: FrameId,
- glyphs: PlainGlyphCacheOwn,
+ glyphs: GlyphCache,
glyph_dimensions: GlyphDimensionsCache,
images: ImageCache,
render_tasks: RenderTaskCache,
textures: TextureCache,
}
#[cfg(feature = "replay")]
const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
@@ -1223,74 +1226,20 @@ impl ResourceCache {
})
.collect(),
};
(resources, external_images)
}
#[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,
textures: &self.texture_cache,
}
}
#[cfg(feature = "replay")]
@@ -1306,57 +1255,18 @@ impl ResourceCache {
info!("loading resource cache");
//TODO: instead of filling the local path to Arc<data> map as we process
// each of the resource types, we could go through all of the local paths
// and fill out the map as the first step.
let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
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;
self.texture_cache = cached.textures;
}
None => {
self.current_frame_id = FrameId(0);
self.cached_glyphs.clear();
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -9,18 +9,20 @@ use device::TextureFilter;
use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
use gpu_cache::{GpuCache, GpuCacheHandle};
use gpu_types::ImageSource;
use internal_types::{CacheTextureId, FastHashMap, TextureUpdateList, TextureUpdateSource};
use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpdateOp};
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.
const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4;
const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1;
// The dimensions of each layer in the texture cache.
const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
@@ -98,16 +100,18 @@ struct CacheEntry {
last_access: FrameId,
// Handle to the resource rect in the GPU cache.
uv_rect_handle: GpuCacheHandle,
// Image format of the item.
format: ImageFormat,
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<EvictionNotice>,
}
impl CacheEntry {
// Create a new entry for a standalone texture.
fn new_standalone(
texture_id: CacheTextureId,
size: DeviceUintSize,
format: ImageFormat,
@@ -119,16 +123,17 @@ impl CacheEntry {
size,
user_data,
last_access,
kind: EntryKind::Standalone,
texture_id,
format,
filter,
uv_rect_handle: GpuCacheHandle::new(),
+ eviction_notice: None,
}
}
// Update the GPU cache for this texture cache entry.
// This ensures that the UV rect, and texture layer index
// are up to date in the GPU cache for vertex shaders
// to fetch from.
fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
@@ -145,16 +150,22 @@ impl CacheEntry {
p0: origin.to_f32(),
p1: (origin + self.size).to_f32(),
texture_layer: layer_index,
user_data: self.user_data,
};
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<CacheEntry>;
// A texture cache handle is a weak reference to a cache entry.
// If the handle has not been inserted into the cache yet, the
// value will be None. Even when the value is Some(), the location
// may not actually be valid if it has been evicted by the cache.
@@ -168,16 +179,44 @@ pub struct TextureCacheHandle {
}
impl TextureCacheHandle {
pub fn new() -> Self {
TextureCacheHandle { entry: None }
}
}
+// 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<Cell<bool>>,
+}
+
+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 {
// A lazily allocated, fixed size, texture array for
// each format the texture cache supports.
array_rgba8_nearest: TextureArray,
array_a8_linear: TextureArray,
array_rgba8_linear: TextureArray,
@@ -296,16 +335,17 @@ impl TextureCache {
&mut self,
handle: &mut TextureCacheHandle,
descriptor: ImageDescriptor,
filter: TextureFilter,
data: Option<ImageData>,
user_data: [f32; 3],
mut dirty_rect: Option<DeviceUintRect>,
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
// is true:
// - Never been in the cache
// - Has been in the cache but was evicted.
// - Exists in the cache but dimensions / format have changed.
let realloc = match handle.entry {
@@ -334,16 +374,19 @@ impl TextureCache {
// If we reallocated, we need to upload the whole item again.
dirty_rect = None;
}
let entry = self.entries
.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.
gpu_cache.invalidate(&entry.uv_rect_handle);
// Upload the resource rect and texture array layer.
entry.update_gpu_cache(gpu_cache);
@@ -493,16 +536,17 @@ impl TextureCache {
if eviction_candidates.len() > 32 {
let entries_to_keep = eviction_candidates.split_off(32);
retained_entries.extend(entries_to_keep);
}
// Free the selected items
for handle in eviction_candidates {
let entry = self.entries.free(handle);
+ entry.evict();
self.free(entry);
}
// Keep a record of the remaining handles for next frame.
self.standalone_entry_handles = retained_entries;
}
// Expire old shared items. Pass in the allocation size
@@ -547,16 +591,17 @@ impl TextureCache {
let mut freed_complete_page = false;
let mut evicted_items = 0;
for handle in eviction_candidates {
if evicted_items > 512 && (found_matching_slab || freed_complete_page) {
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();
}
evicted_items += 1;
}
}
@@ -1064,16 +1109,17 @@ impl TextureArray {
size: DeviceUintSize::new(width, height),
user_data,
last_access: frame_id,
kind,
uv_rect_handle: GpuCacheHandle::new(),
format: self.format,
filter: self.filter,
texture_id: self.texture_id.unwrap(),
+ eviction_notice: None,
}
})
}
}
impl TextureUpdate {
// Constructs a TextureUpdate operation to be passed to the
// rendering thread in order to do an upload to the right
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -413,21 +413,20 @@ pub struct GradientDisplayItem {
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct GradientStop {
pub offset: f32,
pub color: ColorF,
}
#[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<GradientStop>
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ClipChainItem {
pub id: ClipChainId,
pub parent: Option<ClipChainId>,
} // IMPLICIT stops: Vec<ClipId>
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1183,60 +1183,56 @@ impl DisplayListBuilder {
offset: 1.0,
color: last_color,
},
];
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,
};
}
let (start_offset, end_offset) =
DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
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,
}
}
// NOTE: gradients must be pushed in the order they're created
// 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<GradientStop>,
extend_mode: ExtendMode,
) -> RadialGradient {
self.push_stops(&stops);
RadialGradient {
- start_center,
+ center,
+ radius,
start_radius,
- end_center,
end_radius,
- ratio_xy,
extend_mode,
}
}
pub fn push_border(
&mut self,
info: &LayoutPrimitiveInfo,
widths: BorderWidths,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-486ee5f3aefb0172c2c5703e19f833e63eb295b9
+2083e83d958dd4a230ccae5c518e4bc8fbf88009
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -1,25 +1,25 @@
[package]
name = "wrench"
version = "0.3.0"
authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
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 }
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"
yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
serde_json = "1.0"
ron = "0.1.5"
time = "0.1"
crossbeam = "0.2"
@@ -33,11 +33,12 @@ serde = {version = "1.0", features = ["d
core-graphics = "0.13"
core-foundation = "0.5"
[features]
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"
new file mode 100644
--- /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 {}
+ }
+}
+
--- a/gfx/wrench/src/args.yaml
+++ b/gfx/wrench/src/args.yaml
@@ -39,16 +39,19 @@ args:
help: Disable subpixel aa
- slow_subpixel:
long: slow-subpixel
help: Disable dual source blending
- headless:
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
help: Device pixel ratio
takes_value: true
- size:
short: s
long: size
new file mode 100644
--- /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<ffi::egl::types::EGLSurface>,
+ 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<ContextPrototype<'a>, 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::<Vec<_>>()
+
+ } 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<String>,
+ 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<Context, CreationError>
+ {
+ 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<Context, CreationError> {
+ 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<Context, CreationError>
+ {
+ 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<c_int> = 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<ffi::egl::types::EGLContext, CreationError>
+{
+ 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)
+}
+
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -22,28 +22,32 @@ extern crate euclid;
extern crate font_loader;
extern crate gleam;
extern crate glutin;
extern crate image;
#[macro_use]
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;
#[macro_use]
extern crate serde;
extern crate serde_json;
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;
mod png;
mod premultiply;
mod rawtest;
mod reftest;
mod ron_frame_writer;
@@ -154,116 +158,142 @@ impl HeadlessContext {
#[cfg(not(feature = "headless"))]
fn get_proc_address(_: &str) -> *const c_void {
ptr::null() as *const _
}
}
pub enum WindowWrapper {
Window(glutin::GlWindow, Rc<gl::Gl>),
+ Angle(glutin::Window, angle::Context, Rc<gl::Gl>),
Headless(HeadlessContext, Rc<gl::Gl>),
}
pub struct HeadlessEventIterater;
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)
}
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,
}
}
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
}
}
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<gl::Gl> {
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(),
}
}
}
fn make_window(
size: DeviceUintSize,
dp_ratio: Option<f32>,
vsync: bool,
events_loop: &Option<glutin::EventsLoop>,
+ angle: bool,
) -> WindowWrapper {
let wrapper = match *events_loop {
Some(ref events_loop) => {
let context_builder = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::GlThenGles {
opengl_version: (3, 2),
opengles_version: (3, 0),
})
.with_vsync(vsync);
let window_builder = glutin::WindowBuilder::new()
.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() {
gl::GlType::Gl => unsafe {
gl::GlFns::load_with(|symbol| {
HeadlessContext::get_proc_address(symbol) as *const _
})
},
@@ -288,40 +318,50 @@ fn make_window(
"hidpi factor: {} (native {})",
dp_ratio,
wrapper.hidpi_factor()
);
wrapper
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum NotifierEvent {
+ WakeUp,
+ ShutDown,
+}
+
struct Notifier {
- tx: Sender<()>,
+ tx: Sender<NotifierEvent>,
}
// setup a notifier so we can wait for frames to be finished
impl RenderNotifier for Notifier {
fn clone(&self) -> Box<RenderNotifier> {
Box::new(Notifier {
tx: self.tx.clone(),
})
}
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) {
if !scrolled {
self.wake_up();
}
}
}
-fn create_notifier() -> (Box<RenderNotifier>, Receiver<()>) {
+fn create_notifier() -> (Box<RenderNotifier>, Receiver<NotifierEvent>) {
let (tx, rx) = channel();
(Box::new(Notifier { tx: tx }), rx)
}
fn main() {
#[cfg(feature = "logging")]
env_logger::init();
@@ -359,17 +399,19 @@ fn main() {
let zoom_factor = args.value_of("zoom").map(|z| z.parse::<f32>().unwrap());
let mut events_loop = if args.is_present("headless") {
None
} else {
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();
let needs_frame_notifier = ["perf", "reftest", "png", "rawtest"]
.iter()
.any(|s| args.subcommand_matches(s).is_some());
let (notifier, rx) = if needs_frame_notifier {
let (notifier, rx) = create_notifier();
@@ -415,27 +457,29 @@ fn main() {
let dim = window.get_inner_size();
let base_manifest = Path::new("reftests/reftest.list");
let specific_reftest = subargs.value_of("REFTEST").map(|x| Path::new(x));
let mut reftest_options = ReftestOptions::default();
if let Some(allow_max_diff) = subargs.value_of("fuzz_tolerance") {
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
// a new displaty list each frame.
wrench.rebuild_display_lists = true;
let harness = PerfHarness::new(&mut wrench, &mut window, rx.unwrap());
let base_manifest = Path::new("benchmarks/benchmarks.list");
let filename = subargs.value_of("filename").unwrap();
--- a/gfx/wrench/src/perf.rs
+++ b/gfx/wrench/src/perf.rs
@@ -1,12 +1,13 @@
/* 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 NotifierEvent;
use WindowWrapper;
use serde_json;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::mpsc::Receiver;
@@ -118,21 +119,21 @@ impl Profile {
(hash_set, hash_map)
}
}
pub struct PerfHarness<'a> {
wrench: &'a mut Wrench,
window: &'a mut WindowWrapper,
- rx: Receiver<()>,
+ rx: Receiver<NotifierEvent>,
}
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<NotifierEvent>) -> Self {
PerfHarness { wrench, window, rx }
}
pub fn run(mut self, base_manifest: &Path, filename: &str) {
let manifest = BenchmarkManifest::new(base_manifest);
let mut profile = Profile::new();
--- a/gfx/wrench/src/png.rs
+++ b/gfx/wrench/src/png.rs
@@ -1,13 +1,13 @@
/* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
use image::png::PNGEncoder;
use image::{self, ColorType, GenericImage};
use std::fs::File;
use std::path::Path;
use std::sync::mpsc::Receiver;
use webrender::api::*;
use wrench::{Wrench, WrenchThing};
use yaml_frame_reader::YamlFrameReader;
@@ -72,17 +72,17 @@ pub fn save_flipped<P: Clone + AsRef<Pat
})
}
pub fn png(
wrench: &mut Wrench,
surface: ReadSurface,
window: &mut WindowWrapper,
mut reader: YamlFrameReader,
- rx: Receiver<()>,
+ rx: Receiver<NotifierEvent>,
) {
reader.do_frame(wrench);
// wait for the frame
rx.recv().unwrap();
wrench.render();
let (device_size, data, settings) = match surface {
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -1,41 +1,41 @@
/* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
use blob;
use euclid::{TypedRect, TypedSize2D, TypedPoint2D};
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::mpsc::Receiver;
use webrender::api::*;
use wrench::Wrench;
pub struct RawtestHarness<'a> {
wrench: &'a mut Wrench,
- rx: Receiver<()>,
+ rx: &'a Receiver<NotifierEvent>,
window: &'a mut WindowWrapper,
}
fn point<T: Copy, U>(x: T, y: T) -> TypedPoint2D<T, U> {
TypedPoint2D::new(x, y)
}
fn size<T: Copy, U>(x: T, y: T) -> TypedSize2D<T, U> {
TypedSize2D::new(x, y)
}
fn rect<T: Copy, U>(x: T, y: T, width: T, height: T) -> TypedRect<T, U> {
TypedRect::new(point(x, y), size(width, height))
}
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<NotifierEvent>) -> Self {
RawtestHarness {
wrench,
rx,
window,
}
}
pub fn run(mut self) {
--- a/gfx/wrench/src/reftest.rs
+++ b/gfx/wrench/src/reftest.rs
@@ -1,13 +1,13 @@
/* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
use base64;
use image::load as load_piston_image;
use image::png::PNGEncoder;
use image::{ColorType, ImageFormat};
use parse_function::parse_function;
use png::save_flipped;
use std::cmp;
use std::fmt::{Display, Error, Formatter};
@@ -287,20 +287,20 @@ impl ReftestManifest {
})
.collect()
}
}
pub struct ReftestHarness<'a> {
wrench: &'a mut Wrench,
window: &'a mut WindowWrapper,
- rx: Receiver<()>,
+ rx: &'a Receiver<NotifierEvent>,
}
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<NotifierEvent>) -> Self {
ReftestHarness { wrench, window, rx }
}
pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize {
let manifest = ReftestManifest::new(base_manifest, options);
let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));
let mut total_passing = 0;
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -11,22 +11,23 @@ use dwrote;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use font_loader::system_fonts;
use glutin::EventsLoopProxy;
use json_frame_writer::JsonFrameWriter;
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
// use better types for things like the style and stretch.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FontDescriptor {
Path { path: PathBuf, font_index: u32 },
Family { name: String },
@@ -585,9 +586,23 @@ impl Wrench {
let x = self.device_pixel_ratio * (15.0 + co.1);
let mut y = self.device_pixel_ratio * (15.0 + co.1 + dr.line_height());
for ref line in &help_lines {
dr.add_text(x, y, line, co.0.into());
y += self.device_pixel_ratio * dr.line_height();
}
}
}
+
+ pub fn shut_down(self, rx: Receiver<NotifierEvent>) {
+ 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();
+ }
}
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -594,23 +594,20 @@ impl YamlFrameReader {
} else {
ExtendMode::Clamp
};
dl.create_gradient(start, end, stops, extend_mode)
}
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")
.chunks(2)
.map(|chunk| {
GradientStop {
offset: chunk[0]
.as_force_f32()
@@ -624,29 +621,22 @@ impl YamlFrameReader {
let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
ExtendMode::Repeat
} else {
ExtendMode::Clamp
};
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")
.chunks(2)
.map(|chunk| {
GradientStop {
offset: chunk[0]
.as_force_f32()
@@ -659,21 +649,20 @@ impl YamlFrameReader {
.collect::<Vec<_>>();
let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
ExtendMode::Repeat
} else {
ExtendMode::Clamp
};
dl.create_complex_radial_gradient(
- start_center,
+ center,
+ radius,
start_radius,
- end_center,
end_radius,
- ratio_xy,
stops,
extend_mode,
)
}
}
fn handle_rect(
&mut self,
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -900,21 +900,20 @@ impl YamlFrameWriter {
let outset: Vec<f32> = vec![
details.outset.top,
details.outset.right,
details.outset.bottom,
details.outset.left,
];
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()));
stops.push(Yaml::String(color_to_string(stop.color)));
}
yaml_node(&mut v, "stops", Yaml::Array(stops));
bool_node(
&mut v,
@@ -956,21 +955,20 @@ impl YamlFrameWriter {
bool_node(
&mut v,
"repeat",
item.gradient.extend_mode == ExtendMode::Repeat,
);
}
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![];
for stop in display_list.get(base.gradient_stops()) {
stops.push(Yaml::Real(stop.offset.to_string()));
stops.push(Yaml::String(color_to_string(stop.color)));
}
yaml_node(&mut v, "stops", Yaml::Array(stops));