mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1492880. Update webrender to commit a601f9c291cee83257241ef61aaf62353c613438
This commit is contained in:
parent
fde56b2dde
commit
bb303b7ca0
@ -46,20 +46,24 @@ ws = { optional = true, version = "0.7.3" }
|
||||
|
||||
[dependencies.pathfinder_font_renderer]
|
||||
git = "https://github.com/pcwalton/pathfinder"
|
||||
branch = "webrender"
|
||||
optional = true
|
||||
# Uncomment to test FreeType on macOS:
|
||||
# features = ["freetype"]
|
||||
|
||||
[dependencies.pathfinder_gfx_utils]
|
||||
git = "https://github.com/pcwalton/pathfinder"
|
||||
branch = "webrender"
|
||||
optional = true
|
||||
|
||||
[dependencies.pathfinder_partitioner]
|
||||
git = "https://github.com/pcwalton/pathfinder"
|
||||
branch = "webrender"
|
||||
optional = true
|
||||
|
||||
[dependencies.pathfinder_path_utils]
|
||||
git = "https://github.com/pcwalton/pathfinder"
|
||||
branch = "webrender"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
@ -73,5 +77,5 @@ dwrote = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.16"
|
||||
core-text = { version = "11", default-features = false }
|
||||
core-graphics = "0.17.1"
|
||||
core-text = { version = "13", default-features = false }
|
||||
|
@ -59,19 +59,16 @@ ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
|
||||
vec2 device_pos = area.screen_origin +
|
||||
aPosition.xy * area.common_data.task_rect.size;
|
||||
|
||||
// TODO(gw): We only check the clip transform matrix here. We should
|
||||
// probably also be checking the prim_transform matrix. I
|
||||
// have left it as is for now, since that matches the
|
||||
// previous behavior.
|
||||
if (clip_transform.is_axis_aligned) {
|
||||
if (clip_transform.is_axis_aligned && prim_transform.is_axis_aligned) {
|
||||
mat4 snap_mat = clip_transform.m * prim_transform.inv_m;
|
||||
vec4 snap_positions = compute_snap_positions(
|
||||
clip_transform.m,
|
||||
snap_mat,
|
||||
local_clip_rect
|
||||
);
|
||||
|
||||
vec2 snap_offsets = compute_snap_offset_impl(
|
||||
device_pos,
|
||||
clip_transform.m,
|
||||
snap_mat,
|
||||
local_clip_rect,
|
||||
RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
|
||||
snap_positions
|
||||
|
@ -10,21 +10,21 @@ use core_foundation::base::TCFType;
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
use core_foundation::number::{CFNumber, CFNumberRef};
|
||||
use core_foundation::string::{CFString, CFStringRef};
|
||||
use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little};
|
||||
#[cfg(not(feature = "pathfinder"))]
|
||||
use core_graphics::base::kCGImageAlphaPremultipliedFirst;
|
||||
use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst};
|
||||
use core_graphics::base::{kCGBitmapByteOrder32Little};
|
||||
use core_graphics::color_space::CGColorSpace;
|
||||
use core_graphics::context::CGContext;
|
||||
#[cfg(not(feature = "pathfinder"))]
|
||||
use core_graphics::context::CGTextDrawingMode;
|
||||
use core_graphics::context::{CGBlendMode, CGTextDrawingMode};
|
||||
use core_graphics::data_provider::CGDataProvider;
|
||||
use core_graphics::font::{CGFont, CGGlyph};
|
||||
use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
|
||||
#[cfg(not(feature = "pathfinder"))]
|
||||
use core_graphics::geometry::CGRect;
|
||||
use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect};
|
||||
use core_text;
|
||||
use core_text::font::{CTFont, CTFontRef};
|
||||
use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
|
||||
use euclid::Size2D;
|
||||
use gamma_lut::{ColorLut, GammaLut};
|
||||
use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
@ -35,10 +35,14 @@ use internal_types::{FastHashMap, ResourceCacheError};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
|
||||
const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32;
|
||||
|
||||
pub struct FontContext {
|
||||
cg_fonts: FastHashMap<FontKey, CGFont>,
|
||||
ct_fonts: FastHashMap<(FontKey, Au, Vec<FontVariation>), CTFont>,
|
||||
#[allow(dead_code)]
|
||||
graphics_context: GraphicsContext,
|
||||
#[allow(dead_code)]
|
||||
gamma_lut: GammaLut,
|
||||
}
|
||||
|
||||
@ -116,7 +120,9 @@ fn get_glyph_metrics(
|
||||
}
|
||||
|
||||
let mut advance = CGSize { width: 0.0, height: 0.0 };
|
||||
ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
|
||||
unsafe {
|
||||
ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
|
||||
}
|
||||
|
||||
if bounds.size.width > 0.0 {
|
||||
bounds.size.width += extra_width;
|
||||
@ -281,6 +287,7 @@ impl FontContext {
|
||||
Ok(FontContext {
|
||||
cg_fonts: FastHashMap::default(),
|
||||
ct_fonts: FastHashMap::default(),
|
||||
graphics_context: GraphicsContext::new(),
|
||||
gamma_lut: GammaLut::new(contrast, gamma, gamma),
|
||||
})
|
||||
}
|
||||
@ -344,12 +351,14 @@ impl FontContext {
|
||||
|
||||
self.get_ct_font(font_key, Au::from_px(16), &[])
|
||||
.and_then(|ref ct_font| {
|
||||
let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);
|
||||
unsafe {
|
||||
let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);
|
||||
|
||||
if result {
|
||||
Some(glyph as u32)
|
||||
} else {
|
||||
None
|
||||
if result {
|
||||
Some(glyph as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -494,11 +503,17 @@ impl FontContext {
|
||||
None => return GlyphRasterResult::LoadFailed,
|
||||
};
|
||||
|
||||
let bitmap = is_bitmap_font(&ct_font);
|
||||
let (mut shape, (x_offset, y_offset)) = if bitmap {
|
||||
(FontTransform::identity(), (0.0, 0.0))
|
||||
let glyph_type = if is_bitmap_font(&ct_font) {
|
||||
GlyphType::Bitmap
|
||||
} else {
|
||||
(font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
|
||||
GlyphType::Vector
|
||||
};
|
||||
|
||||
let (mut shape, (x_offset, y_offset)) = match glyph_type {
|
||||
GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)),
|
||||
GlyphType::Vector => {
|
||||
(font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
|
||||
}
|
||||
};
|
||||
if font.flags.contains(FontInstanceFlags::FLIP_X) {
|
||||
shape = shape.flip_x();
|
||||
@ -526,7 +541,12 @@ impl FontContext {
|
||||
};
|
||||
|
||||
let glyph = key.index() as CGGlyph;
|
||||
let (strike_scale, pixel_step) = if bitmap { (y_scale, 1.0) } else { (x_scale, y_scale / x_scale) };
|
||||
let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap {
|
||||
(y_scale, 1.0)
|
||||
} else {
|
||||
(x_scale, y_scale / x_scale)
|
||||
};
|
||||
|
||||
let extra_strikes = font.get_extra_strikes(strike_scale / scale);
|
||||
let metrics = get_glyph_metrics(
|
||||
&ct_font,
|
||||
@ -540,43 +560,7 @@ impl FontContext {
|
||||
return GlyphRasterResult::LoadFailed
|
||||
}
|
||||
|
||||
// The result of this function, in all render modes, is going to be a
|
||||
// BGRA surface with white text on transparency using premultiplied
|
||||
// alpha. For subpixel text, the RGB values will be the mask value for
|
||||
// the individual components. For bitmap glyphs, the RGB values will be
|
||||
// the (premultiplied) color of the pixel. For Alpha and Mono, each
|
||||
// pixel will have R==G==B==A at the end of this function.
|
||||
// We access the color channels in little-endian order.
|
||||
// The CGContext will create and own our pixel buffer.
|
||||
// In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
|
||||
// an opaque background. In order to hit the most efficient path in CG
|
||||
// for this, we will tell CG that the CGContext is opaque, by passing
|
||||
// an "[...]AlphaNone[...]" context flag. This creates a slight
|
||||
// contradiction to the way we use the buffer after CG is done with it,
|
||||
// because we will convert it into text-on-transparency. But that's ok;
|
||||
// we still get four bytes per pixel and CG won't mess with the alpha
|
||||
// channel after we've stopped calling CG functions. We just need to
|
||||
// make sure that we don't look at the alpha values of the pixels that
|
||||
// we get from CG, and compute our own alpha value only from RGB.
|
||||
// Note that CG requires kCGBitmapByteOrder32Little in order to do
|
||||
// subpixel AA at all (which we need it to do in both Subpixel and
|
||||
// Alpha+smoothing mode). But little-endian is what we want anyway, so
|
||||
// this works out nicely.
|
||||
let context_flags = if bitmap {
|
||||
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst
|
||||
} else {
|
||||
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst
|
||||
};
|
||||
|
||||
let mut cg_context = CGContext::create_bitmap_context(
|
||||
None,
|
||||
metrics.rasterized_width as usize,
|
||||
metrics.rasterized_height as usize,
|
||||
8,
|
||||
metrics.rasterized_width as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
context_flags,
|
||||
);
|
||||
let raster_size = Size2D::new(metrics.rasterized_width, metrics.rasterized_height);
|
||||
|
||||
// If the font render mode is Alpha, we support two different ways to
|
||||
// compute the grayscale mask, depending on the value of the platform
|
||||
@ -603,74 +587,92 @@ impl FontContext {
|
||||
// the "Alpha without smoothing" and Mono modes.
|
||||
let use_white_on_black = should_use_white_on_black(font.color);
|
||||
let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING);
|
||||
let (antialias, smooth, text_color, bg_color, bg_alpha, invert) = if bitmap {
|
||||
(true, false, 0.0, 0.0, 0.0, false)
|
||||
} else {
|
||||
match (font.render_mode, use_font_smoothing) {
|
||||
(FontRenderMode::Subpixel, _) |
|
||||
(FontRenderMode::Alpha, true) => if use_white_on_black {
|
||||
(true, true, 1.0, 0.0, 1.0, false)
|
||||
} else {
|
||||
(true, true, 0.0, 1.0, 1.0, true)
|
||||
},
|
||||
(FontRenderMode::Alpha, false) => (true, false, 0.0, 1.0, 1.0, true),
|
||||
(FontRenderMode::Mono, _) => (false, false, 0.0, 1.0, 1.0, true),
|
||||
let (antialias, smooth, text_color, bg_color, bg_alpha, invert) = match glyph_type {
|
||||
GlyphType::Bitmap => (true, false, 0.0, 0.0, 0.0, false),
|
||||
GlyphType::Vector => {
|
||||
match (font.render_mode, use_font_smoothing) {
|
||||
(FontRenderMode::Subpixel, _) |
|
||||
(FontRenderMode::Alpha, true) => if use_white_on_black {
|
||||
(true, true, 1.0, 0.0, 1.0, false)
|
||||
} else {
|
||||
(true, true, 0.0, 1.0, 1.0, true)
|
||||
},
|
||||
(FontRenderMode::Alpha, false) => (true, false, 0.0, 1.0, 1.0, true),
|
||||
(FontRenderMode::Mono, _) => (false, false, 0.0, 1.0, 1.0, true),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// These are always true in Gecko, even for non-AA fonts
|
||||
cg_context.set_allows_font_subpixel_positioning(true);
|
||||
cg_context.set_should_subpixel_position_fonts(true);
|
||||
{
|
||||
let cg_context = self.graphics_context.get_context(&raster_size, glyph_type);
|
||||
|
||||
// Don't quantize because we're doing it already.
|
||||
cg_context.set_allows_font_subpixel_quantization(false);
|
||||
cg_context.set_should_subpixel_quantize_fonts(false);
|
||||
// These are always true in Gecko, even for non-AA fonts
|
||||
cg_context.set_allows_font_subpixel_positioning(true);
|
||||
cg_context.set_should_subpixel_position_fonts(true);
|
||||
|
||||
cg_context.set_should_smooth_fonts(smooth);
|
||||
cg_context.set_should_antialias(antialias);
|
||||
// Don't quantize because we're doing it already.
|
||||
cg_context.set_allows_font_subpixel_quantization(false);
|
||||
cg_context.set_should_subpixel_quantize_fonts(false);
|
||||
|
||||
// Fill the background. This could be opaque white, opaque black, or
|
||||
// transparency.
|
||||
cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_alpha);
|
||||
let rect = CGRect {
|
||||
origin: CGPoint { x: 0.0, y: 0.0 },
|
||||
size: CGSize {
|
||||
width: metrics.rasterized_width as f64,
|
||||
height: metrics.rasterized_height as f64,
|
||||
},
|
||||
};
|
||||
cg_context.fill_rect(rect);
|
||||
cg_context.set_should_smooth_fonts(smooth);
|
||||
cg_context.set_should_antialias(antialias);
|
||||
|
||||
// Set the text color and draw the glyphs.
|
||||
cg_context.set_rgb_fill_color(text_color, text_color, text_color, 1.0);
|
||||
cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
|
||||
// Fill the background. This could be opaque white, opaque black, or
|
||||
// transparency.
|
||||
cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_alpha);
|
||||
let rect = CGRect {
|
||||
origin: CGPoint { x: 0.0, y: 0.0 },
|
||||
size: CGSize {
|
||||
width: metrics.rasterized_width as f64,
|
||||
height: metrics.rasterized_height as f64,
|
||||
},
|
||||
};
|
||||
|
||||
// CG Origin is bottom left, WR is top left. Need -y offset
|
||||
let mut draw_origin = CGPoint {
|
||||
x: -metrics.rasterized_left as f64 + x_offset,
|
||||
y: metrics.rasterized_descent as f64 - y_offset,
|
||||
};
|
||||
// Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER
|
||||
// operator, which can't clear to the transparent color!
|
||||
cg_context.set_blend_mode(CGBlendMode::Copy);
|
||||
cg_context.fill_rect(rect);
|
||||
cg_context.set_blend_mode(CGBlendMode::Normal);
|
||||
|
||||
if let Some(transform) = transform {
|
||||
cg_context.set_text_matrix(&transform);
|
||||
// Set the text color and draw the glyphs.
|
||||
cg_context.set_rgb_fill_color(text_color, text_color, text_color, 1.0);
|
||||
cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
|
||||
|
||||
draw_origin = draw_origin.apply_transform(&transform.invert());
|
||||
// CG Origin is bottom left, WR is top left. Need -y offset
|
||||
let mut draw_origin = CGPoint {
|
||||
x: -metrics.rasterized_left as f64 + x_offset,
|
||||
y: metrics.rasterized_descent as f64 - y_offset,
|
||||
};
|
||||
|
||||
if let Some(transform) = transform {
|
||||
cg_context.set_text_matrix(&transform);
|
||||
|
||||
draw_origin = draw_origin.apply_transform(&transform.invert());
|
||||
} else {
|
||||
// Make sure to reset this because some previous glyph rasterization might have
|
||||
// changed it.
|
||||
cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY);
|
||||
}
|
||||
|
||||
if extra_strikes > 0 {
|
||||
let strikes = 1 + extra_strikes;
|
||||
let glyphs = vec![glyph; strikes];
|
||||
let origins = (0..strikes).map(|i| {
|
||||
CGPoint {
|
||||
x: draw_origin.x + i as f64 * pixel_step,
|
||||
y: draw_origin.y,
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
ct_font.draw_glyphs(&glyphs, &origins, cg_context.clone());
|
||||
} else {
|
||||
ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if extra_strikes > 0 {
|
||||
let strikes = 1 + extra_strikes;
|
||||
let glyphs = vec![glyph; strikes];
|
||||
let origins = (0..strikes)
|
||||
.map(|i| CGPoint { x: draw_origin.x + i as f64 * pixel_step, y: draw_origin.y })
|
||||
.collect::<Vec<_>>();
|
||||
ct_font.draw_glyphs(&glyphs, &origins, cg_context.clone());
|
||||
} else {
|
||||
ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
|
||||
}
|
||||
let mut rasterized_pixels = self.graphics_context
|
||||
.get_rasterized_pixels(&raster_size, glyph_type);
|
||||
|
||||
let mut rasterized_pixels = cg_context.data().to_vec();
|
||||
|
||||
if !bitmap {
|
||||
if glyph_type == GlyphType::Vector {
|
||||
// We rendered text into an opaque surface. The code below needs to
|
||||
// ignore the current value of each pixel's alpha channel. But it's
|
||||
// allowed to write to the alpha channel, because we're done calling
|
||||
@ -722,8 +724,14 @@ impl FontContext {
|
||||
top: metrics.rasterized_ascent as f32,
|
||||
width: metrics.rasterized_width,
|
||||
height: metrics.rasterized_height,
|
||||
scale: (if bitmap { scale / y_scale } else { scale }) as f32,
|
||||
format: if bitmap { GlyphFormat::ColorBitmap } else { font.get_glyph_format() },
|
||||
scale: match glyph_type {
|
||||
GlyphType::Bitmap => (scale / y_scale) as f32,
|
||||
GlyphType::Vector => scale as f32,
|
||||
},
|
||||
format: match glyph_type {
|
||||
GlyphType::Bitmap => GlyphFormat::ColorBitmap,
|
||||
GlyphType::Vector => font.get_glyph_format(),
|
||||
},
|
||||
bytes: rasterized_pixels,
|
||||
})
|
||||
}
|
||||
@ -735,3 +743,109 @@ impl<'a> Into<CGFont> for NativeFontHandleWrapper<'a> {
|
||||
(self.0).0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// Avoids taking locks by recycling Core Graphics contexts.
|
||||
#[allow(dead_code)]
|
||||
struct GraphicsContext {
|
||||
vector_context: CGContext,
|
||||
vector_context_size: Size2D<u32>,
|
||||
bitmap_context: CGContext,
|
||||
bitmap_context_size: Size2D<u32>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new() -> GraphicsContext {
|
||||
let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH);
|
||||
GraphicsContext {
|
||||
vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector),
|
||||
vector_context_size: size,
|
||||
bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap),
|
||||
bitmap_context_size: size,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
|
||||
-> &mut CGContext {
|
||||
let (cached_context, cached_size) = match glyph_type {
|
||||
GlyphType::Vector => {
|
||||
(&mut self.vector_context, &mut self.vector_context_size)
|
||||
}
|
||||
GlyphType::Bitmap => {
|
||||
(&mut self.bitmap_context, &mut self.bitmap_context_size)
|
||||
}
|
||||
};
|
||||
let rounded_size = Size2D::new(size.width.next_power_of_two(),
|
||||
size.height.next_power_of_two());
|
||||
if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height {
|
||||
*cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width),
|
||||
u32::max(cached_size.height, rounded_size.height));
|
||||
*cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type);
|
||||
}
|
||||
cached_context
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
|
||||
-> Vec<u8> {
|
||||
let (cached_context, cached_size) = match glyph_type {
|
||||
GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size),
|
||||
GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size),
|
||||
};
|
||||
let cached_data = cached_context.data();
|
||||
let cached_stride = cached_size.width as usize * 4;
|
||||
|
||||
let result_len = size.width as usize * size.height as usize * 4;
|
||||
let mut result = Vec::with_capacity(result_len);
|
||||
for y in (cached_size.height - size.height)..cached_size.height {
|
||||
let cached_start = y as usize * cached_stride;
|
||||
let cached_end = cached_start + size.width as usize * 4;
|
||||
result.extend_from_slice(&cached_data[cached_start..cached_end]);
|
||||
}
|
||||
debug_assert_eq!(result.len(), result_len);
|
||||
result
|
||||
}
|
||||
|
||||
fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext {
|
||||
// The result of rasterization, in all render modes, is going to be a
|
||||
// BGRA surface with white text on transparency using premultiplied
|
||||
// alpha. For subpixel text, the RGB values will be the mask value for
|
||||
// the individual components. For bitmap glyphs, the RGB values will be
|
||||
// the (premultiplied) color of the pixel. For Alpha and Mono, each
|
||||
// pixel will have R==G==B==A at the end of this function.
|
||||
// We access the color channels in little-endian order.
|
||||
// The CGContext will create and own our pixel buffer.
|
||||
// In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
|
||||
// an opaque background. In order to hit the most efficient path in CG
|
||||
// for this, we will tell CG that the CGContext is opaque, by passing
|
||||
// an "[...]AlphaNone[...]" context flag. This creates a slight
|
||||
// contradiction to the way we use the buffer after CG is done with it,
|
||||
// because we will convert it into text-on-transparency. But that's ok;
|
||||
// we still get four bytes per pixel and CG won't mess with the alpha
|
||||
// channel after we've stopped calling CG functions. We just need to
|
||||
// make sure that we don't look at the alpha values of the pixels that
|
||||
// we get from CG, and compute our own alpha value only from RGB.
|
||||
// Note that CG requires kCGBitmapByteOrder32Little in order to do
|
||||
// subpixel AA at all (which we need it to do in both Subpixel and
|
||||
// Alpha+smoothing mode). But little-endian is what we want anyway, so
|
||||
// this works out nicely.
|
||||
let color_type = match glyph_type {
|
||||
GlyphType::Vector => kCGImageAlphaNoneSkipFirst,
|
||||
GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst,
|
||||
};
|
||||
|
||||
CGContext::create_bitmap_context(None,
|
||||
size.width as usize,
|
||||
size.height as usize,
|
||||
8,
|
||||
size.width as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
kCGBitmapByteOrder32Little | color_type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum GlyphType {
|
||||
Vector,
|
||||
Bitmap,
|
||||
}
|
||||
|
@ -1307,6 +1307,11 @@ impl LazyInitializedDebugRenderer {
|
||||
self.debug_renderer.as_mut()
|
||||
}
|
||||
|
||||
/// Returns mut ref to `DebugRenderer` if one already exists, otherwise returns `None`.
|
||||
pub fn try_get_mut<'a>(&'a mut self) -> Option<&'a mut DebugRenderer> {
|
||||
self.debug_renderer.as_mut()
|
||||
}
|
||||
|
||||
pub fn deinit(self, device: &mut Device) {
|
||||
if let Some(debug_renderer) = self.debug_renderer {
|
||||
debug_renderer.deinit(device);
|
||||
@ -2466,7 +2471,7 @@ impl Renderer {
|
||||
self.gpu_profile.end_frame();
|
||||
#[cfg(feature = "debug_renderer")]
|
||||
{
|
||||
if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) {
|
||||
if let Some(debug_renderer) = self.debug.try_get_mut() {
|
||||
debug_renderer.render(&mut self.device, framebuffer_size);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use api::{AddFont, BlobImageResources, AsyncBlobImageRasterizer, ResourceUpdate};
|
||||
use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest};
|
||||
use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest, RasterizedBlobImage};
|
||||
use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
|
||||
use api::{FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
|
||||
use api::{ExternalImageData, ExternalImageType, BlobImageResult, BlobImageParams};
|
||||
@ -32,6 +32,7 @@ use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
|
||||
use render_backend::FrameId;
|
||||
use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
|
||||
use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::hash_map::Entry::{self, Occupied, Vacant};
|
||||
use std::collections::hash_map::ValuesMut;
|
||||
use std::{cmp, mem};
|
||||
@ -98,8 +99,9 @@ enum State {
|
||||
}
|
||||
|
||||
/// Post scene building state.
|
||||
struct RasterizedBlobImage {
|
||||
data: FastHashMap<Option<TileOffset>, BlobImageResult>,
|
||||
enum RasterizedBlob {
|
||||
Tiled(FastHashMap<TileOffset, RasterizedBlobImage>),
|
||||
NonTiled(Vec<RasterizedBlobImage>),
|
||||
}
|
||||
|
||||
/// Pre scene building state.
|
||||
@ -398,7 +400,7 @@ pub struct ResourceCache {
|
||||
pending_image_requests: FastHashSet<ImageRequest>,
|
||||
|
||||
blob_image_handler: Option<Box<BlobImageHandler>>,
|
||||
rasterized_blob_images: FastHashMap<ImageKey, RasterizedBlobImage>,
|
||||
rasterized_blob_images: FastHashMap<ImageKey, RasterizedBlob>,
|
||||
blob_image_templates: FastHashMap<ImageKey, BlobImageTemplate>,
|
||||
|
||||
// If while building a frame we encounter blobs that we didn't already
|
||||
@ -597,10 +599,40 @@ impl ResourceCache {
|
||||
|
||||
pub fn add_rasterized_blob_images(&mut self, images: Vec<(BlobImageRequest, BlobImageResult)>) {
|
||||
for (request, result) in images {
|
||||
let data = match result {
|
||||
Ok(data) => data,
|
||||
Err(..) => {
|
||||
warn!("Failed to rasterize a blob image");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// First make sure we have an entry for this key (using a placeholder
|
||||
// if need be).
|
||||
let image = self.rasterized_blob_images.entry(request.key).or_insert_with(
|
||||
|| { RasterizedBlobImage { data: FastHashMap::default() } }
|
||||
|| { RasterizedBlob::Tiled(FastHashMap::default()) }
|
||||
);
|
||||
image.data.insert(request.tile, result);
|
||||
|
||||
if let Some(tile) = request.tile {
|
||||
if let RasterizedBlob::NonTiled(..) = *image {
|
||||
*image = RasterizedBlob::Tiled(FastHashMap::default());
|
||||
}
|
||||
|
||||
if let RasterizedBlob::Tiled(ref mut tiles) = *image {
|
||||
tiles.insert(tile, data);
|
||||
}
|
||||
} else {
|
||||
if let RasterizedBlob::NonTiled(ref mut queue) = *image {
|
||||
// If our new rasterized rect overwrites items in the queue, discard them.
|
||||
queue.retain(|img| {
|
||||
!data.rasterized_rect.contains_rect(&img.rasterized_rect)
|
||||
});
|
||||
|
||||
queue.push(data);
|
||||
} else {
|
||||
*image = RasterizedBlob::NonTiled(vec![data]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -925,9 +957,10 @@ impl ResourceCache {
|
||||
|
||||
if template.data.is_blob() {
|
||||
let request: BlobImageRequest = request.into();
|
||||
let missing = match self.rasterized_blob_images.get(&request.key) {
|
||||
Some(img) => !img.data.contains_key(&request.tile),
|
||||
None => true,
|
||||
let missing = match (self.rasterized_blob_images.get(&request.key), request.tile) {
|
||||
(Some(RasterizedBlob::Tiled(tiles)), Some(tile)) => !tiles.contains_key(&tile),
|
||||
(Some(RasterizedBlob::NonTiled(..)), None) => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// For some reason the blob image is missing. We'll fall back to
|
||||
@ -1062,13 +1095,32 @@ impl ResourceCache {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
let needs_upload = match self.cached_images.try_get(&key) {
|
||||
let mut needs_upload = match self.cached_images.try_get(&key) {
|
||||
Some(&ImageResult::UntiledAuto(ref entry)) => {
|
||||
self.texture_cache.needs_upload(&entry.texture_cache_handle)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// If the queue of ratserized updates is growing it probably means that
|
||||
// the texture is not getting uploaded because the display item is off-screen.
|
||||
// In that case we are better off
|
||||
// - Either not kicking rasterization for that image (avoid wasted cpu work
|
||||
// but will jank next time the item is visible because of lazy rasterization.
|
||||
// - Clobber the update queue by pushing an update with a larger dirty rect
|
||||
// to prevent it from accumulating.
|
||||
//
|
||||
// We do the latter here but it's not ideal and might want to revisit and do
|
||||
// the former instead.
|
||||
match self.rasterized_blob_images.get(&key) {
|
||||
Some(RasterizedBlob::NonTiled(ref queue)) => {
|
||||
if queue.len() > 2 {
|
||||
needs_upload = true;
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
};
|
||||
|
||||
let dirty_rect = if needs_upload {
|
||||
// The texture cache entry has been evicted, treat it as all dirty.
|
||||
None
|
||||
@ -1114,28 +1166,19 @@ impl ResourceCache {
|
||||
Some(size) => size,
|
||||
None => { return; }
|
||||
};
|
||||
let image = match self.rasterized_blob_images.get_mut(&key) {
|
||||
Some(image) => image,
|
||||
None => {
|
||||
//println!("Missing rasterized blob (key={:?})!", key);
|
||||
return;
|
||||
}
|
||||
|
||||
let tiles = match self.rasterized_blob_images.get_mut(&key) {
|
||||
Some(RasterizedBlob::Tiled(tiles)) => tiles,
|
||||
_ => { return; }
|
||||
};
|
||||
|
||||
let tile_range = compute_tile_range(
|
||||
&area,
|
||||
&template.descriptor.size,
|
||||
tile_size,
|
||||
);
|
||||
image.data.retain(|tile, _| {
|
||||
match *tile {
|
||||
Some(offset) => tile_range.contains(&offset),
|
||||
// This would be a bug. If we get here the blob should be tiled.
|
||||
None => {
|
||||
error!("Blob image template and image data tiling don't match.");
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tiles.retain(|tile, _| { tile_range.contains(tile) });
|
||||
}
|
||||
|
||||
pub fn request_glyphs(
|
||||
@ -1400,28 +1443,34 @@ impl ResourceCache {
|
||||
let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
|
||||
debug_assert!(image_template.data.uses_texture_cache());
|
||||
|
||||
let mut blob_rasterized_rect = None;
|
||||
let image_data = match image_template.data {
|
||||
let mut updates: SmallVec<[(ImageData, Option<DeviceUintRect>); 1]> = SmallVec::new();
|
||||
|
||||
match image_template.data {
|
||||
ImageData::Raw(..) | ImageData::External(..) => {
|
||||
// Safe to clone here since the Raw image data is an
|
||||
// Arc, and the external image data is small.
|
||||
image_template.data.clone()
|
||||
updates.push((image_template.data.clone(), None));
|
||||
}
|
||||
ImageData::Blob(..) => {
|
||||
let blob_image = self.rasterized_blob_images.get(&request.key).unwrap();
|
||||
match blob_image.data.get(&request.tile) {
|
||||
Some(result) => {
|
||||
let result = result
|
||||
.as_ref()
|
||||
.expect("Failed to render a blob image");
|
||||
|
||||
// TODO: we may want to not panic and show a placeholder instead.
|
||||
|
||||
blob_rasterized_rect = Some(result.rasterized_rect);
|
||||
|
||||
ImageData::Raw(Arc::clone(&result.data))
|
||||
let blob_image = self.rasterized_blob_images.get_mut(&request.key).unwrap();
|
||||
match (blob_image, request.tile) {
|
||||
(RasterizedBlob::Tiled(ref tiles), Some(tile)) => {
|
||||
let img = &tiles[&tile];
|
||||
updates.push((
|
||||
ImageData::Raw(Arc::clone(&img.data)),
|
||||
Some(img.rasterized_rect)
|
||||
));
|
||||
}
|
||||
None => {
|
||||
(RasterizedBlob::NonTiled(ref mut queue), None) => {
|
||||
for img in queue.drain(..) {
|
||||
updates.push((
|
||||
ImageData::Raw(img.data),
|
||||
Some(img.rasterized_rect)
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug_assert!(false, "invalid blob image request during frame building");
|
||||
continue;
|
||||
}
|
||||
@ -1429,100 +1478,102 @@ impl ResourceCache {
|
||||
}
|
||||
};
|
||||
|
||||
let entry = match *self.cached_images.get_mut(&request.key) {
|
||||
ImageResult::UntiledAuto(ref mut entry) => entry,
|
||||
ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
|
||||
ImageResult::Err(_) => panic!("Update requested for invalid entry")
|
||||
};
|
||||
|
||||
match (blob_rasterized_rect, entry.dirty_rect) {
|
||||
(Some(rasterized), Some(dirty)) => {
|
||||
debug_assert!(request.tile.is_some() || rasterized.contains_rect(&dirty));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut descriptor = image_template.descriptor.clone();
|
||||
let local_dirty_rect;
|
||||
|
||||
if let Some(tile) = request.tile {
|
||||
let tile_size = image_template.tiling.unwrap();
|
||||
let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
|
||||
|
||||
local_dirty_rect = if let Some(rect) = entry.dirty_rect.take() {
|
||||
// We should either have a dirty rect, or we are re-uploading where the dirty
|
||||
// rect is ignored anyway.
|
||||
let intersection = intersect_for_tile(rect, clipped_tile_size, tile_size, tile);
|
||||
debug_assert!(intersection.is_some() ||
|
||||
self.texture_cache.needs_upload(&entry.texture_cache_handle));
|
||||
intersection
|
||||
} else {
|
||||
None
|
||||
for (image_data, blob_rasterized_rect) in updates {
|
||||
let entry = match *self.cached_images.get_mut(&request.key) {
|
||||
ImageResult::UntiledAuto(ref mut entry) => entry,
|
||||
ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
|
||||
ImageResult::Err(_) => panic!("Update requested for invalid entry")
|
||||
};
|
||||
|
||||
// The tiled image could be stored on the CPU as one large image or be
|
||||
// already broken up into tiles. This affects the way we compute the stride
|
||||
// and offset.
|
||||
let tiled_on_cpu = image_template.data.is_blob();
|
||||
if !tiled_on_cpu {
|
||||
let bpp = descriptor.format.bytes_per_pixel();
|
||||
let stride = descriptor.compute_stride();
|
||||
descriptor.stride = Some(stride);
|
||||
descriptor.offset +=
|
||||
tile.y as u32 * tile_size as u32 * stride +
|
||||
tile.x as u32 * tile_size as u32 * bpp;
|
||||
}
|
||||
let mut descriptor = image_template.descriptor.clone();
|
||||
let mut local_dirty_rect;
|
||||
|
||||
descriptor.size = clipped_tile_size;
|
||||
} else {
|
||||
local_dirty_rect = entry.dirty_rect.take();
|
||||
}
|
||||
if let Some(tile) = request.tile {
|
||||
let tile_size = image_template.tiling.unwrap();
|
||||
let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
|
||||
|
||||
let filter = match request.rendering {
|
||||
ImageRendering::Pixelated => {
|
||||
TextureFilter::Nearest
|
||||
}
|
||||
ImageRendering::Auto | ImageRendering::CrispEdges => {
|
||||
// If the texture uses linear filtering, enable mipmaps and
|
||||
// trilinear filtering, for better image quality. We only
|
||||
// support this for now on textures that are not placed
|
||||
// into the shared cache. This accounts for any image
|
||||
// that is > 512 in either dimension, so it should cover
|
||||
// the most important use cases. We may want to support
|
||||
// mip-maps on shared cache items in the future.
|
||||
if descriptor.allow_mipmaps &&
|
||||
descriptor.size.width > 512 &&
|
||||
descriptor.size.height > 512 &&
|
||||
!self.texture_cache.is_allowed_in_shared_cache(
|
||||
TextureFilter::Linear,
|
||||
&descriptor,
|
||||
) {
|
||||
TextureFilter::Trilinear
|
||||
local_dirty_rect = if let Some(rect) = entry.dirty_rect.take() {
|
||||
// We should either have a dirty rect, or we are re-uploading where the dirty
|
||||
// rect is ignored anyway.
|
||||
let intersection = intersect_for_tile(rect, clipped_tile_size, tile_size, tile);
|
||||
debug_assert!(intersection.is_some() ||
|
||||
self.texture_cache.needs_upload(&entry.texture_cache_handle));
|
||||
intersection
|
||||
} else {
|
||||
TextureFilter::Linear
|
||||
None
|
||||
};
|
||||
|
||||
// The tiled image could be stored on the CPU as one large image or be
|
||||
// already broken up into tiles. This affects the way we compute the stride
|
||||
// and offset.
|
||||
let tiled_on_cpu = image_template.data.is_blob();
|
||||
if !tiled_on_cpu {
|
||||
let bpp = descriptor.format.bytes_per_pixel();
|
||||
let stride = descriptor.compute_stride();
|
||||
descriptor.stride = Some(stride);
|
||||
descriptor.offset +=
|
||||
tile.y as u32 * tile_size as u32 * stride +
|
||||
tile.x as u32 * tile_size as u32 * bpp;
|
||||
}
|
||||
|
||||
descriptor.size = clipped_tile_size;
|
||||
} else {
|
||||
local_dirty_rect = entry.dirty_rect.take();
|
||||
}
|
||||
};
|
||||
|
||||
let eviction = if image_template.data.is_blob() {
|
||||
Eviction::Manual
|
||||
} else {
|
||||
Eviction::Auto
|
||||
};
|
||||
// If we are uploading the dirty region of a blob image we might have several
|
||||
// rects to upload so we use each of these rasterized rects rather than the
|
||||
// overall dirty rect of the image.
|
||||
if blob_rasterized_rect.is_some() {
|
||||
local_dirty_rect = blob_rasterized_rect;
|
||||
}
|
||||
|
||||
//Note: at this point, the dirty rectangle is local to the descriptor space
|
||||
self.texture_cache.update(
|
||||
&mut entry.texture_cache_handle,
|
||||
descriptor,
|
||||
filter,
|
||||
Some(image_data),
|
||||
[0.0; 3],
|
||||
local_dirty_rect,
|
||||
gpu_cache,
|
||||
None,
|
||||
UvRectKind::Rect,
|
||||
eviction,
|
||||
);
|
||||
let filter = match request.rendering {
|
||||
ImageRendering::Pixelated => {
|
||||
TextureFilter::Nearest
|
||||
}
|
||||
ImageRendering::Auto | ImageRendering::CrispEdges => {
|
||||
// If the texture uses linear filtering, enable mipmaps and
|
||||
// trilinear filtering, for better image quality. We only
|
||||
// support this for now on textures that are not placed
|
||||
// into the shared cache. This accounts for any image
|
||||
// that is > 512 in either dimension, so it should cover
|
||||
// the most important use cases. We may want to support
|
||||
// mip-maps on shared cache items in the future.
|
||||
if descriptor.allow_mipmaps &&
|
||||
descriptor.size.width > 512 &&
|
||||
descriptor.size.height > 512 &&
|
||||
!self.texture_cache.is_allowed_in_shared_cache(
|
||||
TextureFilter::Linear,
|
||||
&descriptor,
|
||||
) {
|
||||
TextureFilter::Trilinear
|
||||
} else {
|
||||
TextureFilter::Linear
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let eviction = if image_template.data.is_blob() {
|
||||
Eviction::Manual
|
||||
} else {
|
||||
Eviction::Auto
|
||||
};
|
||||
|
||||
//Note: at this point, the dirty rectangle is local to the descriptor space
|
||||
self.texture_cache.update(
|
||||
&mut entry.texture_cache_handle,
|
||||
descriptor,
|
||||
filter,
|
||||
Some(image_data),
|
||||
[0.0; 3],
|
||||
local_dirty_rect,
|
||||
gpu_cache,
|
||||
None,
|
||||
UvRectKind::Rect,
|
||||
eviction,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,8 +58,8 @@ impl SceneProperties {
|
||||
pub fn flush_pending_updates(&mut self) -> bool {
|
||||
let mut properties_changed = false;
|
||||
|
||||
if let Some(pending_properties) = self.pending_properties.take() {
|
||||
if pending_properties != self.current_properties {
|
||||
if let Some(ref pending_properties) = self.pending_properties {
|
||||
if *pending_properties != self.current_properties {
|
||||
self.transform_properties.clear();
|
||||
self.float_properties.clear();
|
||||
|
||||
@ -73,7 +73,7 @@ impl SceneProperties {
|
||||
.insert(property.key.id, property.value);
|
||||
}
|
||||
|
||||
self.current_properties = pending_properties;
|
||||
self.current_properties = pending_properties.clone();
|
||||
properties_changed = true;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ time = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.16"
|
||||
core-graphics = "0.17.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
dwrote = "0.4.1"
|
||||
|
@ -27,6 +27,6 @@ dwrote = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.16"
|
||||
core-graphics = "0.17.1"
|
||||
foreign-types = "0.3.0"
|
||||
|
||||
|
@ -1 +1 @@
|
||||
f17f6a491d6ff3dc3e13e998dda788d8f5856338
|
||||
a601f9c291cee83257241ef61aaf62353c613438
|
||||
|
@ -31,7 +31,7 @@ winit = "0.16"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.16"
|
||||
core-graphics = "0.17.1"
|
||||
core-foundation = "0.6"
|
||||
|
||||
[features]
|
||||
|
Loading…
Reference in New Issue
Block a user