Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/wr/wrench/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 51 kB image not shown  

Quelle  rawtest.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/* 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 euclid::{point2, size2, rect, Box2D};
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::mpsc::Receiver;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
use crate::{WindowWrapper, NotifierEvent};
use crate::blob;
use crate::reftest::{ReftestImage, ReftestImageComparison};
use crate::wrench::Wrench;

pub struct RawtestHarness<'a> {
    wrench: &'a mut Wrench,
    rx: &'a Receiver<NotifierEvent>,
    window: &'a mut WindowWrapper,
}


impl<'a> RawtestHarness<'a> {
    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) {
        self.test_hit_testing();
        self.test_resize_image();
        self.test_retained_blob_images_test();
        self.test_blob_update_test();
        self.test_blob_update_epoch_test();
        self.test_tile_decomposition();
        self.test_very_large_blob();
        self.test_blob_visible_area();
        self.test_blob_set_visible_area();
        self.test_offscreen_blob();
        self.test_save_restore();
        self.test_blur_cache();
        self.test_capture();
        self.test_zero_height_window();
        self.test_clear_cache();
    }

    fn render_and_get_pixels(&mut self, window_rect: FramebufferIntRect) -> Vec<u8> {
        self.rx.recv().unwrap();
        self.wrench.render();
        self.wrench.renderer.read_pixels_rgba8(window_rect)
    }

    fn compare_pixels(&self, data1: Vec<u8>, data2: Vec<u8>, size: FramebufferIntSize) {
        let size = DeviceIntSize::new(size.width, size.height);
        let image1 = ReftestImage {
            data: data1,
            size,
        };
        let image2 = ReftestImage {
            data: data2,
            size,
        };

        match image1.compare(&image2) {
            ReftestImageComparison::Equal => {}
            ReftestImageComparison::NotEqual { max_difference, count_different, .. } => {
                let t = "rawtest";
                println!(
                    "REFTEST TEST-UNEXPECTED-FAIL | {t} \
                     | image comparison, max difference: {max_difference}, \
                     number of differing pixels: {count_different}");
                println!("REFTEST   IMAGE 1: {}", image1.create_data_uri());
                println!("REFTEST   IMAGE 2: {}", image2.create_data_uri());
                println!("REFTEST TEST-END | {}", t);
                panic!();
            }
        }
    }

    fn submit_dl(
        &mut self,
        epoch: &mut Epoch,
        mut builder: DisplayListBuilder,
        mut txn: Transaction,
    ) {
        txn.use_scene_builder_thread();

        txn.set_display_list(
            *epoch,
            builder.end(),
        );
        epoch.0 += 1;

        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn make_common_properties(&self, clip_rect: LayoutRect) -> CommonItemProperties {
        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        CommonItemProperties {
            clip_rect,
            clip_chain_id: space_and_clip.clip_chain_id,
            spatial_id: space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        }
    }

    fn make_common_properties_with_clip_and_spatial(
        &self,
        clip_rect: LayoutRect,
        clip_chain_id: ClipChainId,
        spatial_id: SpatialId
    ) -> CommonItemProperties {
        CommonItemProperties {
            clip_rect,
            clip_chain_id,
            spatial_id,
            flags: PrimitiveFlags::default(),
        }
    }

    fn test_resize_image(&mut self) {
        println!("\tresize image...");
        // This test changes the size of an image to make it go switch back and forth
        // between tiled and non-tiled.
        // The resource cache should be able to handle this without crashing.

        let mut txn = Transaction::new();
        let img = self.wrench.api.generate_image_key();

        // Start with a non-tiled image.
        txn.add_image(
            img,
            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![255; 64 * 64 * 4]),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 64.0, 64.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        let mut txn = Transaction::new();
        // Resize the image to something bigger than the max texture size (8196) to force tiling.
        txn.update_image(
            img,
            ImageDescriptor::new(8200, 32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![255; 8200 * 32 * 4]),
            &DirtyRect::All,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        let mut txn = Transaction::new();
        // Resize back to something doesn't require tiling.
        txn.update_image(
            img,
            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![64; 64 * 64 * 4]),
            &DirtyRect::All,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        txn = Transaction::new();
        txn.delete_image(img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_tile_decomposition(&mut self) {
        println!("\ttile decomposition...");
        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(DeviceIntSize::new(151, 56)),
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(448.9, 74.0, 151.000_03, 56.).to_box2d());

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            size2(151., 56.0),
            size2(151.0, 56.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        self.rx.recv().unwrap();
        self.wrench.render();

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_very_large_blob(&mut self) {
        println!("\tvery large blob...");

        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(800, 800);

        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );

        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(15000, 15000, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(DeviceIntSize::new(15000, 15000)),
            Some(100),
        );

        let called = Arc::new(AtomicIsize::new(0));
        let called_inner = Arc::clone(&called);

        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
            called_inner.fetch_add(1, Ordering::SeqCst);
        });

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(40., 41., 200., 201.).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 800.0, 800.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            size2(15000.0, 15000.0).into(),
            size2(15000.0, 15000.0),
            size2(0.0, 0.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        // make sure we didn't request too many blobs
        assert!(called.load(Ordering::SeqCst) < 20);

        //use crate::png;
        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));

        // make sure things are in the right spot
        let w = window_rect.width() as usize;
        let h = window_rect.height() as usize;
        let p1 = (40 + (h - 100) * w) * 4;
        assert_eq!(pixels[p1    ], 50);
        assert_eq!(pixels[p1 + 1], 50);
        assert_eq!(pixels[p1 + 2], 150);
        assert_eq!(pixels[p1 + 3], 255);

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_visible_area(&mut self) {
        println!("\tblob visible area...");

        let window_size = self.window.get_inner_size();
        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(50, 20),
                max: point2(450, 420),
            },
            Some(100),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let image_size = size2(400.0, 400.0);

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(10.0, 10.0, 400.0, 400.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        //use super::png;
        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));


        // make sure things are in the right spot
        let w = window_rect.width() as usize;
        let h = window_rect.height() as usize;
        let p1 = (65 + (h - 15) * w) * 4;
        assert_eq!(pixels[p1    ], 255);
        assert_eq!(pixels[p1 + 1], 255);
        assert_eq!(pixels[p1 + 2], 255);
        assert_eq!(pixels[p1 + 3], 255);

        let p2 = (25 + (h - 15) * w) * 4;
        assert_eq!(pixels[p2    ], 221);
        assert_eq!(pixels[p2 + 1], 221);
        assert_eq!(pixels[p2 + 2], 221);
        assert_eq!(pixels[p2 + 3], 255);

        let p3 = (15 + (h - 15) * w) * 4;
        assert_eq!(pixels[p3    ], 50);
        assert_eq!(pixels[p3 + 1], 50);
        assert_eq!(pixels[p3 + 2], 150);
        assert_eq!(pixels[p3 + 3], 255);

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_set_visible_area(&mut self) {
        // In this test we first render a blob with a certain visible area,
        // then change the visible area without updating the blob image.

        println!("\tblob visible area update...");

        let window_size = self.window.get_inner_size();
        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(0, 0),
                max: point2(500, 500),
            },
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(0.0, 0.0, 500.0, 500.0).to_box2d(),
            size2(500.0, 500.0),
            size2(500.0, 500.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        // Render the first display list. We don't care about the result but we
        // want to make sure the next display list updates an already rendered
        // state.
        self.submit_dl(&mut epoch, builder, txn);
        let _ = self.render_and_get_pixels(window_rect);

        // Now render a similar scene with an updated blob visible area.
        // In this test we care about the fact that the visible area was updated
        // without using update_blob_image.

        let mut txn = Transaction::new();

        txn.set_blob_image_visible_area(blob_img, DeviceIntRect {
            min: point2(50, 50),
            max: point2(450, 450),
        });

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
            size2(400.0, 400.0),
            size2(400.0, 400.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let resized_pixels = self.render_and_get_pixels(window_rect);

        // Now render the same scene with a new blob image created with the same
        // visible area as the previous scene, without going through an update.

        let mut txn = Transaction::new();

        let blob_img2 = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img2,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(50, 50),
                max: point2(450, 450),
            },
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
            size2(400.0, 400.0),
            size2(400.0, 400.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img2.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let reference_pixels = self.render_and_get_pixels(window_rect);

        assert_eq!(resized_pixels, reference_pixels);

        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        txn.delete_blob_image(blob_img2);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_offscreen_blob(&mut self) {
        println!("\toffscreen blob update...");

        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(1510, 1510)),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let original_pixels = self.render_and_get_pixels(window_rect);

        let mut epoch = Epoch(1);

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(-10000., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, Transaction::new());

        let _offscreen_pixels = self.render_and_get_pixels(window_rect);

        let mut txn = Transaction::new();

        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(1510, 1510)),
            &Box2D { min: point2(10, 10), max: point2(110, 110) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(2);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        self.compare_pixels(original_pixels, pixels, window_rect.size());

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_retained_blob_images_test(&mut self) {
        println!("\tretained blob images test...");
        let blob_img;
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );

        let mut txn = Transaction::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
        }

        let called = Arc::new(AtomicIsize::new(0));
        let called_inner = Arc::clone(&called);

        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
            assert_eq!(0, called_inner.fetch_add(1, Ordering::SeqCst));
        });

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels_first = self.render_and_get_pixels(window_rect);

        assert_eq!(1, called.load(Ordering::SeqCst));

        // draw the blob image a second time at a different location

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(1.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut txn = Transaction::new();
        txn.resource_updates.clear();

        self.submit_dl(&mut epoch, builder, txn);

        let pixels_second = self.render_and_get_pixels(window_rect);

        // make sure we only requested once
        assert_eq!(1, called.load(Ordering::SeqCst));

        // use png;
        // png::save_flipped("out1.png", &pixels_first, window_rect.size);
        // png::save_flipped("out2.png", &pixels_second, window_rect.size);
        assert!(pixels_first != pixels_second);

        // cleanup
        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_update_epoch_test(&mut self) {
        println!("\tblob update epoch test...");
        let (blob_img, blob_img2);
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut txn = Transaction::new();
        let (blob_img, blob_img2) = {
            let api = &self.wrench.api;

            blob_img = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            blob_img2 = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img2,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            (blob_img, blob_img2)
        };

        // setup some counters to count how many times each image is requested
        let img1_requested = Arc::new(AtomicIsize::new(0));
        let img1_requested_inner = Arc::clone(&img1_requested);
        let img2_requested = Arc::new(AtomicIsize::new(0));
        let img2_requested_inner = Arc::clone(&img2_requested);

        // track the number of times that the second image has been requested
        self.wrench.callbacks.lock().unwrap().request = Box::new(move |requests| {
            for item in requests {
                if item.request.key == blob_img {
                    img1_requested_inner.fetch_add(1, Ordering::SeqCst);
                }
                if item.request.key == blob_img2 {
                    img2_requested_inner.fetch_add(1, Ordering::SeqCst);
                }
            }
        });

        // create two blob images and draw them
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        let info2 = self.make_common_properties(rect(200.0, 60.0, 200.0, 200.0).to_box2d());
        let push_images = |builder: &mut DisplayListBuilder| {
            builder.push_image(
                &info,
                info.clip_rect,
                ImageRendering::Auto,
                AlphaType::PremultipliedAlpha,
                blob_img.as_image(),
                ColorF::WHITE,
            );
            builder.push_image(
                &info2,
                info2.clip_rect,
                ImageRendering::Auto,
                AlphaType::PremultipliedAlpha,
                blob_img2.as_image(),
                ColorF::WHITE,
            );
        };

        push_images(&mut builder);

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_first = self.render_and_get_pixels(window_rect);

        // update and redraw both images
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );
        txn.update_blob_image(
            blob_img2,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        push_images(&mut builder);
        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_second = self.render_and_get_pixels(window_rect);

        // only update the first image
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        push_images(&mut builder);
        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_third = self.render_and_get_pixels(window_rect);

        // the first image should be requested 3 times
        assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
        // the second image should've been requested twice
        assert_eq!(img2_requested.load(Ordering::SeqCst), 2);

        // cleanup
        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_update_test(&mut self) {
        println!("\tblob update test...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = {
            let img = self.wrench.api.generate_blob_image_key();
            txn.add_blob_image(
                img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            img
        };

        // draw the blobs the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_first = self.render_and_get_pixels(window_rect);

        // draw the blob image a second time after updating it with the same color
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // draw the blob image a third time after updating it with a different color
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_third = self.render_and_get_pixels(window_rect);

        assert!(pixels_first != pixels_third);
        self.compare_pixels(pixels_first, pixels_second, window_rect.size());
    }

    // Ensures that content doing a save-restore produces the same results as not
    fn test_save_restore(&mut self) {
        println!("\tsave/restore...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut do_test = |should_try_and_fail| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
            builder.begin();

            let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
            let clip_id = builder.define_clip_rect(
                SpatialId::root_scroll_node(self.wrench.root_pipeline_id),
                rect(110., 120., 200., 200.).to_box2d(),
            );
            let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
            builder.push_rect(
                &self.make_common_properties_with_clip_and_spatial(
                    rect(100., 100., 100., 100.).to_box2d(),
                    clip_chain_id,
                    spatial_id),
                rect(100., 100., 100., 100.).to_box2d(),
                ColorF::new(0.0, 0.0, 1.0, 1.0),
            );

            if should_try_and_fail {
                builder.save();
                let clip_id = builder.define_clip_rect(
                    spatial_id,
                    rect(80., 80., 90., 90.).to_box2d(),
                );
                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
                let space_and_clip = SpaceAndClipInfo {
                    spatial_id,
                    clip_chain_id,
                };
                builder.push_rect(
                    &self.make_common_properties_with_clip_and_spatial(
                        rect(110., 110., 50., 50.).to_box2d(),
                        clip_chain_id,
                        spatial_id),
                    rect(110., 110., 50., 50.).to_box2d(),
                    ColorF::new(0.0, 1.0, 0.0, 1.0),
                );
                builder.push_shadow(
                    &space_and_clip,
                    Shadow {
                        offset: LayoutVector2D::new(1.0, 1.0),
                        blur_radius: 1.0,
                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
                    },
                    true,
                );
                let info = CommonItemProperties {
                    clip_rect: rect(110., 110., 50., 2.).to_box2d(),
                    clip_chain_id,
                    spatial_id,
                    flags: PrimitiveFlags::default(),
                };
                builder.push_line(
                    &info,
                    &info.clip_rect,
                    0.0, LineOrientation::Horizontal,
                    &ColorF::new(0.0, 0.0, 0.0, 1.0),
                    LineStyle::Solid,
                );
                builder.restore();
            }

            {
                builder.save();
                let clip_id = builder.define_clip_rect(
                    spatial_id,
                    rect(80., 80., 100., 100.).to_box2d(),
                );
                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
                builder.push_rect(
                    &self.make_common_properties_with_clip_and_spatial(
                        rect(150., 150., 100., 100.).to_box2d(),
                        clip_chain_id,
                        spatial_id),
                    rect(150., 150., 100., 100.).to_box2d(),
                    ColorF::new(0.0, 0.0, 1.0, 1.0),
                );
                builder.clear_save();
            }

            let txn = Transaction::new();

            self.submit_dl(&mut Epoch(0), builder, txn);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        self.compare_pixels(first, second, window_rect.size());
    }

    // regression test for #2769
    // "async scene building: cache collisions from reused picture ids"
    fn test_blur_cache(&mut self) {
        println!("\tblur cache...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut do_test = |shadow_is_red| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
            builder.begin();
            let shadow_color = if shadow_is_red {
                ColorF::new(1.0, 0.0, 0.0, 1.0)
            } else {
                ColorF::new(0.0, 1.0, 0.0, 1.0)
            };

            builder.push_shadow(
                &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
                Shadow {
                    offset: LayoutVector2D::new(1.0, 1.0),
                    blur_radius: 1.0,
                    color: shadow_color,
                },
                true,
            );
            let info = self.make_common_properties(rect(110., 110., 50., 2.).to_box2d());
            builder.push_line(
                &info,
                &info.clip_rect,
                0.0, LineOrientation::Horizontal,
                &ColorF::new(0.0, 0.0, 0.0, 1.0),
                LineStyle::Solid,
            );
            builder.pop_all_shadows();

            let txn = Transaction::new();
            self.submit_dl(&mut Epoch(0), builder, txn);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        assert_ne!(first, second);
    }

    fn test_capture(&mut self) {
        println!("\tcapture...");
        let path = "../captures/test";
        let layout_size = LayoutSize::new(400., 400.);
        let dim = self.window.get_inner_size();
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, dim.height - layout_size.height as i32),
            size2(layout_size.width as i32, layout_size.height as i32),
        );

        // 1. render some scene

        let mut txn = Transaction::new();
        let image = self.wrench.api.generate_image_key();
        txn.add_image(
            image,
            ImageDescriptor::new(1, 1, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![0xFF, 0, 0, 0xFF]),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(300.0, 70.0, 150.0, 50.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            image,
            ColorF::WHITE,
        );

        let mut txn = Transaction::new();

        txn.set_display_list(
            Epoch(0),
            builder.end(),
        );
        txn.generate_frame(0, RenderReasons::TESTING);

        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        let pixels0 = self.render_and_get_pixels(window_rect);

        // 2. capture it
        self.wrench.api.save_capture(path.into(), CaptureBits::all());

        // 3. set a different scene

        builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let mut txn = Transaction::new();
        txn.set_display_list(
            Epoch(1),
            builder.end(),
        );
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        // 4. load the first one

        let mut documents = self.wrench.api.load_capture(path.into(), None);
        let captured = documents.swap_remove(0);

        // 5. render the built frame and compare
        let pixels1 = self.render_and_get_pixels(window_rect);
        self.compare_pixels(pixels0.clone(), pixels1, window_rect.size());

        // 6. rebuild the scene and compare again
        let mut txn = Transaction::new();
        txn.set_root_pipeline(captured.root_pipeline_id.unwrap());
        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(captured.document_id, txn);
        let pixels2 = self.render_and_get_pixels(window_rect);
        self.compare_pixels(pixels0, pixels2, window_rect.size());
    }

    fn test_zero_height_window(&mut self) {
        println!("\tzero height test...");

        let layout_size = LayoutSize::new(120.0, 0.0);
        let window_size = DeviceIntSize::new(layout_size.width as i32, layout_size.height as i32);
        let doc_id = self.wrench.api.add_document(window_size);

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(
            LayoutRect::from_size(LayoutSize::new(100.0, 100.0))
        );
        builder.push_rect(
            &info,
            info.clip_rect,
            ColorF::new(0.0, 1.0, 0.0, 1.0),
        );

        let mut txn = Transaction::new();
        txn.set_root_pipeline(self.wrench.root_pipeline_id);
        txn.set_display_list(
            Epoch(1),
            builder.end(),
        );
        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(doc_id, txn);

        // Ensure we get a notification from rendering the above, even though
        // there are zero visible pixels
        assert!(self.rx.recv().unwrap() == NotifierEvent::WakeUp { composite_needed: true });
    }


    fn test_hit_testing(&mut self) {
        println!("\thit testing test...");

        let layout_size = LayoutSize::new(400., 400.);
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        // Add a rectangle that covers the entire scene.
        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        builder.push_hit_test(
            LayoutRect::from_size(layout_size),
            ClipChainId::INVALID,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 1),
        );

        // Add a simple 100x100 rectangle at 100,0.
        builder.push_hit_test(
            LayoutRect::from_origin_and_size(
                LayoutPoint::new(100., 0.),
                LayoutSize::new(100., 100.)
            ),
            ClipChainId::INVALID,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 2),
        );

        let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
            ComplexClipRegion::new(
                *rect,
                BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
                ClipMode::Clip
            )
        };

        // Add a rectangle that is clipped by a rounded rect clip item.
        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
        let temp_clip_id = builder.define_clip_rounded_rect(
            space_and_clip.spatial_id,
            make_rounded_complex_clip(&rect, 20.),
        );
        let clip_chain_id = builder.define_clip_chain(None, vec![temp_clip_id]);
        builder.push_hit_test(
            rect,
            clip_chain_id,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 4),
        );

        // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
        let clip_id = builder.define_clip_rounded_rect(
            space_and_clip.spatial_id,
            make_rounded_complex_clip(&rect, 20.),
        );
        let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
        builder.push_hit_test(
            rect,
            clip_chain_id,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 5),
        );

        let mut epoch = Epoch(0);
        let txn = Transaction::new();
        self.submit_dl(&mut epoch, builder, txn);

        // We render to ensure that the hit tester is up to date with the current scene.
        self.rx.recv().unwrap();
        self.wrench.render();

        let hit_test = |point: WorldPoint| -> HitTestResult {
            self.wrench.api.hit_test(
                self.wrench.document_id,
                point,
            )
        };

        let assert_hit_test = |point: WorldPoint, tags: Vec<ItemTag>| {
            let result = hit_test(point);
            assert_eq!(result.items.len(), tags.len());

            for (hit_test_item, item_b) in result.items.iter().zip(tags.iter()) {
                assert_eq!(hit_test_item.tag, *item_b);
            }
        };

        // We should not have any hits outside the boundaries of the scene.
        assert_hit_test(WorldPoint::new(-10., -10.), Vec::new());
        assert_hit_test(WorldPoint::new(-10., 10.), Vec::new());
        assert_hit_test(WorldPoint::new(450., 450.), Vec::new());
        assert_hit_test(WorldPoint::new(100., 450.), Vec::new());

        // The top left corner of the scene should only contain the background.
        assert_hit_test(WorldPoint::new(50., 50.), vec![(0, 1)]);

        // The middle of the normal rectangle should be hit.
        assert_hit_test(WorldPoint::new(150., 50.), vec![(0, 2), (0, 1)]);

        let test_rounded_rectangle = |point: WorldPoint, size: WorldSize, tag: ItemTag| {
            // The cut out corners of the rounded rectangle should not be hit.
            let top_left = point + WorldVector2D::new(5., 5.);
            let bottom_right = point + size.to_vector() - WorldVector2D::new(5., 5.);

            assert_hit_test(
                WorldPoint::new(point.x + (size.width / 2.), point.y + (size.height / 2.)),
                vec![tag, (0, 1)]
            );

            assert_hit_test(top_left, vec![(0, 1)]);
            assert_hit_test(WorldPoint::new(bottom_right.x, top_left.y), vec![(0, 1)]);
            assert_hit_test(WorldPoint::new(top_left.x, bottom_right.y), vec![(0, 1)]);
            assert_hit_test(bottom_right, vec![(0, 1)]);
        };

        test_rounded_rectangle(WorldPoint::new(100., 100.), WorldSize::new(100., 100.), (0, 4));
        test_rounded_rectangle(WorldPoint::new(200., 100.), WorldSize::new(100., 100.), (0, 5));
    }

    fn test_clear_cache(&mut self) {
        println!("\tclear cache test...");

        self.wrench.api.send_message(ApiMsg::DebugCommand(DebugCommand::ClearCaches(ClearCache::all())));

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let txn = Transaction::new();
        let mut epoch = Epoch(0);
        self.submit_dl(&mut epoch, builder, txn);

        self.rx.recv().unwrap();
        self.wrench.render();
    }
}

[ Dauer der Verarbeitung: 0.44 Sekunden  ]