Bug 1522787 - downscale bitmap glyphs to avoid filtering artifacts r=gw
authorLee Salzman <lsalzman@mozilla.com>
Fri, 08 Mar 2019 08:48:38 -0500
changeset 463351 e8a962d607cd
parent 463350 817f8ac60713
child 463352 8c8e2862fd5e
push id35671
push userdluca@mozilla.com
push dateSat, 09 Mar 2019 09:48:12 +0000
treeherdermozilla-central@b486ad6d8c06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1522787
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522787 - downscale bitmap glyphs to avoid filtering artifacts r=gw Differential Revision: https://phabricator.services.mozilla.com/D22704
gfx/wr/webrender/src/glyph_rasterizer/mod.rs
gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
--- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
@@ -480,16 +480,99 @@ pub struct RasterizedGlyph {
     pub left: f32,
     pub width: i32,
     pub height: i32,
     pub scale: f32,
     pub format: GlyphFormat,
     pub bytes: Vec<u8>,
 }
 
+impl RasterizedGlyph {
+    #[allow(dead_code)]
+    pub fn downscale_bitmap_if_required(&mut self, font: &FontInstance) {
+        // Check if the glyph is going to be downscaled in the shader. If the scaling is
+        // less than 0.5, that means bilinear filtering can't effectively filter the glyph
+        // without aliasing artifacts.
+        //
+        // Instead of fixing this by mipmapping the glyph cache texture, rather manually
+        // produce the appropriate mip level for individual glyphs where bilinear filtering
+        // will still produce acceptable results.
+        match self.format {
+            GlyphFormat::Bitmap | GlyphFormat::ColorBitmap => {},
+            _ => return,
+        }
+        let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+        let upscaled = x_scale.max(y_scale) as f32;
+        let mut new_scale = self.scale;
+        if new_scale * upscaled <= 0.0 {
+            return;
+        }
+        let mut steps = 0;
+        while new_scale * upscaled <= 0.5 {
+            new_scale *= 2.0;
+            steps += 1;
+        }
+        // If no mipping is necessary, just bail.
+        if steps == 0 {
+            return;
+        }
+
+        // Calculate the actual size of the mip level.
+        let new_width = (self.width as usize + (1 << steps) - 1) >> steps;
+        let new_height = (self.height as usize + (1 << steps) - 1) >> steps;
+        let mut new_bytes: Vec<u8> = Vec::with_capacity(new_width * new_height * 4);
+
+        // Produce destination pixels by applying a box filter to the source pixels.
+        // The box filter corresponds to how graphics drivers may generate mipmaps.
+        for y in 0 .. new_height {
+            for x in 0 .. new_width {
+                // Calculate the number of source samples that contribute to the destination pixel.
+                let src_y = y << steps;
+                let src_x = x << steps;
+                let y_samples = (1 << steps).min(self.height as usize - src_y);
+                let x_samples = (1 << steps).min(self.width as usize - src_x);
+                let num_samples = (x_samples * y_samples) as u32;
+
+                let mut src_idx = (src_y * self.width as usize + src_x) * 4;
+                // Initialize the accumulator with half an increment so that when later divided
+                // by the sample count, it will effectively round the accumulator to the nearest
+                // increment.
+                let mut accum = [num_samples / 2; 4];
+                // Accumulate all the contributing source sampless.
+                for _ in 0 .. y_samples {
+                    for _ in 0 .. x_samples {
+                        accum[0] += self.bytes[src_idx + 0] as u32;
+                        accum[1] += self.bytes[src_idx + 1] as u32;
+                        accum[2] += self.bytes[src_idx + 2] as u32;
+                        accum[3] += self.bytes[src_idx + 3] as u32;
+                        src_idx += 4;
+                    }
+                    src_idx += (self.width as usize - x_samples) * 4;
+                }
+
+                // Finally, divide by the sample count to get the mean value for the new pixel.
+                new_bytes.extend_from_slice(&[
+                    (accum[0] / num_samples) as u8,
+                    (accum[1] / num_samples) as u8,
+                    (accum[2] / num_samples) as u8,
+                    (accum[3] / num_samples) as u8,
+                ]);
+            }
+        }
+
+        // Fix the bounds for the new glyph data.
+        self.top /= (1 << steps) as f32;
+        self.left /= (1 << steps) as f32;
+        self.width = new_width as i32;
+        self.height = new_height as i32;
+        self.scale = new_scale;
+        self.bytes = new_bytes;
+    }
+}
+
 pub struct FontContexts {
     // These worker are mostly accessed from their corresponding worker threads.
     // The goal is that there should be no noticeable contention on the mutexes.
     worker_contexts: Vec<Mutex<FontContext>>,
     // This worker should be accessed by threads that don't belong to the thread pool
     // (in theory that's only the render backend thread so no contention expected either).
     shared_context: Mutex<FontContext>,
     #[cfg(feature = "pathfinder")]
--- a/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -102,28 +102,32 @@ impl GlyphRasterizer {
         // 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
                 .par_iter()
                 .map(|key: &GlyphKey| {
                     profile_scope!("glyph-raster");
                     let mut context = font_contexts.lock_current_context();
-                    let job = GlyphRasterJob {
+                    let mut job = GlyphRasterJob {
                         key: key.clone(),
                         result: context.rasterize_glyph(&font, key),
                     };
 
-                    // Sanity check.
-                    if let Ok(ref glyph) = job.result {
+                    if let Ok(ref mut glyph) = job.result {
+                        // Sanity check.
                         let bpp = 4; // We always render glyphs in 32 bits RGBA format.
                         assert_eq!(
                             glyph.bytes.len(),
                             bpp * (glyph.width * glyph.height) as usize
                         );
+                        assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
+
+                        // Check if the glyph has a bitmap that needs to be downscaled.
+                        glyph.downscale_bitmap_if_required(&font);
                     }
 
                     job
                 })
                 .collect();
 
             glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
         });
@@ -161,17 +165,16 @@ impl GlyphRasterizer {
 
             for GlyphRasterJob { key, result } in jobs {
                 let glyph_info = match result {
                     Err(_) => GlyphCacheEntry::Blank,
                     Ok(ref glyph) if glyph.width == 0 || glyph.height == 0 => {
                         GlyphCacheEntry::Blank
                     }
                     Ok(glyph) => {
-                        assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
                         let mut texture_cache_handle = TextureCacheHandle::invalid();
                         texture_cache.request(&texture_cache_handle, gpu_cache);
                         texture_cache.update(
                             &mut texture_cache_handle,
                             ImageDescriptor {
                                 size: size2(glyph.width, glyph.height),
                                 stride: None,
                                 format: ImageFormat::BGRA8,