tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

snapping.rs (17012B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::RawtestHarness;
      6 use crate::euclid::point2;
      7 use webrender::api::*;
      8 use webrender::api::units::*;
      9 use webrender::Transaction;
     10 
     11 struct SnapTestContext {
     12    root_spatial_id: SpatialId,
     13    test_size: FramebufferIntSize,
     14    font_size: f32,
     15    ahem_font_key: FontInstanceKey,
     16    variant: usize,
     17 }
     18 
     19 enum SnapTestExpectation {
     20    Rect {
     21        expected_color: ColorU,
     22        expected_rect: DeviceIntRect,
     23        expected_offset: i32,
     24    }
     25 }
     26 
     27 struct ScrollRequest {
     28    external_scroll_id: ExternalScrollId,
     29    amount: f32,
     30 }
     31 
     32 struct SnapTestResult {
     33    scrolls: Vec<ScrollRequest>,
     34    expected: SnapTestExpectation,
     35 }
     36 
     37 type SnapTestFunction = fn(&mut DisplayListBuilder, &mut SnapTestContext) -> SnapTestResult;
     38 
     39 struct SnapTest {
     40    name: &'static str,
     41    f: SnapTestFunction,
     42    variations: usize,
     43 }
     44 
     45 struct SnapVariation {
     46    offset: f32,
     47    expected: i32,
     48 }
     49 
     50 const MAGENTA_RECT: SnapTest = SnapTest {
     51    name: "clear",
     52    f: dl_clear,
     53    variations: 1,
     54 };
     55 
     56 // Types of snap tests
     57 const TESTS: &[SnapTest; 4] = &[
     58    // Rectangle, no transform/scroll
     59    SnapTest {
     60        name: "rect",
     61        f: dl_simple_rect,
     62        variations: SIMPLE_FRACTIONAL_VARIANTS.len(),
     63    },
     64 
     65    // Glyph, no transform/scroll
     66    SnapTest {
     67        name: "glyph",
     68        f: dl_simple_glyph,
     69        variations: SIMPLE_FRACTIONAL_VARIANTS.len(),
     70    },
     71 
     72    // Rect, APZ
     73    SnapTest {
     74        name: "scroll1",
     75        f: dl_scrolling1,
     76        variations: SCROLL_VARIANTS.len(),
     77    },
     78 
     79    // Rect, APZ + external scroll offset
     80    SnapTest {
     81        name: "rect-apz-ext-1",
     82        f: dl_scrolling_ext1,
     83        variations: EXTERNAL_SCROLL_VARIANTS.len(),
     84    }
     85 ];
     86 
     87 // Variants we will run for each snap test with expected float offset and raster difference
     88 const SIMPLE_FRACTIONAL_VARIANTS: [SnapVariation; 13] = [
     89    SnapVariation {
     90        offset: 0.0,
     91        expected: 0,
     92    },
     93    SnapVariation {
     94        offset: 0.1,
     95        expected: 0,
     96    },
     97    SnapVariation {
     98        offset: 0.25,
     99        expected: 0,
    100    },
    101    SnapVariation {
    102        offset: 0.33,
    103        expected: 0,
    104    },
    105    SnapVariation {
    106        offset: 0.49,
    107        expected: 0,
    108    },
    109    SnapVariation {
    110        offset: 0.5,
    111        expected: 1,
    112    },
    113    SnapVariation {
    114        offset: 0.51,
    115        expected: 1,
    116    },
    117    SnapVariation {
    118        offset: -0.1,
    119        expected: 0,
    120    },
    121    SnapVariation {
    122        offset: -0.25,
    123        expected: 0,
    124    },
    125    SnapVariation {
    126        offset: -0.33,
    127        expected: 0,
    128    },
    129    SnapVariation {
    130        offset: -0.49,
    131        expected: 0,
    132    },
    133    SnapVariation {
    134        offset: -0.5,
    135        expected: 0,
    136    },
    137    SnapVariation {
    138        offset: -0.51,
    139        expected: -1,
    140    },
    141 ];
    142 
    143 struct ScrollVariation{
    144    apz_scroll: f32,
    145    prim_offset: f32,
    146    expected: i32,
    147 }
    148 
    149 const SCROLL_VARIANTS: [ScrollVariation; 3] = [
    150    ScrollVariation {
    151        apz_scroll: 0.0,
    152        prim_offset: 0.0,
    153        expected: 0,
    154    },
    155    ScrollVariation {
    156        apz_scroll: -1.0,
    157        prim_offset: 0.0,
    158        expected: 1,
    159    },
    160    ScrollVariation {
    161        apz_scroll: -1.5,
    162        prim_offset: 0.0,
    163        expected: 2,
    164    },
    165 ];
    166 
    167 struct ExternalScrollVariation{
    168    external_offset: f32,
    169    apz_scroll: f32,
    170    prim_offset: f32,
    171    expected: i32,
    172 }
    173 
    174 const EXTERNAL_SCROLL_VARIANTS: [ExternalScrollVariation; 1] = [
    175    ExternalScrollVariation {
    176        external_offset: 100.0,
    177        apz_scroll: -101.0,
    178        prim_offset: -100.0,
    179        expected: 1,
    180    },
    181 ];
    182 
    183 impl<'a> RawtestHarness<'a> {
    184    pub fn test_snapping(&mut self) {
    185        println!("\tsnapping test...");
    186 
    187        // Test size needs to be:
    188        // (a) as small as possible for performance
    189        // (b) a factor of 5 (ahem font baseline requirement)
    190        // (c) an even number (center placement of test render)
    191        let test_size = FramebufferIntSize::new(20, 20);
    192        let mut any_fails = false;
    193 
    194        // Load the ahem.css test font
    195        let font_bytes = include_bytes!("../../reftests/text/Ahem.ttf").into();
    196        let font_key = self.wrench.font_key_from_bytes(font_bytes, 0);
    197        let font_size = 0.5 * test_size.width as f32;
    198        let ahem_font_key = self.wrench.add_font_instance(
    199            font_key,
    200            font_size,
    201            FontInstanceFlags::empty(),
    202            Some(FontRenderMode::Alpha),
    203            SyntheticItalics::disabled(),
    204        );
    205 
    206        // Run each test
    207        for test in TESTS {
    208            for i in 0 .. test.variations {
    209                let mut ctx = SnapTestContext {
    210                    ahem_font_key,
    211                    font_size,
    212                    root_spatial_id: SpatialId::root_scroll_node(self.wrench.root_pipeline_id),
    213                    test_size,
    214                    variant: i,
    215                };
    216 
    217                any_fails = !self.run_snap_test(test, &mut ctx);
    218 
    219                // Each test clears to a magenta rect before running the next test. This
    220                // ensures that if WR's invalidation logic would skip rendering a test due
    221                // to detection that it's the same output, we will still render it to test
    222                // the pixel snapping is actually correct
    223                assert!(self.run_snap_test(&MAGENTA_RECT, &mut ctx));
    224            }
    225        }
    226 
    227        assert!(!any_fails);
    228    }
    229 
    230    fn run_snap_test(
    231        &mut self,
    232        test: &SnapTest,
    233        ctx: &mut SnapTestContext,
    234    ) -> bool {
    235        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
    236        builder.begin();
    237 
    238        let result = (test.f)(&mut builder, ctx);
    239 
    240        let window_size = self.window.get_inner_size();
    241        let window_rect = FramebufferIntRect::from_origin_and_size(
    242            point2(0, window_size.height - ctx.test_size.height),
    243            ctx.test_size,
    244        );
    245 
    246        let txn = Transaction::new();
    247        self.submit_dl(&mut Epoch(0), builder, txn);
    248 
    249        for scroll in result.scrolls {
    250            let mut txn = Transaction::new();
    251            txn.set_scroll_offsets(
    252                scroll.external_scroll_id,
    253                vec![SampledScrollOffset {
    254                    offset: LayoutVector2D::new(0.0, scroll.amount),
    255                    generation: APZScrollGeneration::default(),
    256                }],
    257            );
    258            txn.generate_frame(0, true, false, RenderReasons::TESTING);
    259            self.wrench.api.send_transaction(self.wrench.document_id, txn);
    260 
    261            self.render_and_get_pixels(window_rect);
    262        }
    263 
    264        let pixels = self.render_and_get_pixels(window_rect);
    265 
    266        let ok = validate_output(
    267            &pixels,
    268            result.expected,
    269            ctx.test_size,
    270        );
    271 
    272        if !ok {
    273            println!("FAIL {} [{}]", test.name, ctx.variant);
    274 
    275            // enable to save output as png for debugging
    276            // use crate::png;
    277            // png::save(
    278            //     format!("snap_test_{}.png", test.name),
    279            //     pixels.clone(),
    280            //     ctx.test_size.cast_unit(),
    281            //     png::SaveSettings {
    282            //         flip_vertical: true,
    283            //         try_crop: false,
    284            //     },
    285            // );
    286 
    287            // enable to log output to console for debugging
    288            // for y in 0 .. ctx.test_size.height {
    289            //     for x in 0 .. ctx.test_size.width {
    290            //         let i = ((ctx.test_size.height - y - 1) * ctx.test_size.width + x) as usize * 4;
    291            //         let r = pixels[i+0];
    292            //         let g = pixels[i+1];
    293            //         let b = pixels[i+2];
    294            //         let a = pixels[i+3];
    295            //         print!("[{:2x},{:2x},{:2x},{:2x}], ", r, g, b, a);
    296            //     }
    297            //     print!("\n");
    298            // }
    299        }
    300 
    301        ok
    302    }
    303 }
    304 
    305 fn validate_output(
    306    pixels: &[u8],
    307    expected: SnapTestExpectation,
    308    frame_buffer_size: FramebufferIntSize,
    309 ) -> bool {
    310    match expected {
    311        SnapTestExpectation::Rect { expected_color, expected_rect, expected_offset } => {
    312            let expected_rect = expected_rect.translate(
    313                DeviceIntVector2D::new(0, expected_offset)
    314            );
    315 
    316            for y in 0 .. frame_buffer_size.height {
    317                for x in 0 .. frame_buffer_size.width {
    318                    let i = ((frame_buffer_size.height - y - 1) * frame_buffer_size.width + x) as usize * 4;
    319                    let actual = ColorU::new(
    320                        pixels[i+0],
    321                        pixels[i+1],
    322                        pixels[i+2],
    323                        pixels[i+3],
    324                    );
    325 
    326                    let expected = if expected_rect.contains(DeviceIntPoint::new(x, y)) {
    327                        expected_color
    328                    } else {
    329                        ColorU::new(255, 255, 255, 255)
    330                    };
    331 
    332                    if expected != actual {
    333                        println!("FAILED at ({}, {}):", x, y);
    334                        println!("\tExpected [{:2x},{:2x},{:2x},{:2x}]",
    335                            expected.r,
    336                            expected.g,
    337                            expected.b,
    338                            expected.a,
    339                        );
    340                        println!("\tGot      [{:2x},{:2x},{:2x},{:2x}]",
    341                            actual.r,
    342                            actual.g,
    343                            actual.b,
    344                            actual.a,
    345                        );
    346                        return false;
    347                    }
    348                }
    349            }
    350 
    351            true
    352        }
    353    }
    354 }
    355 
    356 fn dl_clear(
    357    builder: &mut DisplayListBuilder,
    358    ctx: &mut SnapTestContext,
    359 ) -> SnapTestResult {
    360    let color = ColorF::new(1.0, 0.0, 1.0, 1.0);
    361 
    362    let bounds = ctx.test_size
    363        .to_f32()
    364        .cast_unit()
    365        .into();
    366 
    367    builder.push_rect(
    368        &CommonItemProperties {
    369            clip_rect: bounds,
    370            clip_chain_id: ClipChainId::INVALID,
    371            spatial_id: ctx.root_spatial_id,
    372            flags: PrimitiveFlags::default(),
    373        },
    374        bounds,
    375        color,
    376    );
    377 
    378    SnapTestResult {
    379        scrolls: vec![],
    380        expected: SnapTestExpectation::Rect {
    381            expected_color: color.into(),
    382            expected_rect: ctx.test_size.cast_unit().into(),
    383            expected_offset: 0,
    384        }
    385    }
    386 }
    387 
    388 // Draw a centered rect
    389 fn dl_simple_rect(
    390    builder: &mut DisplayListBuilder,
    391    ctx: &mut SnapTestContext
    392 ) -> SnapTestResult {
    393    let color = ColorF::BLACK;
    394    let variant = &SIMPLE_FRACTIONAL_VARIANTS[ctx.variant];
    395 
    396    let prim_size = DeviceIntSize::new(
    397        ctx.test_size.width / 2,
    398        ctx.test_size.height / 2
    399    );
    400 
    401    let rect = DeviceIntRect::from_origin_and_size(
    402        DeviceIntPoint::new(
    403            (ctx.test_size.width - prim_size.width) / 2,
    404            (ctx.test_size.height - prim_size.height) / 2,
    405        ),
    406        prim_size,
    407    );
    408 
    409    let bounds = rect
    410        .to_f32()
    411        .cast_unit()
    412        .translate(
    413            LayoutVector2D::new(0.0, variant.offset)
    414        );
    415 
    416    builder.push_rect(
    417        &CommonItemProperties {
    418            clip_rect: bounds,
    419            clip_chain_id: ClipChainId::INVALID,
    420            spatial_id: ctx.root_spatial_id,
    421            flags: PrimitiveFlags::default(),
    422        },
    423        bounds,
    424        color,
    425    );
    426 
    427    SnapTestResult {
    428        scrolls: vec![],
    429        expected: SnapTestExpectation::Rect {
    430            expected_color: color.into(),
    431            expected_rect: rect,
    432            expected_offset: variant.expected,
    433        }
    434    }
    435 }
    436 
    437 // Draw a centered glyph with ahem.css font
    438 fn dl_simple_glyph(
    439    builder: &mut DisplayListBuilder,
    440    ctx: &mut SnapTestContext,
    441 ) -> SnapTestResult {
    442    let color = ColorF::BLACK;
    443    let variant = &SIMPLE_FRACTIONAL_VARIANTS[ctx.variant];
    444 
    445    let prim_size = DeviceIntSize::new(
    446        ctx.test_size.width / 2,
    447        ctx.test_size.height / 2
    448    );
    449 
    450    let rect = DeviceIntRect::from_origin_and_size(
    451        DeviceIntPoint::new(
    452            (ctx.test_size.width - prim_size.width) / 2,
    453            (ctx.test_size.height - prim_size.height) / 2,
    454        ),
    455        prim_size,
    456    );
    457 
    458    let bounds = rect
    459        .to_f32()
    460        .cast_unit()
    461        .translate(
    462            LayoutVector2D::new(0.0, variant.offset)
    463    );
    464 
    465    builder.push_text(
    466        &CommonItemProperties {
    467            clip_rect: bounds,
    468            clip_chain_id: ClipChainId::INVALID,
    469            spatial_id: ctx.root_spatial_id,
    470            flags: PrimitiveFlags::default(),
    471        },
    472        bounds,
    473        &[
    474            GlyphInstance {
    475                index: 0x41,
    476                point: LayoutPoint::new(
    477                    bounds.min.x,
    478                    // ahem.css font has baseline at 0.8em
    479                    bounds.min.y + ctx.font_size * 0.8,
    480                ),
    481            }
    482        ],
    483        ctx.ahem_font_key,
    484        color,
    485        None,
    486    );
    487 
    488    SnapTestResult {
    489        scrolls: vec![],
    490        expected: SnapTestExpectation::Rect {
    491            expected_color: color.into(),
    492            expected_rect: rect,
    493            expected_offset: variant.expected,
    494        }
    495    }
    496 }
    497 
    498 fn dl_scrolling1(
    499    builder: &mut DisplayListBuilder,
    500    ctx: &mut SnapTestContext,
    501 ) -> SnapTestResult {
    502    let color = ColorF::BLACK;
    503    let external_scroll_id = ExternalScrollId(1, PipelineId::dummy());
    504    let variant = &SCROLL_VARIANTS[ctx.variant];
    505 
    506    let scroll_id = builder.define_scroll_frame(
    507        ctx.root_spatial_id,
    508        external_scroll_id,
    509        LayoutRect::from_size(LayoutSize::new(100.0, 1000.0)),
    510        LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
    511        LayoutVector2D::zero(),
    512        APZScrollGeneration::default(),
    513        HasScrollLinkedEffect::No,
    514        SpatialTreeItemKey::new(0, 0),
    515    );
    516 
    517    let prim_size = DeviceIntSize::new(
    518        ctx.test_size.width / 2,
    519        ctx.test_size.height / 2
    520    );
    521 
    522    let rect = DeviceIntRect::from_origin_and_size(
    523        DeviceIntPoint::new(
    524            (ctx.test_size.width - prim_size.width) / 2,
    525            (ctx.test_size.height - prim_size.height) / 2,
    526        ),
    527        prim_size,
    528    );
    529 
    530    let bounds = rect
    531        .to_f32()
    532        .cast_unit()
    533        .translate(
    534            LayoutVector2D::new(0.0, variant.prim_offset)
    535        );
    536 
    537    builder.push_rect(
    538        &CommonItemProperties {
    539            clip_rect: bounds,
    540            clip_chain_id: ClipChainId::INVALID,
    541            spatial_id: scroll_id,
    542            flags: PrimitiveFlags::default(),
    543        },
    544        bounds,
    545        color,
    546    );
    547 
    548    SnapTestResult {
    549        scrolls: vec![
    550            ScrollRequest {
    551                external_scroll_id,
    552                amount: variant.apz_scroll,
    553            }
    554        ],
    555        expected: SnapTestExpectation::Rect {
    556            expected_color: color.into(),
    557            expected_rect: rect,
    558            expected_offset: variant.expected,
    559        }
    560    }
    561 }
    562 
    563 fn dl_scrolling_ext1(
    564    builder: &mut DisplayListBuilder,
    565    ctx: &mut SnapTestContext,
    566 ) -> SnapTestResult {
    567    let color = ColorF::BLACK;
    568    let external_scroll_id = ExternalScrollId(1, PipelineId::dummy());
    569    let variant = &EXTERNAL_SCROLL_VARIANTS[ctx.variant];
    570 
    571    let scroll_id = builder.define_scroll_frame(
    572        ctx.root_spatial_id,
    573        external_scroll_id,
    574        LayoutRect::from_size(LayoutSize::new(100.0, 1000.0)),
    575        LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
    576        LayoutVector2D::new(0.0, variant.external_offset),
    577        APZScrollGeneration::default(),
    578        HasScrollLinkedEffect::No,
    579        SpatialTreeItemKey::new(0, 0),
    580    );
    581 
    582    let prim_size = DeviceIntSize::new(
    583        ctx.test_size.width / 2,
    584        ctx.test_size.height / 2
    585    );
    586 
    587    let rect = DeviceIntRect::from_origin_and_size(
    588        DeviceIntPoint::new(
    589            (ctx.test_size.width - prim_size.width) / 2,
    590            (ctx.test_size.height - prim_size.height) / 2,
    591        ),
    592        prim_size,
    593    );
    594 
    595    let bounds = rect
    596        .to_f32()
    597        .cast_unit()
    598        .translate(
    599            LayoutVector2D::new(0.0, variant.prim_offset)
    600        );
    601 
    602    builder.push_rect(
    603        &CommonItemProperties {
    604            clip_rect: bounds,
    605            clip_chain_id: ClipChainId::INVALID,
    606            spatial_id: scroll_id,
    607            flags: PrimitiveFlags::default(),
    608        },
    609        bounds,
    610        color,
    611    );
    612 
    613    SnapTestResult {
    614        scrolls: vec![
    615            ScrollRequest {
    616                external_scroll_id,
    617                amount: variant.apz_scroll,
    618            }
    619        ],
    620        expected: SnapTestExpectation::Rect {
    621            expected_color: color.into(),
    622            expected_rect: rect,
    623            expected_offset: variant.expected,
    624        }
    625    }
    626 }